9b. ProgateでRuby on RailsⅥ~Ⅷ(備忘録)

Ⅵ~Ⅷ章では、ユーザ機能及びログイン機能を追加しています。

Ruby on Rails5 学習コースⅥ

・Userモデルとusersテーブルの作成

$ rails g model User name:string email:string
$ rails db:migrate

・Userモデルとusersテーブルの作成

# (models/user.rb)
	validates :email, {uniqueness: true}

・ユーザ新規登録ページの作成

# (users/new.html.erb)
<p>ユーザ名</p>
<input name="name" value="<%= @user.name %>">		# 1行のフォームは<input>タグを使用する
<input name="email" value="<%= @user.email %>">		# value属性にRubyのコードを埋め込む
<input type="submit" value="新規登録">

・完成版

# (models/user.rb)
  validates :name, {presence: true}
  validates :email, {presence: true, uniqueness: true}

# (routes.rb)
  get "users/index" => "users#index"
  get "signup" => "users#new"
  get "users/:id" => "users#show"
  post "users/create" => "users#create"

  get "users/:id/edit" => "users#edit"
  post "users/:id/update" => "users#update"

# (users_controller.rb)
  def index
    @users = User.all
  end
  
  def show
    @user = User.find_by(id: params[:id])
  end
  
  def new
    @user = User.new
  end
  
  def create
    @user = User.new(name: params[:name], email: params[:email])
    if @user.save
      flash[:notice] = "ユーザー登録が完了しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/new")
    end
  end
  
  def edit
    @user = User.find_by(id: params[:id])
  end
  
  def update
    @user = User.find_by(id: params[:id])
    @user.name = params[:name]
    @user.email = params[:email]
    if @user.save
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/edit")
    end
  end

# (layouts/application.html.erb)
    <header>
      <div class="header-logo">
        <%= link_to("TweetApp", "/") %>
      </div>
      <ul class="header-menus">
        <li>
          <%= link_to("TweetAppとは", "/about") %>
        </li>
        <li>
          <%= link_to("投稿一覧", "/posts/index") %>
        </li>
        <li>
          <%= link_to("新規投稿", "/posts/new") %>
        </li>
        <li>
          <%= link_to("ユーザー一覧", "/users/index") %>		# 追加
        </li>
        <li>
          <%= link_to("新規登録", "/signup") %>				# 追加
        </li>
      </ul>
    </header>

# (index.html.erb)
    <% @users.each do |user| %>
          <%= link_to(user.name, "/users/#{user.id}") %>
    <% end %>

# (show.html.erb)
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <%= link_to("編集", "/users/#{@user.id}/edit") %>

# (new.html.erb)
        <% @user.errors.full_messages.each do |message| %>
            <%= message %>
        <% end %>
        
        <%= form_tag("/users/create") do %>
          <p>ユーザー名</p>
          <input name="name" value="<%= @user.name %>">
          <p>メールアドレス</p>
          <input name="email" value="<%= @user.email %>">
          <input type="submit" value="新規登録">
        <% end %>

# (edit.html.erb)
        <% @user.errors.full_messages.each do |message| %>	# エラーメッセージを表示
          <div class="form-error">
            <%= message %>
          </div>
        <% end %>

        <%= form_tag("/users/#{@user.id}/update") do %>		# フォームの送信先を指定
          <p>ユーザー名</p>
          <input name="name" value="<%= @user.name %>">
          <p>メールアドレス</p>
          <input name="email" value="<%= @user.email %>">
          <input type="submit" value="保存">
        <% end %>


・確認すること

  • ヘッダーに「ユーザ一覧」と「新規登録」のリンクが表示されていて、画面遷移できる
  • 新規登録画面にて、ユーザの新規登録ができ、適宜エラーメッセージとサクセスメッセージが表示される
  • メールアドレスが一意であるなどのバリデーションが有効になっている
  • ユーザ一覧ページから各ユーザの詳細ページへ画面遷移できる
  • ユーザの詳細ページの編集ボタンより編集ができ、適宜エラーメッセージとサクセスメッセージが表示される


