139
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

【Rails】ログイン機能を実装する

TODOアプリにログイン機能を実装します。

【Rails】バリデーションを実装する
【Rails】パーシャルを利用する

Userモデルを作成

nameemailpassword_digestという属性を持つUserモデルを作成します。passwordではなく、必ずpassword_digestという属性を設定してください。

$ rails g model User name:string email:string password_digest:string
$ rails db:migrate

ハッシュ化したパスワードで認証ができるようにする

パスワードを平文で保存することは危険です。ハッシュ化したパスワードをDBに保存し、ユーザーのログイン時に入力されたパスワードをハッシュ化した値と比較するようにしましょう。

bcryptを追加

ハッシュ化を行うためのgemを追加します。

/Gemfile
source 'https://rubygems.org'

gem 'rails',   '5.1.6'
gem 'bcrypt',  '3.1.12'
.
.
.
$ bundle install

Userモデルにhas_secure_passwordを追加

password_digestという属性を持つモデルにhas_secure_passwordと書き込むことで、以下の3つが可能になります。

  1. セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
  2. 仮想的な属性passwordpassword_confirmationが使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。
  3. authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。
/app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end

ログイン用のダミーデータを作成

password_digestという属性がある場合、ユーザー登録時にはpasswordpassword_confirmationの2つの属性を登録します。

$ rails c
irb(main):001:0> User.create(name: "sampleTarou", email: "tarou@example.com", password: "hogehoge", password_confirmation: "hogehoge")

セッション管理の基盤を作成

セッションを利用してユーザーを判別する仕組みを作ります。
Sessionsコントローラを生成するときに(密かに)自動生成されるセッション用ヘルパーモジュールを利用すると、sessionメソッドが使えるようになり、セッション管理の仕組みを簡単に実装することができます。

具体的には、ログイン時に
session[:user_id] = user.idでブラウザのcookieにハッシュ化したユーザーidを保存し、ページ遷移の度に
@current_user = User.find_by(id: session[:user_id])でidを元にログイン中のユーザーの情報を取得できるようになります。

Sessionsコントローラーを作成

newcreatedestroyアクションを持ったSessionsコントローラーを作成します。
viewが必要なのはログインページを表示するアクションであるnewアクションだけなので、余計なファイルを生成しないためにcreatedestroyアクションは手動で追加します。

$ rails g controller Sessions new
/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
  end

  def destroy
  end
end

ルーティングを設定します。

/config/routes.rb
Rails.application.routes.draw do
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
.
.
end

ログインページを作成

セッションにはSessionモデルというものがなく、@taskのようなインスタンス変数に相当するものもありません。

本来form_for(@task)と書くだけで、「フォームのactionは/taskというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースの名前とそれに対応するURLを具体的に指定する必要があります。

/app/views/sessions.new.html.erb
<h1>ログイン</h1>
<%= form_for(:session, url: login_path) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>
  <%= f.label :password %>
  <%= f.password_field :password %>
  <%= f.submit "Log in" %>
<% end %>

ログイン状態に応じてトップページの表示を切り分け

ログイン前はログイン画面へのリンク、ログイン後はタスク一覧とログアウトボタンが表示されるようにします。(logged_in?メソッドについては後述)

/app/views/tasks/index.html.erb
<h1>TODOアプリ</h1>
<% if logged_in? %>
  <%= render 'tasks/logged_in' %>
<% else %>
  <%= render 'tasks/not_logged_in' %>
<% end %>
/app/views/tasks/_logged_in.html.erb
<h2>タスク一覧</h2>
<table>
    <thead>
      <tr>
        <th>タスク名</th>
      </tr>
    </thead>
    <tbody>
      <% @tasks.each do |task| %>
        <tr>
          <td><%= task.title %></td>
          <td><%= link_to "編集", edit_task_path(task) %></td>
          <td><%= link_to "削除", task_path(task), method: :delete %></td>
        </tr>
      <% end %>
    </tbody>
</table>
<%= link_to "タスク追加", new_task_path %>
<%= link_to "ログアウト", logout_path, method: :delete %>
/app/views/tasks/_not_logged_in.html.erb
<p>タスクの管理ができるアプリです。まずはログインをしてください。</p>
<%= link_to "ログイン", login_path%>

セッション用ヘルパーメソッドを作成

log_inメソッド

ブラウザのcookieに、ハッシュ化したユーザーidを保存するメソッドです。

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

current_userメソッド

cookieに保存されたユーザーidを元に、ユーザーの情報を取得するメソッドです。

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

findではなくfind_byで検索をしているのは、ユーザーidが存在しなかった場合findを使うと例外が返されてしまうためです。find_byを使うとnilが返されます。

current_user?メソッド

マイページなど、ログイン中のユーザーにしか表示させたく無いページのbefore_actionでよく使われます。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
#受け取ったユーザーがログイン中のユーザーと一致すればtrueを返す
def current_user?(user)
  user == current_user
end

logged_in?メソッド

現在のユーザーがログインしているかどうかを判別するメソッドです。ログイン状況に応じて表示する画面を切り替えたりする処理が簡単に実装できるようになります。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
 # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end

log_outメソッド

ブラウザのcookieに保存されているユーザーidを削除するメソッドです。

app/helpers/sessions_helper.rb
module SessionsHelper
.
.
 # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

ログイン、ログアウト処理を実装

Sessionヘルパーモジュールを読み込む

作成したヘルパーメソッドを全てのページで使えるようにするために、Applicationコントローラから読み込みを行います。(heplerメソッドはデフォルトで全てのviewファイルから呼び出すことができますが、controllerから呼び出す場合には以下のように明示的に読み込む必要があります。)

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

createアクションを作成

ログインページから送信された情報を受け取り、ログイン処理を行うアクションです。

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])
      log_in user
      redirect_to root_url
    else
      render 'new'
    end
  end
.
.

destroyアクションを作成

cookieに保存されたユーザーidを削除し、ログアウトを行うアクションです。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

タスクをいじる前にログイン状況をチェック

タスクの表示、作成、編集、削除の前にログイン状況をチェックするように設定します。

logged_in_userアクションを作成

ログインしていないユーザーをログインページにリダイレクトするアクションを作成します。application_controllerに追加することで、全てのコントローラーで利用できるようになります。

/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
.
.
  private
   # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        redirect_to login_url
      end
    end
end

before_actionを設定

コントローラーの冒頭にbefore_action :<privateアクション名>, only:[:アクション名]と書き込むことで、アクションが実行される前に、指定したprivateアクションを実行することができます。

/app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  before_action :logged_in_user, only:[:edit, :update, :destroy]
.
.
.

終わりに

予想以上に分量が多くなりました。今回実装した内容だと、ブラウザを閉じる度にセッションが切れてログアウトします。ブラウザを閉じてもセッションを維持する方法もあるので、またいつか他の記事でまとめたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
139
Help us understand the problem. What are the problem?