チュートリアルをやってみる。サインイン、サインアウト編
この記事は以下のチュートリアルをやってみた記録である。
第8章 サインイン、サインアウト | Rails チュートリアル
サインイン、サインアウトをするにはセッションの管理が必要となる。
- 忘却モデル ブラウザをとじることでセッション終了
- 継続モデル 「パスワードを保存する」チェックするとセッション継続
- 永続モデル ユーザが明示的にサインアウトするまでセッションが継続される
セッションにクッキーを利用する。
sessionコントローラを作る。
HTTPメソッドとRESTアクションの関係から、sessionコントローラのnewアクションがサインイン画面、createアクションがサインイン処理、destroyアクションがログアウトとする。
rails generate controller Sessions --no-test-framework
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController def new end def create end def destroy end end
画面
app/views/sessions/new.html.erb
<% provide(:title, "Sign in") %> <h1>Sign in</h1> <div class="row"> <div class="span6 offset3"> <%= form_for(:session, url: sessions_path) do |f| %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.submit "Sign in", class: "btn btn-large btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>
form_forでは:sessionから情報を取り出して各フォームと関連づける。
ルーティング
config/routes.rb
SampleApp::Application.routes.draw do resources :users resources :sessions, only: [:new, :create, :destroy] root 'static_pages#home' match '/signup', to: 'users#new', via: 'get' match '/signin', to: 'sessions#new', via: 'get' match '/signout', to: 'sessions#destroy', via: 'delete' ....
リソースも定義されている。
サインイン送信
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController ... def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーをサインインさせ、ユーザーページ (show) にリダイレクトする。 else # エラーメッセージを表示し、サインインフォームを再描画する。 end end ... end
つまり、paramsに入っている:sessionキーの値を取り出し、そこのemailに入っているものを小文字化したもので
ユーザを探し、もし、あってそれがパスワード認証を通過させるならOK。
失敗時のフラッシュメッセージを表示する。
class SessionsController < ApplicationController .... def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーをサインインさせ、ユーザーページ (show) にリダイレクトする。 else flash.now[:error] = 'Invalid email/password combination' render 'new' end end ..... end
成功時の処理
ユーザのサインイン処理を入れる
class SessionsController < ApplicationController .... def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) sign_in user redirect_to user else flash.now[:error] = 'Invalid email/password combination' render 'new' end end ..... end
サインイン状態を永続化させて持っておき、サインアウトしたら解除される仕組みをつくる。
モジュールをつくり、コントローラとビューで使う。そのようなモジュールとしてSessionHelperがすでにできている。
これはビュー側にはインクルードされている。よって、コントローラ側にもインクルードする。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper end
Railsセッションを用いて、記憶トークンを保持する。
session[:remember_token] = user.id
ユーザの取り出しは
User.find(session[:remember_token)
記憶トークンはユーザと結びつけておく必要があるため、usersテーブルにremember_tokenカラムを増やす。
マイグレーション生成
rails generate migration add_remember_token_to_users
db/migrate/20150915085034_add_remember_token_to_users.rb
class AddRememberTokenToUsers < ActiveRecord::Migration def change add_column :users, :remember_token, :string add_index :users, :remember_token end end
マイグレート
bundle exec rake db:migrate bundle exec rake db:test:prepare
記憶トークンとしてurlsafe_base64を使い、暗号化したものをdbに記録する。(トークンを盗まれたときのため)
記憶トークンサインインするたびに更新しておく。(セッションハイジャックのため)
before_createコールバックを利用して、ユーザが新規生成されるときから記憶トークンを設定するようにする。
app/models/user.rb
class User < ActiveRecord::Base before_save { self.email = email.downcase } before_create :create_remember_token .... def User.new_remember_token SecureRandom.urlsafe_base64 end def User.encrypt(token) Digest::SHA1.hexdigest(token.to_s) end private def create_remember_token self.remember_token = User.encrypt(User.new_remember_token) end end
サインイン
app/helpers/sessions_helper.rb
module SessionsHelper def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token, User.encrypt(remember_token)) self.current_user = user end def current_user=(user) @current_user = user end def current_user remember_token = User.encrypt(cookies[:remember_token]) @current_user ||= User.find_by(remember_token: remember_token) end def signed_in? !current_user.nil? end end
permanentで、トークンに期限をつけている。
= は空なら実行されfind_byが行われる。 |
サインインした際の変化をヘッダーにつける。
signed_in?でドロップアウトメニューを出している。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse"> <div class="navbar-inner"> <div class="container"> <%= link_to "sample app", root_path, id: "logo" %> <nav> <ul class="nav pull-right"> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Help", help_path %></li> <% if signed_in? %> <li><%= link_to "Users", '#' %></li> <li id="fat-menu" class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", '#' %></li> <li class="divider"></li> <li> <%= link_to "Sign out", signout_path, method: "delete" %> </li> </ul> </li> <% else %> <li><%= link_to "Sign in", signin_path %></li> <% end %> </ul> </nav> </div> </div> </header>
ドロップアウトメニューはBootstrapのJavaScriptを使うので
app/assets/javascripts/application.js
//= require jquery //= require jquery_ujs //= require bootstrap //= require turbolinks //= require_tree .
ユーザ登録と同時にサインイン
現在は登録したあと自分でログインしないといけないので。
app/controllers/users_controller.rb
class UsersController < ApplicationController ... def create @user = User.new(user_params) if @user.save sign_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end ... end
のように、ユーザが作られたときにそのままsign_inしてしまうようにする。
サインアウト
ルートへリダイレクト、サインアウトの処理の中身はヘルパーに書く。
app/controllers/sessions_controller.rb
def destroy sign_out redirect_to root_url end
app/helpers/sessions_helper.rb
module SessionsHelper ... def sign_out self.current_user = nil cookies.delete(:remember_token) end end
カレントユーザをクリアして、クッキーを消去。
ローカルサーバで確認できた。
rails server