Rails4はどのようにコントローラのインスタンス変数をビューに渡しているのか

※ RubyやRailsの素人なので，用語や表現など間違っているところがあったら，指摘してもらえば幸いです！

## はじめに

コントローラで生成されたインスタンス変数はビュー側でも使えることは，ある意味当たり前かも知れないけど，Railsの中ではどういう処理が行われているのか気になった．

ビュー側でコントローラ側で生成されたすべてのインスタンス変数にアクセスできることから，

- コントローラのアクションメソッドの実行後
- レンダラーがビューをレンダリングする前

のタイミングで，インスタンス変数の情報を渡すような処理をしているのではないかと推測しつつ，Railsのコードを読んでみた．

### 環境
- Ruby 2.2.3
- Rails 4.1.13
- RubyMine 8.0.3


## 1. コントローラのアクションメソッドはどこから呼び出されているのか 

### コントローラのアクションメソッドへのエントリポイント

結論からいうと，`ActionController::ImplicitRender#send_action`で，`super`により呼び出されていた．

```ruby:gems/actionpack/lib/action_controller/metal/implicit_render.rb
module ActionController
  module ImplicitRender
    def send_action(method, *args)
      ret = super
      default_render unless performed?
      ret
    end

    def default_render(*args)
      render(*args)
    end
  end
end
```

ところが，なぜ`ActionController::ImplicitRender#send_action`のスーパーメソッドがアクションメソッドになっているんだろう．

### ActionController::ImplicitRender#send_actionのスーパメソッドがアクションメソッドになっている理由

まずは，継承関係を調べてみよう．

- `ActionController::ImplicitRender`は，`ActionController::Base`にmix-inされている．

```ruby:gems/actionpack/lib/action_controller/base.rb
module ActionController
  class Base < Metal
    MODULES = [
      ...
      ImplicitRender,
      ...
    ]

    MODULES.each do |mod|
      include mod
    end
  end
end
```

- `ActionController::Base`は， `ActionController::Metal`を継承している．
- `ActionController::Metal`は，`AbstractController::Base`を継承している．

```ruby:gems/actionpack/lib/action_controller/metal.rb
module ActionController
  class Metal < AbstractController::Base
  end
end
```

- `AbstractController::Base`の中で，手がかりを見つけた．

```ruby:gems/actionpack/lib/abstract_controller/base.rb
module AbstractController
  class Base
    class << self
      private
      
        def process_action(method_name, *args)
          send_action(method_name, *args)
        end

        alias send_action send
	end      
```

`ActionController::ImplicitRender#send_action`のスーパメソッドは，`AbstractController::Base#send_action`で，その正体は他ではなく，`send`であった．

つまり，`ActionController::ImplicitRender#send_action`の中の`ret = super`の実体は`ret = send(method, *args)`で，コントローラのアクションメソッドが呼び出されていたということかな．

## 2. レンダラーはいつビューをレンダリングするのか

コントローラのアクションメソッドの中で，以下のように`render`メソッドを明示的に呼び出している場合，呼び出していない場合がある．
      
   
```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
  def fuga
    # 明示的に呼び出していない場合
  end

  def hoge
    # 明示的に呼び出している場合
    render :fuga
  end  
end
```

- `render`メソッドを明示的に呼び出していない場合    
 - コントローラのアクションメソッドの処理が終わった時，`response_body`が生成されてないため，`performed?`は`false` [^1]
 - `ActionController::ImplicitRender#send_action`から`ActionController::ImplicitRender#default_render`が呼び出される．
 - `ActionController::ImplicitRender#default_render`から`ActionController::Instrumentation#render`が呼び出される
   
- `render`メソッドを明示的に呼び出している場合
 - コントローラのアクションメソッドの中で，`ActionController::Instrumentation#render`が呼び出される
 - コントローラのアクションメソッドの処理が終わった時，`response_body`が生成されてないため，`performed?`は`true`    
 - `ActionController::ImplicitRender#send_action`から`ActionController::ImplicitRender#default_render`が呼び出されない．

ちなみに，`ActionController::ImplicitRender#send_action`の中の`performed?`メソッドは，`ActionController::Metal#performed?`で，レスポンスが生成されているのか否かを判断する．

```ruby:gems/actionpack/lib/action_controller/metal.rb
module ActionController
  class Metal
    def performed?
      response_body || (response && response.committed?)
    end
  end
end
```

さて，どの場合からも共通している`ActionController::Instrumentation#render`から探ってみよう．

### ActionController::Instrumentation#render

スーパーメソッド`ActionController::Rendering#render`が呼び出される．

```ruby:gems/actionpack/lib/action_controller/metal/instrumentation.rb
module ActionController
  module Instrumentation
    def render(*args)
      render_output = nil
      self.view_runtime = cleanup_view_runtime do
        Benchmark.ms { render_output = super }
      end
      render_output
    end
  end
end
```

### ActionController::Rendering#render

- 既に`response_body`が生成されていたら，`DoubleRenderError`を発生させる．
- そうでない場合は，スーパーメソッド`AbstractController::Rendering#render`が呼び出される．

```ruby:gems/actionpack/lib/action_controller/metal/rendering.rb
module ActionController
  module Rendering
    def render(*args) #:nodoc:
      raise ::AbstractController::DoubleRenderError if self.response_body
      super
    end
  end
end
```

### AbstractController::Rendering#render

`response_body`を生成するところは，`ActionController::Renderers#render_to_body`であった．

