【Rails】Sessionの実態はどこに?/初心者向け

rails

こんにちは。てくてーくです。

今回はRailsのsessionメソッドについて学んでいきます。

Railsチュートリアルをやっているとログイン機能の実装でsessionメソッドが出てきますが、チュートリアルの中ではサラッとした説明しかありませんでしたのでここでより理解を深めるためにまずはどこにsessionの実態があるのかというのを探していきたいと思います。

sessionメソッドは事前に定義されている

sessionメソッドは事前にRailsに定義されているようなのですが、一体どこにどうやって定義されているのでしょうか。

sessionメソッドを使用した部分から継承元をたどっていき探していきます。

まずはチュートリアルで使用されている app/helpers/sessions_helper.rb を見ていきます。

module SessionsHelper
 def log_in(user) 
  session[:user_id] = user.id
 end
end

セッションヘルパーモジュールで使用されていますね。ではこのモジュールはどこで使用されるのでしょうか。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
 protect_from_forgery with: :exception
 include SessionsHelper
end

こちらでMix-inされていますね。

Mix-inについての説明はこちらをご覧ください。モジュールってなに?|tekute-ku

このapplication controllerクラスにはsessionメソッドについては書かれていません。

継承元のActionController::Baseを見ていきましょう。これ以降はrailsの中身になっていきます。

GitHub Railsのソースコード

ActionController::Baseを見に行ってみるとsessionメソッドについては書かれていません。

 .
 .
 module ActionController
 .
 .
 .
 class Base < Metal

しかしBaseクラスはMetalクラスを継承しているようなのでActionController::Metalを見に行ってみます。

ActionController::Metalを見ていると・・・お、ついに発見しました。sessionメソッド??です。

.
.
module ActionController
.
 classMetal < AbstractController::Base
.
   attr_internal :response, :request
   delegate:session,to: "@_request"
.
 end
.
end
delegate:session,to: "@_request"

しかしこれがsessionメソッドの定義なのでしょうか。見たことがない書き方をしており初心者にはとても手に負えなさそうなのでググってみることにしました。

delegateとは

delegate 委任

delegateとはRailsガイドによると

delegateマクロを使ってメソッドを簡単に委譲できます。
とあります。調べていくとdelegateはメソッドを異なるクラス間でマクロ的な使い方ができるみたいです。
マクロ:事前に操作手順などを記録しておいて、その記録をまとめて実行できるようにする機能のこと
attr_internal :response, :request
delegate:session,to: "@_request"
こちらでいうと@_requestのsessionをMetalクラスで使うことができるようになるようです。
delegateの詳しい使い方はこちらの [Rails] delegateってなに。 solidus を参考にしました。
ここで初めてsessionが出てきましたが “@_request” とはなんなのでしょうか?
その前にattr_internal :response, :requestがなにやら関係していそうですのでこちらを調べてみたいと思います。

attr_internalとは

attr_internal :response, :request

attribute・・属性  internal・・内部、内臓

attr_internalはモジュールの拡張機能の一つのようです。

あるクラスで属性を定義すると後にそのクラスのサブクラスが作られるときに名前が衝突するリスクが生じます。RailsのActive Supportではattr_internal_accessorというマクロが定義されていて内部のインスタンス名が衝突しにくいように配慮されています。

上のコードを例に例えてみましょう

classMetal
 attr_internal :response, :request
end

classGold < Metal
  attr_accessor :request
end

このようなコードがある場合、attr_internal があるおかげでrequestの衝突を回避しています。

このとき内部インスタンス変数の名前には冒頭にアンダースコアが追加されます。上の例だと

@_request がインスタンス変数になります。このように内部でrailsがうまいことやってくれているおかげで衝突を回避しています。

また attr_internal は attr_internal_accessor と書くみたいですが同じ意味みたいですので省略されているようです。

つまりこのコードを読み取ると

.
.
module ActionController
.
 classMetal < AbstractController::Base
.
   attr_internal:response,:request
   delegate:session,to: "@_request"
.
 end
.
end
  • インスタンス変数読み取りのためのインスタンスメソッドresponseとrequestを定義している。
  • インスタンス変数はそれぞれ@_response、@_request となる
  • sessionメソッドがインスタンス変数@_requestに使えるようになる

@_requestがインスタンス変数だということは分かりましたが、まだ@_requestの実態がよく分かりません。

仕方ないのでsessions_controllerでraiseをしてみました。

class SessionsController < ApplicationController
 def new
 end

 def create
  raise session.inspect
 #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
end

すると出力された中に@_requestを発見。どこにあるのかというと

ActionDispatch::Request::Sessionみたいです。早速見に行ってみましょう。

どうやら@_requestはActionDispatch::Request::Sessionのオブジェクトだったようです。

ようやく見つけました。sessionメソッドが書かれています。session.rb

まとめ

まとめてみると

Application Controllerにsessionメソッドを書くとその継承元であるActionController::MetalがdeligateしているActionDispatch::Request::Sessionクラスのオブジェクトである@_requestのsessionメソッドたちが使えるようになる、といったところでしょうか。

何気なく書いていたsessionメソッドがこんなに複雑だとは思いませんでした。正直初心者にはかなり難しくて合っているかどうかも分かりませんがお役に立てれば幸いです。

参考:Rails の session を完全に理解したRailsガイド内部属性Github railsRailsガイドdeligateリファレンスマニュアルattr

コメント