プログラミング備忘録

プログラミングの学習状況をメモしています

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_usernilではないことを指す。

def logged_in?
  !current_user.nil?
end

否定演算子(!)を利用して、nil可動化を確認。ユーザーがログインしていればtrue、そうでなければfalseになる。

logged_in?メソッドは、ヘッダーのパーシャル(部分テンプレート)などで活用する。

ちなみに、Railsチュートリアルではメニューの項目にBootstrapでのドロップダウンを使用しており、使用方法についての解説があった。

dropdownクラスや、dropdown-menuクラスといったCSSを利用している。これらの機能を有効にするためには、jQueryを読み込む必要があった。

deviseのメソッド

今回、モジュールにさまざまなメソッドを作成したが、gem「devise」ではあらかじめメソッドが用意されているおかげで、今回の作業を大幅に省くことができるということが分かった。