これまでのあらすじ
『人はなぜ自分だけのWebサービスを作りたいと思うのだろうか?そんなことを考えていたら "Railsチュートリアル" に手を出していた。こんなつもりではなかった。ただ興味本位で手を出しただけだ。』、冷たい取調室の中でそんなことを訴えるのであった。
ちゃんとあらすじを書かないのは過去の内容を知りたければ前回記事を読んでっていう願いとこんなバカなことやっている人間でもRailsチュートリアル進めることできるよっていう想いからです [要出典]。はい、前回はログイン機構を作りました。今回はそのアドバンスな内容やっていきます。
みんな大好きハッテン回
そうしたわけで前回作ったログイン機能の拡張を今回やっていきます。ボーナス回ですね(?)。機能的には「このコンピュータにログイン情報を記録する」ってやつの実装です。
まずは何はともあれremember_digest
を作るためにDB拡張していきます。
$ rails generate migration add_remember_digest_to_users remember_digest:string $ rails db:migrate
そうしたらモデルにいろいろメソッドを書き加えていきます。
# app/models/user.rb class User < ApplicationRecord attr_accessor :remember_token ### いろいろ略 ### # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end enc
コメントついているから特に話すことがない。ノリ的にはremember_digest
っていう長期間保持のために一時的(?)に扱うデータを作ったり破壊したりしてる感じ。
そんでモデルでの定義ができたら、コントローラ(とヘルパー)でやりたいことを記述していきます。
# app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new end def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) # ユーザーログイン後にユーザー情報のページにリダイレクトする log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_to @user else # エラーメッセージを作成する flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out if logged_in? redirect_to root_url end end
# app/helpers/sessions_helper.rb module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end # 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) #raise user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end # 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end end
個人的な話なんですけど似たようなメソッド名が並ぶとコメントつけても流れを追わないと分かんなくなるんですけど、練度を上げると分かるようになるんですかね?それともアレですかね、分かりやすい命名に時間とるぐらいならコントローラぶくぶく太らせる僕の思考がひどすぎるからよくわかんないって結果になっているだけですかね…?まあいいや。これで情報を保持してくれるとのことです。
ガワの方はチェックボックスつくってremember_me
投げるようにしてやればOKっぽいね。
そんでテストはこんな感じ。
# test/helpers/sessions_helper_test.rb require 'test_helper' class SessionsHelperTest < ActionView::TestCase def setup @user = users(:michael) remember(@user) end test "セッションがnilのときはcurrent_userはuserを返す" do assert_equal @user, current_user assert is_logged_in? end test "セッションが正しくないときcurrent_userはnilを返す" do @user.update_attribute(:remember_digest, User.digest(User.new_token)) assert_nil current_user end end
# test/integration/users_login_test.rb require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end ### いろいろ略 ### test "ログイン情報記憶してログイン" do log_in_as(@user, remember_me: '1') assert_equal cookies['remember_token'], assigns(:user).remember_token end test "ログイン情報記憶せずにログイン" do # クッキーを保存してログイン log_in_as(@user, remember_me: '1') delete logout_path # クッキーを削除してログイン log_in_as(@user, remember_me: '0') assert_empty cookies['remember_token'] end end
# test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase # 初期値 def setup @user = User.new(name: "Example", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end ### いろいろ略 ### test "ダイジェストが存在しない場合のauthenticated?" do assert_not @user.authenticated?('') end end
ここでは書かなかったけどassert_***
は必須パラメータのあとにパラメータをつけると文字列メッセージになるらしい。ますますテストの日本語化が捗りますね。
第九章の感想
内容としてはそんな感じで、クッキー☆メソッドを使うことでページの状態を長期保持できるとのことです。いやーこれでハッテン的な機能が追加できるんですね感動ものですよ。大枠としてはそんな感じです。今回学びとして細かいところにちょろちょろあったのですが、なんかいろいろ細かすぎてここに書けない。すまぬ。まあ、今後学びを使うような機会があったらなんかネタ記事にして投稿します。
次回はユーザ情報の更新などをやっていくとのことです。2章で簡単に作った内容を10章で実際に作ってみて確認してみましょうって感じなのかな?
つづく……。