railsチュートリアル 第八章


はじめに

railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

セッション

HTTPはステートレス (Stateless) なプロトコルであり、ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内にはまったくない。

よって、ユーザーログインの必要なwebアプリケーションではセッション (Session)と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に別途設定する必要がある。

セッションコントローラ

まずはログインのフォームをnewアクションで処理するような、コントローラを作成する。

$ rails generate controller Sessions new

createdestroy といったアクションも扱うが、ビューファイルが必要ないため、ここでは newアクション だけを一緒に生成しておく。

routes.rbでは、Usersリソースのように resourcesメソッド を使ってRESTfulなルーティングを自動的にフルセットで利用することはないので、「名前付きルーティング」だけを使う。

routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  #追加
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'

  resources :users
end

このように名前付きルーティングが増えてきた場合は、以下のコマンドを打つことで現状のルーティングを確認することができる。

$ rails routes

ログインフォーム

コントローラとルーティングを定義した次はビューファイルを整えていく。

ログインフォームでは、会員登録をまだしていない人のために、登録のリンクも作っておく。

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

このソースではform_forタグを使用しているが、form_tagも同じく使用可能

セッションフォームとユーザー登録フォームの違いとして、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもない。

したがって、新しいセッションフォームを作成するときには、form_forヘルパーに追加の情報を独自に渡す必要がある。↓

<%= form_for(:session, url: login_path) do |f| %>

以下に、 HTMLフォームを示しておく。

login.form
<form accept-charset="UTF-8" action="/login" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden"
         value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
  <label for="session_email">Email</label>
  <input class="form-control" id="session_email"
         name="session[email]" type="text" />
  <label for="session_password">Password</label>
  <input id="session_password" name="session[password]"
         type="password" />
  <input class="btn btn-primary" name="commit" type="submit"
       value="Log in" />
</form>

コントローラで、それぞれのアクションを定義する。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    render 'new'
  end

  def destroy
  end
end

また、createアクション でユーザーを検証する処理を書く。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
end

セッションでは、ユーザー登録のように Active Recordモデル を使っていないため、自分でエラーメッセージを作る必要がある。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      #追加
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
end

↑では、flash[:danger]としてしまうと、一度表示されたメッセージが消えなくなってしまうため
flash.now[:danger]
を使い、一度だけ表示されるようにする。

ログイン

まず、SessionsHelperコントローラに読みこますことで、セッションに必要なメソッドを使えるようにしておく。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

log_inメソッド

railsで定義済みのsessionメソッドを使って、Sessionsヘルパーにlog_inメソッドを定義する。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end

これによりsessionコントローラcreateアクション を整えることができる。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    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

redirect_to user
はrailsでは自動的に以下のルーティングに変換される。
user_url(user)

現在のユーザー

セッションIDに対応するユーザーネームを取り出す、 current_userメソッド を定義する。

if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
else
  @current_user
end

↑のようにすれば、current_userを定義することができるが、もっとコンパクトに定義することができる。
「||=」を使うことで以下のようになる。

@current_user ||= User.find_by(id: session[:user_id])

「||=」は左辺がnilあれば、右辺を代入するという使い方ができる。

よって、current_userメソッド は以下のように定義することができる。

app/helpers/sessions_helper.rb
# 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

レイアウトリンクの変更

ユーザーがログインしているかしていないかで、レイアウトを変更する必要がある。

下記のように logged_in? のメソッドを使えば、レイアウトを変更する必要がある。

<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
<% end %>

よって、logged_in? を定義する。
current_userがnilでなければログインしている、という風に解釈できるので↓のように定義できる。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  #追加
  def logged_in?
    !current_user.nil?
  end
end

ユーザー登録時にログイン状態

登録が終われば自動的にログインがすんでいる状態になるようにusersコントローラのcreateアクションを追加修正しておく。

app/controllers/users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      #追加
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
end

ログアウト

session.delete(:user_id)

↑で、現在のユーザーをnilにすることができるので、logoutメソッド を以下のように定義する。

app/helpers/sessions_helper.rb
module SessionsHelper
 # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    #セキュリティ上、念の為次の一行も追加する
    @current_user = nil
  end
end

次に、Sessionsコントローラdestroyアクション を定義する。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
.

  def destroy
      log_out
      redirect_to root_url
  end
end

終わりに

第八章では、Sessionコントローラでログイン・ログアウトを実装した。

webアプリケーションには必要な機能のためしっかり理解しておこうと思う。

次↓
https://qiita.com/jonnyjonnyj1397/items/9cb25f506404c7ae5b9e