LoginSignup
0
0

cancancanのコード読む(load_and_authorize_resouce)

Posted at

プロジェクトで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の挙動が決まると思いますが、これはまた次に書く。
  • メタプロとか全然なれていないので、実はすごく時間がかかったがとても良い機会だった
0
0
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
0
0