2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

@current_user ||= User.find_by(id: session[:user_id])って何してるの?

Posted at

はじめに

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 のカレントユーザーのキャッシュについて


最後までお読みいただきありがとうございました。
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?