49日目
今日の学習
Ruby on Rails
Railsチュートリアル 第8章
昨日から引き続き、ログイン機能の実装を行う。
昨日は、ログイン機能をRESTful
なリソースとして扱うために、コントローラを作成してルーティングを設定した。
ユーザーの検索と認証
https://railstutorial.jp/chapters/basic_login?version=6.0#sec-finding_and_authenticating_a_user
まず最初に作成する処理は、入力が無効な場合。ログイン失敗時にエラーメッセージを出したりする設定を行う。
その次に、ログインが成功した場合の処理を作成する。ここでは、パスワードとメールアドレスの組み合わせが有効かどうかを判定する。
# ログイン(create) def create # 入力された情報をparamsで受け取り、データベースからfind_byで検索 user = User.find_by(email: params[:session][:email].downcase) # userのデータが true かつ、パスワードも一致しているか if user && user.authenticate(params[:session][:password]) # 後述のモジュールを利用しログイン後リダイレクト log_in user redirect_to user else # ログインできなかった場合エラーメッセージを表示 flash.now[:danger] = 'Invalid email/password combination' render 'new' end end
params
で受け取った入力情報は、ネストしたハッシュ(入れ子ハッシュ)((ハッシュの中にハッシュがある構造))
として受け取る。
{ session: { password: "foobar", email: "user@example.com" } }
上のような受け取り方をするため、パスワードやアドレスを参照したい場合は、以下のように取得する。
params[:session][:email] params[:session][:password]
次の行では、少し前に学習した、authenticate
メソッドを利用して、入力されたメールアドレスを持つuserのパスワードが、入力されたものとデータベースのもので一致しているかどうかを調べている。
if user && user.authenticate(params[:session][:password])
&&(論理積)
を利用して、真偽値がどちらもtrue
かどうかを調べている。有効なユーザーであり、かつ正しいパスワードという二つの要素がtrue
で、はじめてログイン条件を満たせる。
else
以降に、ログインできなかった場合の処理を書く。
この場合、flash[:danger]
とするのではなく、flash.now[:danger]
とすること。
前者の場合、render
で強制的にレンダリングしても、リクエストとみなされないためフラッシュメッセージが表示されたままになってしまう。
flashとflash.nowの違いを検証してみた - Qiita
フラッシュのテスト
ログイン失敗時のフラッシュが、ページを遷移したときちゃんと消えるかどうかを確かめるテストを作成した。
test/integration/users_login_test.rb
test "login with invalid information" do # ログインページにアクセスする get login_path # ちゃんとsessions/newが描画されているか確認 assert_template "sessions/new" # 成功しないログイン情報を送る post login_path, params: { session: { email: "", password: "" } } # ログイン失敗してsessions/newが描画されているか確認 assert_template "sessions/new" # フラッシュがあるかどうか確認(true) assert_not flash.empty? # 別のページに移動 get root_path # フラッシュが無いかどうか確認(true) assert flash.empty? end
ログイン
8章では、ブラウザを閉じると自動的に有効期限が切れるcookies
を利用して、一時セッションでユーザーがログインできるようにする(9章ではブラウザを閉じても保持されるセッションを追加する)。
セッションを実装するには、様々なコントローラやビューで、おびただしい数のメソッドを定義する必要があるらしい。
そこで、モジュール機能
を使い、全コントローラの親クラスであるApplicationController
に作成したモジュールを読み込ませ、どのコントローラでもモジュールを使えるようにしたい。
app/controllers/application_controller.rb
に、include SessionsHelper
を記入して準備完了。
app/heplers/sessions_helper.rb
に、以下の内容を記載していく。
log_inメソッド
ログイン手法を使いまわせるようにするために、log_in
メソッドを定義する。
session
メソッドを利用する。これはハッシュのように扱える。
def log_in(user) session[:user_id] = user.id end
このメソッドは、先ほどコントローラのcreate
メソッド内で使用したメソッド。
current_userメソッド
セッションIDに対応するユーザーを定義する。
ユーザーIDが存在しない状態でfind
を使うと例外が発生してしまうので、例外を出さないようにするために以下のような形にする。
def current_user if session[:user_id] User.find_by(id: session[:user_id]) end end
これだと、セッションにユーザーIDが存在しない場合、このコードは自動的にnil
を返す。
さらに、User.find_by
の結果をインスタンス変数
に保存したり、or演算子(||)
を利用して1行ですっきりさせると以下のようになる。
@current_user = @current_user || User.find_by(id: session[:user_id]) # ||= を利用して書き換え @current_user ||= User.find_by(id: session[:user_id])
Ruby本で見覚えのある||=が出てきた。
変数の値がnil
なら変数に代入するが、nil
でなければ代入せずそのままという操作。
||
式を左から右に評価し、演算子の左の値(上で言うと@current_user
)が最初にtrue
になった時点で処理を終了する評価法を短絡評価
と言うようだ。
最終的に、current_user
メソッドは以下のようになる(session[:user_id]
の重複は9章で解消するらしい)。
def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end
レイアウトリンクの変更
ログインの有無でレイアウトを変更する。ログインの有無を、if-else文
を使用して状況に応じて表示するリンクを変更する。
そのために、ログインしているかどうかを判別するメソッドが必要なので、logged_in?
メソッドを定義していく。
ユーザーがログイン中の状態とは、「sessionにユーザーidが存在している」ことを指す。これは、current_user
がnil
ではないことを指す。
def logged_in? !current_user.nil? end
否定演算子(!)
を利用して、nil
可動化を確認。ユーザーがログインしていればtrue
、そうでなければfalse
になる。
logged_in?
メソッドは、ヘッダーのパーシャル(部分テンプレート)などで活用する。
ちなみに、Railsチュートリアルではメニューの項目にBootstrapでのドロップダウンを使用しており、使用方法についての解説があった。
dropdown
クラスや、dropdown-menu
クラスといったCSSを利用している。これらの機能を有効にするためには、jQueryを読み込む必要があった。
deviseのメソッド
今回、モジュールにさまざまなメソッドを作成したが、gem「devise」ではあらかじめメソッドが用意されているおかげで、今回の作業を大幅に省くことができるということが分かった。