```ruby:gems/actionpack/lib/abstract_controller/rendering.rb
module AbstractController
  module Rendering
    def render(*args, &block)
      options = _normalize_render(*args, &block)
      self.response_body = render_to_body(options)
      _process_format(rendered_format, options) if rendered_format
      self.response_body
    end
  end
end
```

### ActionController::Renderers#render_to_body

- コンテンツタイプが`json`, `js`, `xml`の場合は，`_handle_render_options`により処理されるらしい．
- そうでない場合は，スーパーメソッド`ActionController::Rendering#render_to_body`が呼び出される．

```ruby:gems/actionpack/lib/action_controller/metal/renderers.rb
module ActionController
  module Renderers
    def render_to_body(options)
      _handle_render_options(options) || super
    end
    
    def _handle_render_options(options)
      _renderers.each do |name|
        if options.key?(name)
          _process_options(options)
          return send("_render_option_#{name}", options.delete(name), options)
        end
      end
      nil
    end    
  end
end
```

### ActionController::Rendering#render_to_body

スーパーメソッド`ActionView::Rendering#render_to_body`が呼び出される．

```ruby:gems/actionpack/lib/action_controller/metal/rendering.rb
module ActionController
  module Rendering
    def render_to_body(options = {})
      super || _render_in_priorities(options) || ' '
    end
  end
end
```

### ActionView::Rendering#render_to_body

`ActionView::Rendering#_render_template`が呼び出される．

```ruby:gems/actionview/lib/action_view/rendering.rb
module ActionView
  module Rendering
    def render_to_body(options = {})
      _process_options(options)
      _render_template(options)
    end  
  end
end
```

### ActionView::Rendering#_render_template

1. `view_renderer`は，`ActionView::Renderer`のインスタンスを一回だけ生成して返す．
2. `view_context`は，`ActionView::Base`のインスタンスを毎回生成して返す．

つまり，ここが`ActionView::Renderer#render`へのエントリーポイントのようだ．

```ruby:gems/actionview/lib/action_view/rendering.rb
module ActionView
  module Rendering
    module ClassMethods
      def view_context_class
        @view_context_class ||= begin
          routes = respond_to?(:_routes) && _routes
          helpers = respond_to?(:_helpers) && _helpers

          Class.new(ActionView::Base) do
            if routes
              include routes.url_helpers
              include routes.mounted_helpers
            end

            if helpers
              include helpers
            end
          end
        end
      end
    end

    attr_internal_writer :view_context_class

    def view_context_class
      @_view_context_class ||= self.class.view_context_class
    end

    def view_context
      view_context_class.new(view_renderer, view_assigns, self)
    end

    def view_renderer
      @_view_renderer ||= ActionView::Renderer.new(lookup_context)
    end

    private
      def _render_template(options) #:nodoc:
        variant = options[:variant]

        lookup_context.rendered_format = nil if options[:formats]
        lookup_context.variants = variant if variant

        view_renderer.render(view_context, options)
      end
  end
end
```

## 3. いつインスタンス変数が渡されるのか

`ActionView::Rendering#view_context`で呼び出しているメソッド`view_assigns`は`AbstractController::Rendering#view_assigns`だった．

### AbstractController::Rendering#view_assigns

ここで，コントローラで生成されたすべてのインスタンス変数（保護されている変数は除く）の情報を返すメソッドだった．

```ruby:gems/actionpack/lib/abstract_controller/rendering.rb
module AbstractController
  module Rendering
    DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %w(
      @_action_name @_response_body @_formats @_prefixes @_config
      @_view_context_class @_view_renderer @_lookup_context
      @_routes @_db_runtime
    ).map(&:to_sym)

    def view_assigns
      protected_vars = _protected_ivars
      variables      = instance_variables

      variables.reject! { |s| protected_vars.include? s }
      variables.each_with_object({}) { |name, hash|
        hash[name.slice(1, name.length)] = instance_variable_get(name)
      }
    end
    
    def _protected_ivars # :nodoc:
      DEFAULT_PROTECTED_INSTANCE_VARIABLES
    end    
  end
end
```

ということは，これをパラメータとするコンストラクターを持つ`ActionView::Base`が怪しかったので，見てみた．

### ActionView::Base#initialize

`ActionView::Base#initialize`の`assigns`パラメータは，`ActionView::Base#assign`によりインスタンス変数としてセットされる！

```ruby:gems/actionview/lib/action_view/base.rb
module ActionView
  class Base
    def assign(new_assigns) # :nodoc:
      @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
    end

    def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
      @_config = ActiveSupport::InheritableOptions.new

      if context.is_a?(ActionView::Renderer)
        @view_renderer = context
      else
        lookup_context = context.is_a?(ActionView::LookupContext) ?
          context : ActionView::LookupContext.new(context)
        lookup_context.formats  = formats if formats
        lookup_context.prefixes = controller._prefixes if controller
        @view_renderer = ActionView::Renderer.new(lookup_context)
      end

      assign(assigns)
      assign_controller(controller)
      _prepare_context
    end
  end
end
```

## まとめ
- コントローラのメソッドは``ActionController::ImplicitRender#send_action`で実行される．
- レンダラーは，`ActionView::Renderer#render`にてビューをレンダリングする．
- コントローラで生成されたインスタンス変数の情報は，`AbstractController::Rendering#view_assigns`からゲットできる．
- `ActionView::Base#assign`により，ビューにインスタンス変数の情報をセットできる．

## 脚注
[^1]: 厳密にいうと，`render`メソッドを呼び出していなくても，`redirect_to`メソッドを呼び出した場合も`response_body`は生成されて，`performed?`は`false`になる．