Ruby on Rails5 学習コースⅦ

・テーブルにカラムを追加する

$ rails g migration add_image_name_to_users				# マイグレーションファイルのみを作成

# (db/migrate/2018MMDDHHMMSS_add_image_name_to_users)	# 作成されたマイグレーションファイルを以下のように編集
	def change
		add_column :users, :image_name, :string			# usersテーブルにimage_nameカラムを追加
	end

$ rails db:migrate									# DBに変更を反映

・ユーザ登録時に初期画像を保存

# (users_controller.rb)
def create
	@user = User.new(
		name: params[:name],
		email: params[:email],
		image_name: "default_user.jpg"			# ユーザ登録時のimage_nameカラムの値を設定
	)
	if @user.save
		:
end

・ビューに画像を表示する

# (users/show.html.erb)
<img src="<%= "/user_images/#{@user.image_name}" %>">

・画像選択ボタンを表示する

# (users/edit.html.erb)
<%= form_tag("...", {multipart: true}) do %>		# 画像の送信にはform_tagに{multipart: true}を追加する
	<h2>画像</h2>
	<input name="image" type="file">				# 「type="file"」とすることで画像を設定できるようになる

・Rubyのコードでファイルを作成する

$ rails console
> File.write("public/sample.txt", "Hello World")

・画像をDBに保存する

# (users_controller.rb)
def update
		:
	if params[:image]
		@user.image_name = "#{@user.id}.jpg"		# 画像のファイル名をimage_nameカラムに保存
		image = params[:image]						# 送信されたファイルの情報をimage変数に格納
		# image変数の画像データをreadメソッドを用いて取得し、ファイルに書き込む
		File.binwrite("public/user_images/#{@user.image_name}", image.read)
	end
		:
end

・完成版

# (db/migrate/2018MMDDHHMMSS_add_image_name_to_users)
  def change
    add_column :users, :image_name, :string
  end

# (users_controller.rb)
  def index
    @users = User.all
  end
  
  def show
    @user = User.find_by(id: params[:id])
  end
  
  def new
    @user = User.new
  end
  
  def create
    @user = User.new(
      name: params[:name],
      email: params[:email],
      image_name: "default_user.jpg"					# 追加
    )
    if @user.save
      flash[:notice] = "ユーザー登録が完了しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/new")
    end
  end
  
  def edit
    @user = User.find_by(id: params[:id])
  end
  
  def update
    @user = User.find_by(id: params[:id])
    @user.name = params[:name]
    @user.email = params[:email]
    if params[:image]									# 画像を保存する処理を追加
      @user.image_name = "#{@user.id}.jpg"
      image = params[:image]
      File.binwrite("public/user_images/#{@user.image_name}", image.read)
    end
    if @user.save
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/edit")
    end
  end

# (users/index.html.erb)
    <% @users.each do |user| %>
      <div class="users-index-item">
        <div class="user-left">
          <img src="<%= "/user_images/#{user.image_name}" %>">	# 追加
        </div>
        <div class="user-right">
          <%= link_to(user.name, "/users/#{user.id}") %>
        </div>
      </div>
    <% end %>

# (users/show.html.erb)
      <img src="<%= "/user_images/#{@user.image_name}" %>">				# 追加
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <%= link_to("編集", "/users/#{@user.id}/edit") %>

# (users/edit.html.erb)
        <% @user.errors.full_messages.each do |message| %>
          <div class="form-error">
            <%= message %>
          </div>
        <% end %>
        <%= form_tag("/users/#{@user.id}/update", {multipart: true}) do %>	# 変更
          <p>ユーザー名</p>
          <input name="name" value="<%= @user.name %>">
          <p>画像</p>
          <input name="image" type="file">						# 追加
          <p>メールアドレス</p>
          <input name="email" value="<%= @user.email %>">
          <input type="submit" value="保存">
        <% end %>


・確認すること

  • 編集画面で画像選択ボタンが表示されていて、画像が保存/表示される


