プロジェクトでcancancanを使っていますが、たまに挙動がわからないことや、期待通りに動いてくれないことがありました。
そもそも、gemの内部的な動きを知らなかったということもあり、この機会に読んでみました。
- 今回は、load_and_authorize_resourceの挙動のみ確認する
- rubymineでdebugモードで起動して、posts#indexにリクエストとばしてひたすらデバッグしていきます。
- 重要なコード以外はむちゃ省略してます。
https://github.com/CanCanCommunity/cancancan
引数もどんな感じで処理されるのか知りたかったので、下記のように設定しました。
class PostsController < ApplicationController
load_and_authorize_resource :account, only: :index
def index
end
end
load_and_authorize_resource自体の定義は2つあるんですが、最初に呼ばれるのは下記でした。
module CanCan
module ControllerAdditions
module ClassMethods
def load_and_authorize_resource(*args)
cancan_resource_class.add_before_action(self, :load_and_authorize_resource, *args)
end
end
end
end
def cancan_resource_class
ControllerResource # Cancan::ControllerResourceクラスのこと
end
module CanCan
class ControllerResource
include ControllerResourceLoader
def self.add_before_action(controller_class, method, *args)
# controller_class => PostsController
# method => :load_and_authorize_resource
# args => [:account, {:only=>:index}]
options = args.extract_options! # => {only: :index}
resource_name = args.first # => :account
before_action_method = before_callback_name(options) # => :before_action
controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller|
controller.class.cancan_resource_class
.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
# ブロック引数のcontrollerはPostsControllerのインスタンス
# 下記がPostsControllerに動的にはえてくる
# before_action only: :index do
# CanCan::ControllerResource.new(controller, :account, {}).load_and_authorize_resource
# end
end
end
end
あんまりみなれない書き方なんですが、before_actionにブロック渡すときはこういう書き方になるらしいです(?)
module CanCan
class ControllerResource
def initialize(controller, *args)
# controller => # <PostsController:0x0000000000a438>
# args => [:account, {}]
@controller = controller
@params = controller.params # <ActionController::Parameters {"controller"=>"posts", "action"=>"index"} permitted: false>
@options = args.extract_options! # {}
@name = args.first # => :args
end
def load_and_authorize_resource
load_resource
authorize_resource
end
end
end
Tips
HogeControllerがロードされるとputs 1が評価される
次にbefore_action内のputs 2が評価される。
本番環境とかだと、サーバー起動時にファイルを読み込むのでそのときputs 1が評価されるが
dev環境だと、遅延評価らしく、実際にindexとかにリクエストとばしたタイミングでputs 1が評価されるので挙動違う
つまり本来であれば、サーバー起動時とかにload_and_authorize_resourceが評価されて、before_actionが動的に生えてくる。
あとはリクエスト毎にbefore_actionが呼ばれる。
class HogeController
puts 1
before_action do
puts 2
end
def index
end
end
まとめ
- load_and_authorize_resourceのデバッグを行った。
- 結果的にbefore_action do ~ endブロックをロード時に生成している
- do ~ endの中身はload_resourceとauthorize_resource
- CanCan::ControllerResourceがinitializeされるときの引数でload_resourceとauthorize_resourceの挙動が決まると思いますが、これはまた次に書く。
- メタプロとか全然なれていないので、実はすごく時間がかかったがとても良い機会だった