はじめに
Ruby on RailsでのWEBアプリケーション開発を学習しております。
ログイン中のユーザを取得するメソッドをヘルパーメソッドとして定義した際、下記のように記述をしました。
なぜそのように書くのかがわかりにくかったので、整理していきたいと思います。
誤った解釈等ありましたらご指摘いただけると幸いです。よろしくお願いいたします。
問題のコード
module SessionsHelper
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
end
疑問1 そのコードで何をしているのか
@current_user ||= User.find_by(id: session[:user_id])
上記の1行は下記のように書くこともできる。
@current_user || @current_user = User.find_by(id: session[:user_id])
[解釈]
@current_userが真の場合はそのままにし、偽の場合は右辺の値 User.find_by(id: session[:user_id]) を代入する処理をしている。
疑問2 @currenr_userが真である確認がなぜ必要か
理屈としては @current_user || がなくてもアプリケーションは動くはず。
クエリを何度も発行すると遅くなるのでそれを防いでいるのか?
とういうことで、ログを比較してみた。
[元のコードで投稿の詳細画面を訪れた場合]
@current_user ||= User.find_by(id: session[:user_id])
current_userメソッドで1度クエリが発行されている。(下記参照)
Started GET "/tasks/5" for ::1 at 2023-07-06 11:47:03 +0900
Processing by TasksController#show as HTML
Parameters: {"id"=>"5"}
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:3:in `current_user'
Task Load (3.2ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" = $1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
↳ app/controllers/tasks_controller.rb:57:in `show'
Rendering layout layouts/application.html.erb
Rendering tasks/show.html.erb within layouts/application
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
↳ app/views/tasks/show.html.erb:14
Label Load (2.4ms) SELECT "labels".* FROM "labels" INNER JOIN "labellings" ON "labels"."id" = "labellings"."label_id" WHERE "labellings"."task_id" = $1 [["task_id", 5]]
↳ app/views/tasks/show.html.erb:21
Rendered tasks/show.html.erb within layouts/application (Duration: 6.8ms | Allocations: 1587)
[Webpacker] Everything's up-to-date. Nothing to do
Rendered layout layouts/application.html.erb (Duration: 13.8ms | Allocations: 8403)
Completed 200 OK in 25ms (Views: 12.5ms | ActiveRecord: 6.5ms | Allocations: 10280)
[コードを変更し投稿の詳細画面を訪れた場合]
下記に変更
@current_user = User.find_by(id: session[:user_id])
ログが長い。
current_userメソッドで4度クエリが発行されている。(下記参照)
Started GET "/tasks/1" for ::1 at 2023-07-06 11:54:10 +0900
Processing by TasksController#show as HTML
Parameters: {"id"=>"1"}
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:4:in `current_user'
Task Load (2.0ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/tasks_controller.rb:57:in `show'
Rendering layout layouts/application.html.erb
Rendering tasks/show.html.erb within layouts/application
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/views/tasks/show.html.erb:14
Label Load (2.6ms) SELECT "labels".* FROM "labels" INNER JOIN "labellings" ON "labels"."id" = "labellings"."label_id" WHERE "labellings"."task_id" = $1 [["task_id", 1]]
↳ app/views/tasks/show.html.erb:21
Rendered tasks/show.html.erb within layouts/application (Duration: 8.1ms | Allocations: 1600)
[Webpacker] Everything's up-to-date. Nothing to do
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:4:in `current_user'
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:4:in `current_user'
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/helpers/sessions_helper.rb:4:in `current_user'
Rendered layout layouts/application.html.erb (Duration: 24.2ms | Allocations: 9656)
Completed 200 OK in 37ms (Views: 22.2ms | ActiveRecord: 5.7ms | Allocations: 12607)
[解釈]
今回のアプリではSessionsHelperをApplicationControllerでincludeし、
至るところでcurrent_userメソッドを呼び出している。
今回ログを確認した投稿詳細ページについても、
current_userを使って処理を指定していた。
例1)current_userがnilの場合、ログイン画面に遷移させるメソッドで使用
def login_required
redirect_to new_session_path unless current_user
end
例2)ユーザーがログインしていればtrue、その他ならfalseを返すメソッドで使用
def logged_in?
current_user.present?
end
このようなcurrent_userメソッドを使用した処理を行う際に、
毎回クエリを呼び出す結果となってしまうため、
データベースが大きくなればなるほど処理速度に差が出てしまいそう。(遅くなりそう)
疑問3 キャッシュの有効範囲
ここまでで、一度@current_userを定義していれば
その後もキャッシュが保持されて使い回せるため、
下記の書き方が良さそうだと理解した。
@current_user ||= User.find_by(id: session[:user_id])
しかし、@current_userのキャッシュはいつリセットされるのだろう?
別のページに遷移したら消えてしまうのか?という疑問に直面。
[解釈]
有効範囲・期間はそのリクエスト完了まで。
インスタンス変数のスコープはそのインスタンスである。
別のリクエストが送られるとコントローラで新しいインスタンスが作られるため、リセットされる。
参考にさせていただいた記事
【Rails】@current_user ||= User.find_by(id: session[:user_id])という書き方について
rails tutorial のカレントユーザーのキャッシュについて
最後までお読みいただきありがとうございました。