Ruby on Rails5 学習コースⅧ

・ログインページの作成

# (routes.rb)
	get "login" => "users#login_form"

# (users_controller.rb)
	def login_form
	end

# (users/login_form.html.erb)
	<p>パスワード</p>
	<input type="password">					# type="password"を指定して入力内容を伏字にする

・パスワードカラムの追加

$ rails g migration add_password_to_users		# マイグレーションファイル作成

# (YYYYMMDDHHMMHH_add_password_to_users.rb)
	def change
		add_column :users, :pasword, :string	# usersテーブルにpasswordカラムを追加
	end

$ rails db:migrate								# DBに変更を反映

・バリデーションの設定

# (models/user.rb)
	validates :password, {presence: true}

・ルーティングとアクションの追加

# (routes.rb)
	post "login" => "users#login"			# フォームの値を送信する為postを使用

# (users/login_form.html.erb)
	<%= form_tag("/login") do %>
		<p>メールアドレス</p>
		<input name="email">
		<p>パスワード</p>
		<input type="password" name="password">
	<% end %>

・ログイン機能の実装

# (users_controller.rb)
	def login
		# ログインするユーザを特定する
		@user = User.find_by(email: params[:email], password: params[:password])
		if @user														# ユーザが存在する場合
			flash[:notice] = "ログインしました"
			redirect_to("/posts/index")
		else															# ユーザが存在しない場合
			@error_message = "メールアドレスまたはパスワードが間違っています"
			@email = params[:email]
			@password = params[:password]
			render("users/login_form")
		end
	end

# (users/login_form.html.erb)
	<% if @error_message %>
		<%= @error_message %>
	<% end %>
	<%= form_tag("/login") do %>
		<p>メールアドレス</p>
		<input name="email" value="<%= @email %>">
		<p>パスワード</p>
		<input type="password" name="password" value="<%= @password %>">
	<% end %>

・session変数を使用してユーザ情報をブラウザで保持する

# (users_controller.rb)
	def login
		@user = User.find_by(email: params[:email], password: params[:password])
		if @user
			session[:user_id] = @user.id
				:
	end

・ログアウト機能の実装

# (routes.rb)
	post "logout" => "users#logout"		# sessionの値を変更するのでpostを使用

# (users_controller.rb)
	def logout
		session[:user_id] = nil
		flash[:notice] = "ログアウトしました"
		redirect_to("/login")
	end

# (layouts/application.html.erb)
	<% if session[:user_id] %>
		<li>
			<%= session[:user_id] %>
		</li>
			:
	

・ユーザ登録時のログイン

# (new.html.erb)
		:
	# パスワード用のフォームを追加
	<p>パスワード</p>
	<input name="password" type="password" value="<%= @user.password %>">
		:

# (users_controller.rb)
	def create
		@user = User.new(
			name: params[:name],
			email: params[:email],
			image_name: "default_user.jpg",
			password: params[:password]				# passwordカラムの値を設定
		)
	end

・ユーザ登録時にログイン状態にする

# (users_controller.rb)
	def create
		@user = User.new(...)
		if @user.save
			session[:user_id] = @user.id			# 新規登録時にログイン状態にする
				:

・ユーザ名の表示

# (layouts/appilcation.html.erb)
	<% current_user = User.find_by(id: session[:user_id]) %>
	<li>
		<%= link_to(current_user.name, "/users/#{current_user.id}") %>
	</li>

・アクション側で共通の変数を定義する

# (application_controller.rb)
	before_action :set_current_user		# 全てのコントローラーで共通する処理は纏める

	def set_current_user
		@current_user = User.find_by(id: session[:user_id])
	end

・アクセス制限の処理を共通化する

# (application_controller.rb)
	def authenticate_user
		if @current_user == nil
			flash[:notice] = "ログインが必要です"
			redirect_to("/login")
		end
	end

# (users_controller.rb)
	# onlyを用いて適用したいアクションを指定
	before_action :authenticate_user, {only: [:index, :show, :edit, :update]}

