こんにちは、Nanayakuです。
今回は、ログイン機能の追加について解説していきます。
目次
ログインの仕組み
HTTPは「ステートレス」と言う特徴があります。
これは、1つ1つの通信が独立しており、前回の通信の情報を記憶することが出来ないと言う事です。
これでは、ログイン状態を保持しているのは不可能です。
実装しようとすると、全てのリクエストでユーザー情報を入力する必要があります。
そこで、考えられたので「Cookie」と「Session」と言う技術です。
Cookieはユーザーのブラウザにテクストデータ(ユーザー固有の情報)を保存し、接続してから切断するまでログイン状態を保持できます。
ただし、ブラウザにテキストデータとして残すので、セキュリティの問題があります。
Sessionは、サーバー側に情報を保存し、暗号化してクライアント側にCookieとして保存する方法です。
Railsでは、このSessionを使用します。
Sessionの実装
Sessionを実装には、SessionのコントローラーとViewを作成します。
コントローラーの作成
「rails g controller Sessions new」のコマンドでnewアクションを持ったSessionsディレクトリを作成します。
ルーターの設定
画像のようにルートを追記します。
get 'login', to: 'sessions#new'
getは所得するだけのメソッドなので、ログインのためのフォームへ転送します。
post 'login', to: 'sessions#create'
postは作成するメソッドなので、フォームで入力した情報を元にSessionを作成する処理の振り分けます。
delete 'logout', to: 'sessions#destroy'
deleteは削除するメソッドなので、Sessionを削除する処理に振り分けます。
まとめるとルーターは、「ログインフォームに移動」「Sessionの作成」「Sessionの削除」の役割のコントローラーに処理を振り分けています。
Viewの作成・編集
コントローラーを作成した時に、一緒に作成された「sessions/new.html.erb」にログインフォームを作成します。
form_for構文は以下のようになります。
<%= form_for :session, url: login_path do |f| %>
<% end %>
user(登録フォーム)の場合は以下のように、URLを@userで書けました。
<%= form_for @user do |f| %>
@userで出来たのは、入力されたデータを保存するUserモデルがあったため、Railsが自動的にcreateメソッドへアクセスしました。
しかし、今回はデータを保存するSessionモデルがないため、オプションでURLを指定しなければなりません。
Topページと登録フォームのログインのリンクを「login_path」にします。
コンローラーの設定
UsersControllerとSessionsControllerの違いと書くコードの解説をします。
Sessionはモデルを持たないため、インスタンス変数の@userに代入する必要がありません。
user = User.find_by(email: params[:session][:email])
これは、フォームから送信されたメールアドレスと一致するユーザーがいるか検索しています。
user.authenticate(params[:session][:password])
has_secure_passwordをUserモデルに追加したことで、そのオブジェクト内でauthenticateメソッドが使えるようになっています。このメソッドは、引数に与えられた文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較します。
つまり、authenticateメソッドはデータベースのパスワードとフォームから送信されたパスワードを比較し、trueかfalseを返します。
if user && user.authenticate(params[:session][:password])
上記の2つの条件を合わせ、「該当のメールアドレスをもつuserが存在し、かつuserのパスワードが正しい」と言う条件になります。
def log_in(user)
session[:user_id] = user.id
end
sessionメソッドは、sessionに暗号化した情報を保存するメソッドです。
log_inメソッドでsession[:user_id]にログイン情報を保存しています。
ナビゲーションの作成
ナビゲーションバーの作成とログイン画面へ遷移するリンクを作成します。
まずは、「app/helpers/application_helper.rb」と「app/controllers/application_controller.rb」に以下のコードを追記します。
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
「||=」は、左辺が未定義または偽なら右辺の値を代入すると言う意味です。
これにより、「データベースの中のid」と「sessionに保存されているid」が一致したならば、@current_userにその情報を代入すると言う意味です。
つまり、current_userは、現在ログインしているuserを返すメソッドです。
- application_helperなどのhelperはViewで共通で使用するコード
- application_controllerはcontrollerで共通で使用するコード
をそれぞれ指定できます。
ログイン状態でであることを表示したい場合、application_helperとapplication_controllerの両方にメソッドを書きます。
「app/views/layouts/application.html.erb」と「app/assets/stylesheets/custom.scss」でナビゲーションバーのViewを作成します。
ログアウト機能の追加
ログアウトは、保存したsession情報を削除することでできます。
Sessionsコントローラーにdestroyアクションとlog_outメソッドを追記します。
画像の赤枠が追記した部分です。
「session.delete(:user_id)」でsessionの中にあるユーザー情報を削除します。
この時、インスタンス変数の@current_userの中には、ログインしているユーザー情報が残ったままなのでnilを代入します。
destroyアクションはユーザー情報を削除し、Topページに転送して「ログアウトしました」とフィードバックを表示する意味です。
Sessionsコントローラーにストロングパラメーターを実装
params.require(:session).permit(:email)
これは、フォームの情報のemailのみを生成することを意味しており、引数(:email)を渡すことで、フォームのemailのみ取得できる意味です。
passwardも同じです。
重複コードの対策
ApplicationControllerとApplicationHelperには同じコードが存在するため、どちらかを削除する必要があります。
エラーが出て動かなくなるわけではないのですが、Railsの設計思想に反しており、このまま開発を続けた場合支障をきたす可能性があります。
helper_methodメソッドは、コントローラで定義されたいくつかのメソッドを明示的に共有して、それらをビューで使用できるようにすることです。これは、コントローラとヘルパー/ビューの両方からアクセスする必要のあるメソッドで使用されます
共通化したいメソッドが2つあるので以下のような書き方になります。
helper_method :current_user, :logged_in?
最後に
今回作ったアプリをgitにあげました。
良かったら見てみて下さい。
https://github.com/sabakan789/test.rails
備忘録がわりに作ったので、間違っている所とかあったら、コメントくれると嬉しいです。