ルーティングの一覧
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
HTTPリクエスト | URL | 名前付きルート | アクション名 | 用途 |
GET | /login | login_path | new | 新しいセッションのページ(ログイン) |
POST | /login | login_path | create | 新しいセッションの作成(ログイン) |
DELETE | /logout | logout_path | destroy | セッションの削除(ログアウト) |
ユーザー登録フォームでform_withヘルパーを使い、ユーザーのインスタンス変数@userを引数にとっていました。
すると、railsが勝手に「フォームのactionは/usersというURLへのPOSTである」と解釈をして、createまで実行してくれました。
<%= form_with(model: @user, local: true) do |f| %>
.
.
.
<% end %>
しかし、ログインの場合はモデルを介していないので、railsに「どこに、何を?」を指定しなければいけません。
セッションの場合はリソースのスコープ(ここではセッション)とそれに対応するURLを具体的に指定する必要があります。
以下の場合、[:session]をlogin_pathに送るということになります。ちなみにscopeの値は任意なので、分かりやすければなんでも構わないです。
form_with(url: login_path, scope: :session, local: true)
全体的には以下の通り
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(url: login_path, scope: :session, local: true) 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>
htmlは以下の通り
<form accept-charset="UTF-8" action="/login" method="post">
<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="email" />
<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>
name='◯◯◯'の◯◯◯部分が重要ですね。
f.email_field :email → session[email] となって、
これを取り出すには、params[:session][:email]となります。
(パスワードも同様、params[:session][:password])
log_inメソッド(引数あり)を作成
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
end
ログインを実装
ユーザーが存在しているかまず確認し、存在していれば、それをフォームで送られてきたパスワードをauthenticatedメソッドで実行。うまくいけば、session[:user_id] = user.idが実行。
class SessionsController < ApplicationController
def new
end
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
def destroy
logout
redirect_to root_url
end
end
ユーザーIDが存在しない状態でfindを使うと例外が発生してしまいます。
findのこの動作は、プロフィールページでは適切でした。
IDが無効の場合は例外を発生してくれなければ困るからです。
しかし、「ユーザーがログインしていない」などの状況が考えられる今回のケースでは、session[:user_id]の値はnilになりえます。この状態を修正するために、createメソッド内でメールアドレスの検索に使ったのと同じfind_byメソッドを使うことにします。ただし今度はemailではなく、idで検索します。
User.find_by(id: session[:user_id])
nilガードをしてもよさそうだが、それよりもfind_byにする方が手っ取り早い。
current_user / logged_in? / log_outを実装
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
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id)
@current_user = nil
end
end
なぜ、@current_user = nil が必要なのか?
session.delete[:user_id]で十分そうだけど。
リンクを作成
<%= link_to "Profile", current_user %>
なお、上のコードは省略形で、このように書くこともできます。
<%= link_to "Profile", user_path(current_user) %>
method: :deleteと指定しているにも関わらず、なぜかroutingエラーが発生。
「getのrouteは存在しません」と。それはそうです。分かってるんです。
色々調べたら、application.jsのujs(控えめなJS)が関係しているようでした。
以下のページを参考にさせてもらいました。ありがとうございました。
https://qiita.com/natu_kumo_/items/8ef3343fda6715ed1d1a
https://qiita.com/yoshinyan/items/194d3c4dcc0f246b1ad1
ログインとログアウト機能の実装が完了しました。
8章では、セッションを利用して、ログインできるようになりました。
以下の3点が最も重要だったかと思います。
1、モデルを介していないため、form-withでurlとscopeを使用すること
2、ログアウトのlink_toではmethod: :deleteでdestroyアクションへ飛ばすこと
3、current_userやlogged_in?をSessionsHelperに作成し、表示を切り替える(記事では割愛しました。)