・ログインユーザがアクセスできないページの設定

# (application_controller.rb)
	def forbid_login_user
		if @current_user
			flash[:notice] = "すでにログインしています"
			redirect_to("/posts/index")
		end
	end

# (home_controller.rb)
	before_action :forbid_login_user, {only: [:top]}

# (users_controller.rb)
	before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]}

・ユーザの編集を制限する

# (users/show.html.erb)
	# 詳細ページのユーザidとログインユーザidが等しい場合のみ編集ボタンを表示
	<% if @user.id == @current_user.id %>
		<%= link_to("編集", "/users/#{@user.id}/edit") %>
	<% end %>

# (users_controller.rb)
	before_action :ensure_correct_user, {only: [:edit, :update]}

	def ensure_correct_user
		# 正しいユーザかどうかを判定する
		if @current_user.id != params[:id].to_i		# to_iメソッドで文字列を数値に変換
			flash[:notice] = "権限がありません"
			redirect_to("/posts/index")
		end
	end

・完成版

# (routes.rb)
  get "login" => "users#login_form"			# 追加
  post "login" => "users#login"				# 追加
  post "logout" => "users#logout"			# 追加

  post "users/:id/update" => "users#update"
  get "users/:id/edit" => "users#edit"
  post "users/create" => "users#create"
  get "signup" => "users#new"
  get "users/index" => "users#index"
  get "users/:id" => "users#show"

  get "posts/index" => "posts#index"
  get "posts/new" => "posts#new"
  get "posts/:id" => "posts#show"
  post "posts/create" => "posts#create"
  get "posts/:id/edit" => "posts#edit"
  post "posts/:id/update" => "posts#update"
  post "posts/:id/destroy" => "posts#destroy"
  
  get "/" => "home#top"
  get "about" => "home#about"

# (application_controller.rb)
  before_action :set_current_user
  
  def set_current_user
    @current_user = User.find_by(id: session[:user_id])
  end
  
  def authenticate_user
    if @current_user == nil
      flash[:notice] = "ログインが必要です"
      redirect_to("/login")
    end
  end
  
  def forbid_login_user
    if @current_user
      flash[:notice] = "すでにログインしています"
      redirect_to("/posts/index")
    end
  end

# (home_controller.rb)
  before_action :forbid_login_user, {only: [:top]}		# 追加

  def top
  end
  
  def about
  end

# (posts_controller.rb)
  before_action :authenticate_user						# 追加
  
  def index
    @posts = Post.all.order(created_at: :desc)
  end
  
  def show
    @post = Post.find_by(id: params[:id])
  end
  
  def new
    @post = Post.new
  end
  
  def create
    @post = Post.new(content: params[:content])
    if @post.save
      flash[:notice] = "投稿を作成しました"
      redirect_to("/posts/index")
    else
      render("posts/new")
    end
  end
  
  def edit
    @post = Post.find_by(id: params[:id])
  end
  
  def update
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    if @post.save
      flash[:notice] = "投稿を編集しました"
      redirect_to("/posts/index")
    else
      render("posts/edit")
    end
  end
  
  def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    flash[:notice] = "投稿を削除しました"
    redirect_to("/posts/index")
  end

