TODOアプリにログイン機能を実装します。
・【Rails】バリデーションを実装する
・【Rails】パーシャルを利用する
##Userモデルを作成
name
、email
、password_digest
という属性を持つUserモデルを作成します。password
ではなく、必ずpassword_digest
という属性を設定してください。
$ rails g model User name:string email:string password_digest:string
$ rails db:migrate
##ハッシュ化したパスワードで認証ができるようにする
パスワードを平文で保存することは危険です。ハッシュ化したパスワードをDBに保存し、ユーザーのログイン時に入力されたパスワードをハッシュ化した値と比較するようにしましょう。
###bcrypt
を追加
ハッシュ化を行うためのgemを追加します。
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つが可能になります。
- セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
- 仮想的な属性
password
とpassword_confirmation
が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。 -
authenticateメソッド
が使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。
class User < ApplicationRecord
has_secure_password
end
###ログイン用のダミーデータを作成
password_digest
という属性がある場合、ユーザー登録時にはpassword
、password_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コントローラーを作成
new
、create
、destroy
アクションを持ったSessionsコントローラーを作成します。
viewが必要なのはログインページを表示するアクションであるnew
アクションだけなので、余計なファイルを生成しないためにcreate
、destroy
アクションは手動で追加します。
$ rails g controller Sessions new
class SessionsController < ApplicationController
def new
end
def create
end
def destroy
end
end
ルーティングを設定します。
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を具体的に指定する必要があります。
<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?
メソッドについては後述)
<h1>TODOアプリ</h1>
<% if logged_in? %>
<%= render 'tasks/logged_in' %>
<% else %>
<%= render 'tasks/not_logged_in' %>
<% end %>
<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 %>
<p>タスクの管理ができるアプリです。まずはログインをしてください。</p>
<%= link_to "ログイン", login_path%>
##セッション用ヘルパーメソッドを作成
###log_in
メソッド
ブラウザのcookieに、ハッシュ化したユーザーidを保存するメソッドです。
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
end
###current_user
メソッド
cookieに保存されたユーザーidを元に、ユーザーの情報を取得するメソッドです。
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
でよく使われます。
module SessionsHelper
.
.
#受け取ったユーザーがログイン中のユーザーと一致すればtrueを返す
def current_user?(user)
user == current_user
end
###logged_in?
メソッド
現在のユーザーがログインしているかどうかを判別するメソッドです。ログイン状況に応じて表示する画面を切り替えたりする処理が簡単に実装できるようになります。
module SessionsHelper
.
.
# ユーザーがログインしていればtrue、その他ならfalseを返す
def logged_in?
!current_user.nil?
end
end
###log_out
メソッド
ブラウザのcookieに保存されているユーザーidを削除するメソッドです。
module SessionsHelper
.
.
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id)
@current_user = nil
end
end
##ログイン、ログアウト処理を実装
###Sessionヘルパーモジュールを読み込む
作成したヘルパーメソッドを全てのページで使えるようにするために、Applicationコントローラ
から読み込みを行います。(heplerメソッドはデフォルトで全てのviewファイルから呼び出すことができますが、controllerから呼び出す場合には以下のように明示的に読み込む必要があります。)
class ApplicationController < ActionController::Base
include SessionsHelper
end
###create
アクションを作成
ログインページから送信された情報を受け取り、ログイン処理を行うアクションです。
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を削除し、ログアウトを行うアクションです。
class SessionsController < ApplicationController
.
.
def destroy
log_out if logged_in?
redirect_to root_url
end
end
##タスクをいじる前にログイン状況をチェック
タスクの表示、作成、編集、削除の前にログイン状況をチェックするように設定します。
###logged_in_user
アクションを作成
ログインしていないユーザーをログインページにリダイレクトするアクションを作成します。application_controller
に追加することで、全てのコントローラーで利用できるようになります。
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アクションを実行することができます。
class TasksController < ApplicationController
before_action :logged_in_user, only:[:edit, :update, :destroy]
.
.
.
##終わりに
予想以上に分量が多くなりました。今回実装した内容だと、ブラウザを閉じる度にセッションが切れてログアウトします。ブラウザを閉じてもセッションを維持する方法もあるので、またいつか他の記事でまとめたいと思います。