つづけて、ログイン処理、、、の前にユーザ情報として画像ファイルを表示できるようにします。(これで見た目がグッとそれっぽくなりますね)
ユーザ情報に画像ファイルを紐付ける
まずは画像を保存するための情報として、usersテーブルにimage_nameカラムを追加します。
rails g migration add_image_name
ファイル(db/migrate/20181010071154_add_image_name.rb)が生成されるので、これに追記するカラムの定義を記載して、「rails db:migrate」を実行すればデータベースに反映されます。
def change add_column :users, :image_name, :string end
ユーザに紐付く画像ファイルの仕様は以下の2点とします。
・初期登録時には「default_user.jpg」という初期画像で登録する
・ユーザ登録後にユーザ変更画面から画像を変更可能とする
まずはユーザ登録処理時に画像情報(image_name)を追加します。
@user = User.new(name: params[:name], email: params[:email]) ↓ @user = User.new(name: params[:name], email: params[:email], image_name: "default_user.jpg")
ユーザ情報の詳細表示画面で、紐付く画像を表示できるようにビューにimgタグを追記します。
<img src="/user_images/<%= @user.image_name %>">
ポイントは、データベースにはファイル名だけ記録し、ファイルの実態は決まったディレクトリに保存しておく、といったところでしょうか。
ユーザ編集画面で画像ファイルをアップロードできるようビューに追記します。
ポイントは、バイナリファイルを扱う時には form_tagのオプションに{multipart: true}を付ける点です。(忘れがち)
<%= form_tag("/users/#{@user.id}/update",{multipart: true}) do %> <p>ユーザー名</p> <input name="name" value="<%= @user.name %>"> <p>メールアドレス</p> <input name="email" value="<%= @user.email %>"> <p>画像</p> <input type="file" name="image_name"> <input type="submit" value="保存"> <% end %>
つづけて、アクション側(users#update)で画像ファイルの保存処理を追加します。
ここのポイントは、画像ファイルの実体は決まったフォルダ(public/user_images)にユーザID名.jpgという名前で保存する点です。
if params[:image_name] @user.image_name = "#{@user.id}.jpg" File.binwrite("public/user_images/#{@user.image_name}", params[:image_name].read) end
ログイン画面の作成
ようやくログイン画面の作成です。
まずはデータベースの準備として、usersテーブルにパスワードの項目を追加します。
画像情報の紐付けと同じように、「rails g migration add_password」で生成したファイルに以下を追記して、「rails db:migrate」で反映します。
def change add_column :users, :password, :string end
あわせてパスワードが空欄だと困るので、userモデルにバリデーションを追記します。
validates :password, {presence: true}
ログイン画面へのルーティングを追加します。(/loginでアクセスできるようにします)
get "/login" => "users#login_form"
アクションは特にすること無いので空で作成しておきます。
def login_form end
ビュー(login_form.html.erb)を作成します。
<% if @error_message %> <div class="form-error"> <%= @error_message %> </div> <% end %> <%= form_tag("/login") do %> <p>メールアドレス</p> <input type="text" name="email" value="<%= @email %>"> <p>パスワード</p> <input type="password" name="password" value="<%= @password %>"> <input type="submit" value="ログイン"> <% end %>
共通レイアウトのヘッダー部分にログイン画面へのリンクを追加します。
<li> <%= link_to("ログイン", "/login") %> </li>
ログイン処理の作成
ログイン処理のルーティングを追加します。ログイン画面と同じ”/login”で重複しそうに思いますが、こっちはPOSTメソッドなので重複しません。
post "/login" => "users#login"
アクションを作成します。emailとpasswordで検索し、レコードがあれば認証成功として投稿一覧画面へ移動します。
ポイントはsessionという特別な変数にユーザIDをセットして保持しておく点でしょうか。この変数はログアウトするまで持ち続けます。
また、失敗した場合、再度ログイン画面を表示しますが、入力内容を保持するためパラメータで受け取った値を変数に格納しておきます。
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 @email = params[:email] @password = params[:password] @error_message = "メールアドレスまたはパスワードが間違っています" render("users/login_form") end end
ユーザ登録時の処理を変更
登録時にパスワードの入力が無かったので、ユーザ登録ビュー(users/new.html.erb)にパスワード入力欄を追加します。
<p>パスワード</p> <input type="password" name="password" value="<%= @user.password %>">
ユーザ登録処理(users#create)にパスワードの登録を追加し、あわせてログイン状態(session変数をセット)にしておきます。
def create @user = User.new(name: params[:name], email: params[:email], image_name: "default_user.jpg", password: params[:password]) if @user.save flash[:notice] = "ユーザー登録が完了しました" session[:user_id] = @user.id # セッション情報をセット redirect_to("/posts/index") # 投稿一覧画面に遷移 else render("users/new") end end
ログアウト機能の作成
まずはルーティングを追加します。
post "/logout" => "users#logout"
共通レイアウトのヘッダー部分にログアウトのリンクを追加します。(ここは画面を介さないのでPOSTメソッドでのリンクとします)
<li> <%= link_to("ログアウト", "/logout", {method: "post"}) %> </li>
アクションを作成します。やることはsession変数をnilにしてあげるだけです。
def logout session[:user_id] = nil flash[:notice] = "ログアウトしました" redirect_to("/login") end
ログイン有無でヘッダ表示の切り替え
ヘッダに表示されるリンクですが、今のままだとログイン前に必要な機能と、ログイン後に必要な機能がゴチャッと表示されているので、ログイン前と後とで表示する項目を整理します。
共通レイアウトビュー(application.html.erb)を編集し、セッション情報の有無でヘッダの表示内容を切り替えます。
<% if session[:user_id] %> <li> 現在ログインしているユーザーのid: <%= session[: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> <% else %> <li> <%= link_to("TweetAppとは", "/about") %> </li> <li> <%= link_to("新規登録", "/signup") %> </li> <li> <%= link_to("ログイン", "/login") %> </li> <% end %>
ログイン機能の改良
ヘッダにユーザIDを表示している部分を、ユーザ名を表示するように改良し、あわせてユーザ詳細画面へのリンクにします。
アプリケーション全体に関係するアクションなので、application_controller.rbに追記します。
ポイントは before_action で指定したアクションは、各アクションの実行前に動くので、各アクション共通で利用する@current_user変数をここで定義しています。
before_action :get_current_user def get_current_user @current_user = User.find_by(id: session[:user_id]) end
現在ログインしているユーザーのid: <%= session[:user_id] %> ↓ <%= link_to(@current_user.name, "/users/#{@current_user.id}") %>
ログインしていない場合にアクセス制限をかける
現状だと、URLさえ分かればログインしていなくても全ての機能にアクセス可能な状態となっているので、ログイン前の状態ではアクセスできないよう制限をかけます。
ログインチェックの関数は全アクションで共通なので、これもapplication_controller.rbに作成します。
def authenticate_user if session[:user_id] == nil #ログインしていない場合 flash[:notice] = "ログインが必要です" #フラッシュを表示 redirect_to("/login") #ログイン画面にリダイレクト end end
つづけて、postsコントローラの全アクション、usersコントローラのユーザ登録以外(index, show, edit, update)にてbefore_actionでさっきのログインチェック関数を呼びます。
before_action :authenticate_user # 全アクションで実行 before_action :authenticate_user, {only: [:index, :show, :edit, :update]} #指定アクションだけ実行
ログインしている場合にアクセス制限をかける
既にログインしている場合、ログイン画面やユーザ登録画面は不要なのでアクセスできないようにします。
まずは、さっきと同じようにapplication_controller.rbにチェック用の関数を作成します。
def is_login? if @current_user flash[:notice] = "すでにログインしています" redirect_to("/posts/index") end end
usersコントローラにて以下を追記して制限をかけます。
before_action :is_login?, {only: [:new, :login_form, :login, :create]}
あわせてhomeコントローラにてTOPページへのアクセスもログイン後は不要ということで制限しておきます。
before_action :is_login?, {only: :top}
自分以外のユーザ情報を変更できないようにする
現状は自分以外のユーザについてもユーザ情報の変更ができてしまうので、自分の情報のみ変更できるようにアクセス制限をかけます。
まずはユーザ詳細ビュー(users/show.html.erb)で自分の画面のみ「編集」リンクを表示できるようにします。
<% if @current_user.id == @user.id %> <%= link_to("編集", "/users/#{@user.id}/edit") %> <% end %>
これだけだとURL(/users/1/editとか)に直接アクセス可能なままなので、こちらもアクセス制限をかけます。
関係するのはユーザ情報だけなので、usersコントローラに以下を追記します。
ポイントは、@current_user.id が integer型で、params[:id]がstring型なので、比較のため、params[:id].to_i としてデータ型をあわせないと上手く比較できない、といったところでしょうか。
before_action :ensure_correct_user, {only: [:edit, :update]} def ensure_correct_user if @current_user.id != params[:id].to_i flash[:notice] = "権限がありません" redirect_to("/posts/index") end end
ここまでで、ひとまずこのコースは完了です。
次はユーザと投稿の紐付け、いいね機能の実装、パスワード暗号化を実施します。つづく。。
コメント