# (users_controller.rb)
  before_action :authenticate_user, {only: [:index, :show, :edit, :update]}			# 追加
  before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]}	# 追加
  before_action :ensure_correct_user, {only: [:edit, :update]}						# 追加
  
  def index
    @users = User.all
  end
  
  def show
    @user = User.find_by(id: params[:id])
  end
  
  def new
    @user = User.new
  end
  
  def create
    @user = User.new(
      name: params[:name],
      email: params[:email],
      image_name: "default_user.jpg",
      password: params[:password]						# 追加
    )
    if @user.save
      session[:user_id] = @user.id
      flash[:notice] = "ユーザー登録が完了しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/new")
    end
  end
  
  def edit
    @user = User.find_by(id: params[:id])
  end
  
  def update
    @user = User.find_by(id: params[:id])
    @user.name = params[:name]
    @user.email = params[:email]
    
    if params[:image]
      @user.image_name = "#{@user.id}.jpg"
      image = params[:image]
      File.binwrite("public/user_images/#{@user.image_name}", image.read)
    end
    
    if @user.save
      flash[:notice] = "ユーザー情報を編集しました"
      redirect_to("/users/#{@user.id}")
    else
      render("users/edit")
    end
  end
  
  def login_form
  end
  
  def login
    @user = User.find_by(email: params[:email], password: params[:password])
    if @user
      session[:user_id] = @user.id
      flash[:notice] = "ログインしました"
      redirect_to("/posts/index")
    else
      @error_message = "メールアドレスまたはパスワードが間違っています"
      @email = params[:email]
      @password = params[:password]
      render("users/login_form")
    end
  end
  
  def logout
    session[:user_id] = nil
    flash[:notice] = "ログアウトしました"
    redirect_to("/login")
  end
  
  def ensure_correct_user											# 追加
    if @current_user.id != params[:id].to_i
      flash[:notice] = "権限がありません"
      redirect_to("/posts/index")
    end
  end

# (layouts/application.html.erb)
    <header>
      <div class="header-logo">
        <% if @current_user %>
          <%= link_to("TweetApp", "/posts/index") %>
        <% else %>
          <%= link_to("TweetApp", "/") %>
        <% end %>
      </div>
      <ul class="header-menus">
        <% if @current_user %>
          <li><%= link_to(@current_user.name, "/users/#{@current_user.id}") %></li>
          <li><%= link_to("投稿一覧", "/posts/index") %></li>
          <li><%= link_to("新規投稿", "/posts/new") %></li>
          <li><%= link_to("ユーザー一覧", "/users/index") %></li>
          <li><%= link_to("ログアウト", "/logout", {method: :post}) %></li>		# postを指定
        <% else %>
          <li><%= link_to("TweetAppとは", "/about") %></li>
          <li><%= link_to("新規登録", "/signup") %></li>
          <li><%= link_to("ログイン", "/login") %></li>
        <% end %>
      </ul>
    </header>

# (users/show.html.erb)
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <% if @user.id == @current_user.id %>								# 追加
        <%= link_to("編集", "/users/#{@user.id}/edit") %>
      <% end %>

# (/users/login_form.html.erb)
        <% if @error_message %>
          <div class="form-error">
            <%= @error_message %>
          </div>
        <% end %>
        <%= form_tag("/login") do %>
          <p>メールアドレス</p>
          <input name="email" value="<%= @email %>">
          <p>パスワード</p>
          <input type="password" name="password" value="<%= @password %>">
          <input type="submit" value="ログイン">
        <% end %>

# (/users/new.html.erb)
        <%= form_tag("/users/create") do %>
          <p>ユーザー名</p>
          <input name="name" value="<%= @user.name %>">
          <p>メールアドレス</p>
          <input name="email" value="<%= @user.email %>">
          <p>パスワード</p>
          <input type="password" name="password" value="<%= @user.password %>">
          <input type="submit" value="新規登録">
        <% end %>


・確認すること

  • ログイン前のヘッダーのリンクは、「TweetApp」「TweetAppとは」「新規登録」「ログイン」
  • 「TweetApp」は、トップページへ画面遷移する
  • 「ログイン」より、ログインページへ画面遷移し、ログインできる(パスワードは伏字)
  • ログイン時、エラーメッセージ又はサクセスメッセージが表示される
  • ログイン後のヘッダーのリンクは、「TweetApp」「ログインユーザ名」「投稿一覧」「新規投稿」「ユーザ一覧」「ログアウト」
  • 「TweetApp」は、投稿一覧ページへ画面遷移する
  • 「ログインユーザ名」は、ユーザ詳細ページへ画面遷移する
  • 「ログアウト」より、ログアウトできる
  • ログイン前のアクセスが制限できている
  • ログインユーザがアクセスできないページが設定できている
  • 他ユーザの編集ができないよう制限できている