ActiveSupport::Callbacksを読む

- 明日の朝までに読まないといけない

## 背景

- とあるコントローラの処理が重い
- 調査の結果、before_actionとかafter_actionとかで指定されているfilterに原因があることが分かった
- そこで、それぞれのCallbackの実行にかかる時間を書き出したい
- そのために、該当するControllerのrun_callbacksメソッドを黒魔術で上書きする

## ActiveSupport::Callbacksについて

- ApplicationControllerやActiveRecordがこれをincludeして、run_callbacksなどのメソッドを使用可能になる。

## 今回読むコード

https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/activesupport/lib/active_support/callbacks.rb#L97-L142

```activesupport/lib/active_support/callbacks.rb
    # Runs the callbacks for the given event.
    #
    # Calls the before and around callbacks in the order they were set, yields
    # the block (if given one), and then runs the after callbacks in reverse
    # order.
    #
    # If the callback chain was halted, returns +false+. Otherwise returns the
    # result of the block, +nil+ if no callbacks have been set, or +true+
    # if callbacks have been set but no block is given.
    #
    #   run_callbacks :save do
    #     save
    #   end
    #
    #--
    #
    # As this method is used in many places, and often wraps large portions of
    # user code, it has an additional design goal of minimizing its impact on
    # the visible call stack. An exception from inside a :before or :after
    # callback can be as noisy as it likes -- but when control has passed
    # smoothly through and into the supplied block, we want as little evidence
    # as possible that we were here.
    def run_callbacks(kind)
      callbacks = __callbacks[kind.to_sym]
      if callbacks.empty?
        yield if block_given?
      else
        env = Filters::Environment.new(self, false, nil)
        next_sequence = callbacks.compile
        invoke_sequence = Proc.new do
          skipped = nil
          while true
            current = next_sequence
            current.invoke_before(env)
            if current.final?
              env.value = !env.halted && (!block_given? || yield)
            elsif current.skip?(env)
              (skipped ||= []) << current
              next_sequence = next_sequence.nested
              next
            else
              next_sequence = next_sequence.nested
              begin
                target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
                target.send(method, *arguments, &block)
              ensure
                next_sequence = current
              end
            end
            current.invoke_after(env)
            skipped.pop.invoke_after(env) while skipped && skipped.first
            break env.value
          end
        end
        # Common case: no 'around' callbacks defined
        if next_sequence.final?
          next_sequence.invoke_before(env)
          env.value = !env.halted && (!block_given? || yield)
          next_sequence.invoke_after(env)
          env.value
        else
          invoke_sequence.call
        end
      end
    end
```

## binding.pryで実験

```
[9] pry(#<Api::V2::AppleInAppPurchaseController>)> callbacks.class
=> ActiveSupport::Callbacks::CallbackChain
[10] pry(#<Api::V2::AppleInAppPurchaseController>)> callbacks.compile.class
=> ActiveSupport::Callbacks::CallbackSequence
```

callbacks

```rb
=> #<ActiveSupport::Callbacks::CallbackChain:0x00007f907ffdd998
 @callbacks=
  #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064416350
   @after=[],
   @before=[],
   @call_template=
    #<ActiveSupport::Callbacks::CallTemplate:0x00007f9064416378
     @arguments=[],
     @method_name=:set_controller_action,
     @override_block=nil,
     @override_target=nil>,
   @nested=
    #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064417ca0
     @after=
      [#<Proc:0x00007f90644175e8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:232>,
       #<Proc:0x00007f9064416c60@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>,
       #<Proc:0x00007f9064416a08@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>],
     @before=
      [#<Proc:0x00007f9064416468@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f90644165f8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064416850@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064416e68@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:175>,
       #<Proc:0x00007f9064417188@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f9064417368@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
       #<Proc:0x00007f90644178e0@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>],
     @call_template=nil,
     @nested=nil,
     @user_conditions=nil>,
   @user_conditions=[]>,
 @chain=
  [#<ActiveSupport::Callbacks::Callback:0x00007f90643c1738
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=:set_controller_action,
    @if=[],
    @key=:set_controller_action,
    @kind=:around,
    @name=:process_action,
    @unless=[]>,
   #<ActiveSupport::Callbacks::Callback:0x00007f90643add28
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=:init_i18n_debugger,
    @if=[],
    @key=:init_i18n_debugger,
    @kind=:before,
    @name=:process_action,
    @unless=[]>,
   #<ActiveSupport::Callbacks::Callback:0x00007f9072cfd938
    @chain_config=
     {:scope=>[:kind],
      :terminator=>
       #<Proc:0x00007f9072ce44d8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:34 (lambda)>,
      :skip_after_callbacks_if_terminated=>true},
    @filter=
     #<Proc:0x00007f9072cfdb90@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/config-2.2.1/lib/config/integrations/rails/railtie.rb:28>,
```

callback_chains

```rb
=> #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064416350
 @after=[],
 @before=[],
 @call_template=
  #<ActiveSupport::Callbacks::CallTemplate:0x00007f9064416378
   @arguments=[],
   @method_name=:set_controller_action,
   @override_block=nil,
   @override_target=nil>,
 @nested=
  #<ActiveSupport::Callbacks::CallbackSequence:0x00007f9064417ca0
   @after=
    [#<Proc:0x00007f90644175e8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:232>,
     #<Proc:0x00007f9064416c60@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>,
     #<Proc:0x00007f9064416a08@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:247>],
   @before=
    [#<Proc:0x00007f9064416468@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f90644165f8@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064416850@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064416e68@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:175>,
     #<Proc:0x00007f9064417188@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f9064417368@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>,
     #<Proc:0x00007f90644178e0@/Users/daiki-kudo/.rbenv/gems/2.6.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:194>],
   @call_template=nil,
   @nested=nil,
   @user_conditions=nil>,
 @user_conditions=[]>
```