Railsの学習(6日目)
ユーザーがいない場合の処理
登録されていない値でログインした場合、再度ログイン画面に戻したり、エラ〜メッセージを出したり、フォームに打ち込んだ内容を初期値として表示できるようにする。
エラーメッセージを表示するためには「find_byで検索したが見つからなかった」という結果を伝えるためのコードを書く。
変数passwordとemailを定義し、アクションに残っている情報を入力した値として初期値となるようにする。
ユーザーがログインした後、その情報を保持できるようにしなければならない。そこで使うのが「変数session」である。sessionに代入された値はブラウザ側に保存され、それ以降はブラウザ側からセッションがrailsに送られることでログイン情報が保持される。
sessionのキー名はuser_idとし、値を代入する。これによりユーザーIDがセッションキーとなり、ログインユーザーの情報を保持することができる。
ログイン中のユーザーIDを表示するために、画像のようにログイン中のユーザーid:<%=session[:user_id]%>とすることで、ブラウザから送信された変数sessionの値を取得することができます。
ログアウト機能を作る。
ログアウト機能を作るには、ログインで入手したsession idを削除すればいい。
つまりセッションIDをからにすれば良いので、session idにnilを代入する処理を行うのである。ログアウト=session idへnilを代入する処理ということだ。
そのためにlogoutのrootingを作り、logoutのアクションを作る。この時、rootingでは「getではなくpostを使う」。nilを代入してログアウトした際にはフラッシュメッセージが表示されるようにし、ログイン画面にリダイレクトする。
ちなみにgetとpostの使い分けは以下の通り。何かを更新するときはpost,そうでないときはgetと言ったところだろうか。
ログイン状態とログアウト状態でヘッダーに表示される内容を変えるにはログイン前後でsession[:user_id]が変わることを利用して、リンクの内容を変えるようにする。
ユーザー登録時にログインするように設定するには。
まだユーザー登録フォームにパスワード入力フォームがないので、それを作る。
まずは左の画像のようにパスワードようのフォームを作る。そして右のようにユーザー登録時にpasswordカラムにppasswordデータが保存されるようにする・
登録したユーザーがそのままログイン状態になるよう、入力したidの値をsessionに代入することで登録と同時にログイン状態にする。
ユーザー名の表示
ユーザー名の表示をするために、find_byメソッドを用いてデータベースからsession[:user_id]を取得し、現在のユーザー名を表す「current_user.name」に代入する。それのリンクと表示をコーディングすればOK
さっきはviewファイルで変数を定義したが、本来はaction側で定義するもの。
各アクションに対応したビューファイルは、application.html.erbの<%= yield %>部分に代入され表示されている。これにより、application.html.erbは全アクションから呼び出される。
application.html.erbは全てのアクションで呼び出されるため、application.html.erbでアクションを呼び出そうと思うと全てのアクションで@current_userを定義する必要が出てくる。
そこで共通の処理を行うアクションがあった場合、それをまとめると便利である。それを可能にするのが「before_action」であり、beforeの名の通り、アクションが呼び出される際には必ずbefore_actionが実行される。これにより全アクションで共通する処理を一箇所にまとめることができる。
画像のようにset_current_userメソッドを定義し、before_actionに指定することで、全コントローラーの全アクションで@current_userを定義することができる。
ログインしてない時のアクセス制限。
制限をしていないとログインしていない時でもURLを入力したらアクセスできてしまう。ではどうするかというと、current_user(ログイン中のユーザー)がnil(いない)ときにログイン画面にリダイレクトする、というふうにすればよい。この処理は他のコントローラーやアクションでも使いたいのでapplicationコントローラとbefore_actionで共通化しておく
applicationコントローラーに「authenticate_user」メソッドを定義し(ちなみにauthenticateとは認証という意味。つまりユーザーを認証するということになる)
先ほどの処理は全アクションに適用したいわけではないので、選択的にbefore_actionの処理をしていく。図のように「only」を使うことで任意のメソッドのみで処理するができる。各コントローラは、applicationコントローラを継承しているので、継承元のメソッドを使うことができる。
@current_userがauthenticate_userメソッドの中でも使用されているが、@変数で定義した変数は同じクラスの異なるメソッド間で共通して使用することが可能。
ログインユーザーがアクセスできないページ
ログイン中のユーザーには新規登録などのページは必要ないので、アクセスできないようにする必要がある。applicationコントローラ内に「ログインユーザーを禁止する」という意味の、forbid_login_userメソッドを作成しましょう。forbidとは禁止するという意味である。このメソッドでは、ログインユーザーが存在する場合、投稿一覧ページにリダイレクトするようにしている("/posts/index")。メソッドの実行にはbefore_actionを用い、onlyで適用したいアクションを指定しよう。
ユーザーの編集を制限する。
ログインしているユーザーのみ、編集できるようにしたい。また編集リンクが表示されないようにしたい。
ログインしていないユーザーには編集のページを表示しないようにしたいので、ページを表示しないようにしたいので、@user.idと@current_user.idが等しい時のみ編集ページが表示、リンクされるようにする。
上のやり方では編集のリンクがなくなったが、編集ページのURLを直接入力すれば編集ページに飛べてしまう。そのため、アクション側でも条件分岐をして制限をかける必要がある。そのためuserコントローラのeditアクションとupdateアクションを制限する必要がある。
手法としては、「正しいユーザーかを確かめる」という意味のensure_correct_userメソッドを用意し(ensureは確保する、保証する、という意味)、ログイン中のidと編集したいユーザーのidが等しいか判断させることができる。下の画像では等しくなかった場合は権限がありませんというフラッシュメッセージと、投稿一覧ページにリダイレクトさせるようにしている。
ログイン中のユーザーのidは@current_user.idに、編集したいユーザーのidはparams[:id]にそれぞれ代入されている。しかし、params[:id]で取得できる値は文字列であるため、数値である@current_user.idと比較してもfalseとなってしまう。そこでto_iメソッド(整数(Integer))を用いると、文字列を数値に変換することができる。to_iメソッドでparams[:id]を数値に変換し、@current_user.idと比較しよう。
投稿とユーザーの紐付け
まずは投稿ページで誰が投稿したかわかるようにしたい。よって、各投稿に「どのユーザーが投稿したか」の情報を持たせるために、postsテーブルに「user_id」というカラムを用意する。
まずはrails g migration add_user_id_to_posts というマイグレーションファイルを作り、そのマイグレーションファイルのchangeクラスに「add_colum」を利用してカラムを追加する。ちなみにidを整数で保存したいのでデータ型をintegerとしている
postsテーブルにuser_idカラムを追加できていることが確認できたら、validatesを設定する。誰が投稿したかというデータはあるべきなので、user_id,{presence: true}とする。
新規投稿をログインユーザーに紐付ける
newメソッドの部分で、user_idの値として「@current_user.id」とする。こうすることで新規投稿時のidに現在ログインしているユーザーのid(つまり投稿者のid)がデータベースに保存される。
投稿にユーザー情報を表示する。
その投稿は誰が投稿したのかがわかるように情報を表示させたい。
ユーザーのデータを取得するにはfind_byを用いて、user.idから探す必要がある。
よって、今回は投稿詳細ページなので、postsコントローラのshowアクション内で、
「@post.user_id」を用いて、そのidに該当するユーザーの情報をデータベースから取得する。
アクション側で定義した変数@userを用いて、ユーザー画像とユーザー名を表示するまた、ユーザー名の部分は、そのユーザーの詳細ページへのリンクになるようにしてみる。つまりデータベースからデータを取得し、その画像と名前を表示するようにHTMLを書く。そしてそれらをクリックすればユーザーの詳細ページに飛べるよう、リンクを作成する。
railsではモデル内にインスタンスメソッドを定義することができる。
左の図のようにPostモデル内で定義したインスタンスメソッドは、右の図のようにpostインスタンスに対して用いることができる。helloのインスタンスはpost.helloで呼び出せている
左の図のように、Postモデル内にその投稿に紐付いたuserインスタンスを戻り値として返すuserメソッドを定義する。これにより、以降でUser.find_by(id: @post.user_id)を使うときは@userのみで代入できるようになる。
投稿一覧にもユーザー情報を表示してみる
ユーザー詳細ページに投稿を表示する
ユーザーとユーザーが投稿してきた内容をidで紐付けする。しかしfine_byでは1件しか取得できない
ある条件に合致する複数のデータを取得するには「where」が必要になる。
まずは 「User」モデルに紐付けされた「postsインスタンス」に whereで定義した値を戻り値として返すと、右のようにposts(投稿者のidが紐付けられたデータ)が呼び出せるようになる。
先ほど作ったUserモデルに定義したpostsメソッドを用いて、ユーザーの詳細ページに投稿一覧が表示されるようにしたい
@user.postsを用いて、各投稿をそれぞれ表示する。whereメソッドで取得した値は配列に入ってい流ので、each文で繰り返し処理をして一つずつ投稿を表示していく。
投稿者だけが編集できるようにする。
以下の@post.user_idと@current_user.idが等しいときに、編集ボタンと削除ボタンのそれぞれのリンクが表示されるようにする。(つまり投稿者とログイン者が同じでなければ編集も削除もできない)
今のままではリンクが削除されただけでURLを直接入力すれば編集可能になってしまう。なので投稿者だけが編集できるようにしたい。
今回は、投稿に紐づくユーザーと現在ログインしているユーザーが異なる
かどうかを比べるために、postsコントローラ内に「ensure_correct_user」
(userが正しいか認証する)というメソッドを用意する。
before_actionを用いることで、このメソッドをedit、update、destroy(編集も更新も削除もできない)のそれぞれのアクションで使用。