Railsはどのようにテンプレートを見つけているか

# これは何？
- Railsのテンプレート探索についてまとめた記事です
- Railsのバージョンは4.2です
- 本記事は以下の内容を翻訳したものに加筆・修正を加えています
  - [Digging Rails: How Rails Finds Your Templates Part 1](http://climber2002.github.io/blog/2015/02/21/how-rails-finds-your-templates-part-1)
  - [" Part 2](http://climber2002.github.io/blog/2015/02/22/digging-rails-how-rails-finds-your-templates-part-2)
  - [" Part 3](http://climber2002.github.io/blog/2015/03/22/digging-rails-how-rails-finds-your-templates-part-3)
  - [" Part 4](http://climber2002.github.io/blog/2015/04/06/digging-rails-how-rails-finds-your-templates-part-4)



# 構成
この後の構成は以下のとおりです。

* はじめに
* 1章 オプションのノーマライズ
* 2章 オプションよりレンダー
* 3章 レンダラーによるレンダー
* 4章 オプションよりテンプレートパスを見つける
* まとめ


# はじめに

Railsアプリケーションでコントローラアクションにリクエストがあった時、Railsがどのようにレンダーするテンプレートを見つけているか疑問に思ったことはありませんか？例えば`ArticlesController`の`index`アクションにリクエストがあった時、通常テンプレート`app/views/articles/index.html.erb`が選ばれ、レンダーされます。最近、Railsのコードを読んでいてますが、今回、皆さんと一緒に`ActionPack`や`ActionView`のソースコードを見ていきたいと思います。

まず最初の章ではrenderがどのように機能しているのかを見ていきます。Railsのバージョンは4.2を想定していることに留意してください。もし違うバージョンを見ている場合多少実装が異なるかもしれません。


# 1章 オプションのノーマライズ

レンダーのエントリーポイントは`AbstractController::Rendering#render`メソッドです。`AbstractController`は`ActionController`や`ActionMailer`において共通で取り込まれているモジュールです。その2つのモジュールは多くの機能性が共通しているため、それら共通の機能性を`AbstractController`として切り出しています。

```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    # Normalize arguments, options and then delegates render_to_body and
    # sticks the result in self.response_body.
    # :api: public
    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
```

コントローラでは、レンダーメソッドを直接呼ぶことができます。例えば、`render 'new'`でテンプレート`new.html.erb`をレンダーすることができます。または、明示的にrenderメソッドを呼ばない場合、デフォルトレンダラーとして`ActionController::ImplicitRender`が呼ばれます。

```rb:rails/actionpack/lib/action_controller/metal/implicit_renderer.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
```

`send_action`メソッドはコントローラアクションがトリガーとなって呼ばれます。まず最初にsuperを呼び、次にもしアクションで何もレンダーされなかった場合に`performed?`メソッドがfalseを返します。そして見ていただけるとわかる通り`default_render`メソッドが呼ばれます。`default_render`が呼ばれると、単に`render`メソッドが呼ばれます。

`AbstractController::Rendering#render`メソッドでは`_normalize_render`メソッドが呼ばれ、次に`render_to_body`メソッドが呼ばれます。`_normalize_render`はオプションHashが返ってきます。この章では`_normalize_render`メソッドがどのように生成されるかを確認していきます。

`_normalize_render`がどのように実装されているかを見ていきましょう。

```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    private

    # Normalize args and options.
    # :api: private
    def _normalize_render(*args, &block)
      options = _normalize_args(*args, &block)
      #TODO: remove defined? when we restore AP <=> AV dependency
      if defined?(request) && request && request.variant.present?
        options[:variant] = request.variant
      end
      _normalize_options(options)
      options
    end

  end

end
```

`_normalize_args`と`_normalize_options`が呼ばれていることが分かります。`_normalize_args`と `_normalize_options`は違った目的があります。


### \_normalize_args

`_normalize_args`はすべての引数をオプションハッシュに変換します。例えば、`render`メソッドを以下のように呼ぶことができます。

```rb
render 'new', status: :ok
```

ここで第一引数である`new`が文字列であり、`_normalize_args`はオプションハッシュにこの第一引数を取り込み、適切なキーをあてるという責務があります。

それでは実装を見ていきましょう。

```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    # Normalize args by converting render "foo" to render :action => "foo" and
    # render "foo/bar" to render :file => "foo/bar".
    # :api: plugin
    def _normalize_args(action=nil, options={})
      if action.is_a? Hash
        action
      else
        options
      end
    end

  end

end
```

通常、このメソッドは全く何もしないということが分かります。もし`action`がハッシュであれば`action`を返し、もしそうでなく例えば`action`が文字列であればオプションハッシュである第二引数を返します。

`ApplicationController`はこのメソッドをオーバーライドするいくつかのモジュールもインクルードすることに注意してください。その点については後ほど見ていきます。


### \_normalize_options

`_normalize_options`メソッドはモジュールが他のオプションを取り込むためにあります。Railsアプリケーションでは、`ApplicationController`は`ActionController::Base`を継承し、`ActionController::Base`は多くのモジュールをインクルードし、各モジュールはこのメソッドをオーバーライドして他のオプションを追加しています。

まずはじめに`AbstractController::Rendering`でどのようにこのメソッドが実装されているのかを確認していきましょう。


```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    # Normalize options.
    # :api: plugin
    def _normalize_options(options)
      options
    end

  end

end
```

通常このメソッドは何もしませんが、他のモジュールでオーバーライドされます。

### Override \_normalize_args

Railsのソースコードは複雑で、その一つの理由として多くのモジュールが他のモジュールのメソッドをオーバーライドすることにあります。例として`ArticlesController`の先祖を見ていきましょう。

```rb
$ rails c
Loading development environment (Rails 4.2.0)
2.0.0-p353 :001 > puts ArticlesController.ancestors
ArticlesController
#<Module:0x007f8f0a971800>
ApplicationController
#<Module:0x007f8f0a8f93c8>
#<Module:0x007f8f05465118>
#<Module:0x007f8f05465140>
ActionController::Base
WebConsole::ControllerHelpers
Turbolinks::XHRHeaders
Turbolinks::Cookies
Turbolinks::XDomainBlocker
Turbolinks::Redirection
ActiveRecord::Railties::ControllerRuntime
ActionDispatch::Routing::RouteSet::MountedHelpers
ActionController::ParamsWrapper
ActionController::Instrumentation
ActionController::Rescue
ActionController::HttpAuthentication::Token::ControllerMethods
ActionController::HttpAuthentication::Digest::ControllerMethods
ActionController::HttpAuthentication::Basic::ControllerMethods
ActionController::DataStreaming
ActionController::Streaming
ActionController::ForceSSL
ActionController::RequestForgeryProtection
ActionController::Flash
ActionController::Cookies
ActionController::StrongParameters
ActiveSupport::Rescuable
ActionController::ImplicitRender
ActionController::MimeResponds
ActionController::Caching
ActionController::Caching::Fragments
ActionController::Caching::ConfigMethods
AbstractController::Callbacks
ActiveSupport::Callbacks
ActionController::EtagWithTemplateDigest
ActionController::ConditionalGet
ActionController::Head
ActionController::Renderers::All
ActionController::Renderers
ActionController::Rendering
ActionView::Layouts
ActionView::Rendering
ActionController::Redirecting
ActionController::RackDelegation
ActiveSupport::Benchmarkable
AbstractController::Logger
ActionController::UrlFor
AbstractController::UrlFor
ActionDispatch::Routing::UrlFor
ActionDispatch::Routing::PolymorphicRoutes
ActionController::ModelNaming
ActionController::HideActions
ActionController::Helpers
AbstractController::Helpers
AbstractController::AssetPaths
AbstractController::Translation
AbstractController::Rendering
ActionView::ViewPaths
ActionController::Metal
AbstractController::Base
ActiveSupport::Configurable
Object
ActiveSupport::Dependencies::Loadable
PP::ObjectMixin
JSON::Ext::Generator::GeneratorMethods::Object
Kernel
BasicObject
```

よくあるコントローラを見てみるとわかる通り、多くの先祖を持っていて、さらにほとんどがモジュールであることが分かります。`AbstractController::Rendering`以降のすべてのモジュールがこのメソッドをオーバーライドする可能性があります。そこでメソッドを実装している先祖を確認するgistを作成しました。[^gem]

[^gem]: gistは元記事執筆者作成です。また、[訳者がgem化していますのでそちらもご確認ください。](https://github.com/ngtk/ancestors_that_implement_instance_method)

```rb
class Module
  def ancestors_that_implement_instance_method(instance_method)
    ancestors.find_all do |ancestor|
      (ancestor.instance_methods(false) + ancestor.private_instance_methods(false)).include?(instance_method)
    end
  end
end
```

上記コードをrailsコンソールで実行すると`ClassName.ancestors_that_implement_instance_method`を呼ぶことでメソッドが実装された先祖を確認することができます。

それでは`_normalize_args`メソッドをオーバーライドしている先祖を見ていきましょう。

```rb
$ rails c
Loading development environment (Rails 4.2.0)
2.0.0-p353 :013 >   ArticlesController.ancestors_that_implement_instance_method(:_normalize_args)
 => [ActionController::Rendering, ActionView::Rendering, AbstractController::Rendering]
```

`ActionView::Rendering`と`ActionController::Rendering`の2つのモジュールがインスタンスメソッドをオーバーライドしています。上から下の順に確認していきましょう。

はじめに`ActionView::Rendering`を見ていきます。

```rb:actionview/lib/action_view/rendering.rb
module ActionView
  module Rendering
    # Normalize args by converting render "foo" to render :action => "foo" and
    # render "foo/bar" to render :template => "foo/bar".
    # :api: private
    def _normalize_args(action=nil, options={})
      options = super(action, options)
      case action
      when NilClass
      when Hash
        options = action
      when String, Symbol
        action = action.to_s
        key = action.include?(?/) ? :template : :action
        options[key] = action
      else
        options[:partial] = action
      end

      options
    end
  end
end
```

第一引数`action`に注目すると、`/`を含む文字列の場合に`action`に対してのキーは`:template`となり、`/`を含まない文字列の場合に`action`に対してのキーは`:action`になります。

そのため、`render 'new'`と呼ぶときオプションは`{ action: ‘new’ }`となり、`render 'articles/new'`と呼ぶときオプションは`{ template: ‘articles/new’ }`となります。

さて次にこのメソッドをオーバーライドする`ActionController::Rendering`を見ていきます。

```rb:actionpack/lib/action_controller/metal/rendering.rb
module ActionController
  module Rendering
    # Normalize arguments by catching blocks and setting them on :update.
    def _normalize_args(action=nil, options={}, &blk) #:nodoc:
      options = super
      options[:update] = blk if block_given?
      options
    end
  end
end
```

このオーバーライドではブロックが渡ってくるとき、そのブロックを`optionss[:update]`にセットしていることがわかります。

### Override \_normalize_options

`_normalize_args`のように`_normalize_options`をオーバーライドするモジュールを調べていきましょう。オーバーライドするモジュールが`[ActionController::Rendering, ActionView::Layouts, ActionView::Rendering, AbstractController::Rendering]`であることがわかります。

はじめに`ActionView::Rendering`を見ていきましょう。

```rb:actionpack/lib/action_controller/metal/rendering.rb
module ActionView
  module Rendering

    # Normalize options.
    # :api: private
    def _normalize_options(options)
      options = super(options)
      if options[:partial] == true
        options[:partial] = action_name
      end

      if (options.keys & [:partial, :file, :template]).empty?
        options[:prefixes] ||= _prefixes
      end

      options[:template] ||= (options[:action] || action_name).to_s
      options
    end
  end
end
```

通常、3つのオプションを追加していることが分かります。

`options[:partial]`がtrueであった場合、`options[:partial]`に`action_name`がセットされます。`action_name`メソッドはトリガーになったアクションの名前が返ります。
例えば`ArticlesController`の`index`アクションでトリガーした場合には、`action_name`は`index`になります。
もし`:partial`や`:file`、`:template`オプションがどれも指定されていない場合、`options[:prefixes]`がセットされます。この`:prefixes`に何がセットされるかは後で確認することにしましょう。次に`options[:template]`がセットされます。これは引数から渡されるかまたは`action_name`が使われます。
それでは`options[:prefixes]`の中身が何か、`_prefixes`メソッドが実装を確認しましょう。

`AbstractController::Rendering`モジュールは`ActionView::ViewPaths`モジュールをインクルードしており、`_prefixes`メソッドはそこで実装されています。

`ActionView::ViewPaths`は後ほどでも詳細を見ていく重要なモジュールです。このモジュールはコントローラでビューパスを管理するためのものです。例えば、通常Railsはビューパス`#{Rails.root}app/views`を追加することによって、アプリケーションはその特定のビューパスでテンプレートを検索するということを知ります。

それでは`_prefixes`メソッドに注目していきましょう。

```rb:rails/actionview/lib/action_view/view_paths.rb
module ActionView

  module ViewPaths

    # The prefixes used in render "foo" shortcuts.
    def _prefixes # :nodoc:
      self.class._prefixes
    end

  end

end
```

`_prefixes`メソッドはクラスメソッドの`_prefixes`を呼んでいるだけです。

```rails/actionview/lib/action_view/view_paths.rb
module ActionView

  module ViewPaths
    module ClassMethods
      def _prefixes # :nodoc:
        @_prefixes ||= begin
          return local_prefixes if superclass.abstract?

          local_prefixes + superclass._prefixes
        end
      end

      private

      # Override this method in your controller if you want to change paths prefixes for finding views.
      # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
      def local_prefixes
        [controller_path]
      end

    end
  end

end
```

Rails 4.2ではこのメソッドが実装されており、非推奨の親クラスのプレフィックスを処理していることに注意してください。上記のコードでは分かりやすくするためにその処理を除外しています。

この`ActionView::ViewPaths._prefixes`は再帰的に呼ばれます。これは親クラスの`_prefixes`で`local_prefixes`を追加しています。`local_prefixes`は唯一の要素`controller_path`を持つ配列を返します。

`controller_path`メソッドはとても単純で、`AbstractController::Base`で実装されています。例えば`ArticlesController`において`controller_path`は`articles`を返し、`Articles::CommentsController`において`controller_path`は`articles/comments`を返します。

つまり、`_prefixes`メソッドは最初に親のプレフィックスを取得して、次に現在の`controller_path`を先頭に追加します。

例えば、アプリケーションに`ArticlesController`がある場合、以下のコードで`_prefixes`を表示できます。


```rb
$ rails c
Loading development environment (Rails 4.2.0)
2.0.0-p353 :001 > ArticlesController.new.send(:_prefixes)
 => ["articles", "application"]
```

プレフィックスとして`articles`と`application`の2つがあることが分かります。`ApplicationController`は`ActionController::Base`を継承しており、`ActionController::Base`は`abstract`です。

なので、`ArticlesController#index`アクションで引数なしでレンダーした場合にオプションは次の通りになります。

:prefixes : array [“articles”, “application”]
:template : string “index”

次に`ActionView::Layouts`がどのように`_normalize_options`メソッドをオーバーライドしているかを見ていきましょう。

```rb:rails/actionview/lib/action_view/layouts.rb
module ActionView
  module Layouts

    def _normalize_options(options) # :nodoc:
      super

      if _include_layout?(options)
        layout = options.delete(:layout) { :default }
        options[:layout] = _layout_for_option(layout)
      end
    end

  end
end
```

`options[:layout]`がセットされていない場合デフォルトの`layout`は`:default`です。そして`_layout_for_option`の戻り値が`options[:layout]`にセットされます。

もし興味があれば`_layout_for_options`の実装を確認しても良いでしょう。このモジュールがレイアウトを探索するとき、最初に`“app/views/layouts/#{class_name.underscore}.rb”`を探索し、見つからない場合に親クラスを探索します。railsアプリケーションが作成されたとき`application.html.erb`が`app/views/layouts`ディレクトリに作成されますが、デフォルトで全てのコントローラの親が`ApplicationController`であるためにこのレイアウトがデフォルトで使用されるのです。

最後に`ActionController::Rendering`がどのように`_normalize_options`をオーバーライドしているかを確認していきましょう。

```rb:rails/actionpack/lib/action_controller/metal/rendering.rb
module ActionController
  module Rendering

    # Normalize both text and status options.
    def _normalize_options(options) #:nodoc:
      _normalize_text(options)

      if options[:html]
        options[:html] = ERB::Util.html_escape(options[:html])
      end

      if options.delete(:nothing)
        options[:body] = nil
      end

      if options[:status]
        options[:status] = Rack::Utils.status_code(options[:status])
      end

      super
    end

  end
end
```

このメソッドは`:html`や`:nothing`、`:status`オプションを処理する簡単なものです。

そして最後に`ArticlesController#index`を引数なしで呼んだ場合のオプションが以下の値になることがわかります。

:prefixes : array [“articles”, “application”]
:template : string “index”
:layout :  proc (呼んだ時に“app/views/layouts/application.html.erb”が返る)

さて、オプションがどのようにノーマライズされるかを見てきました。Railsはどのテンプレートをレンダーするべきかを特定する時にオプションから詳細を抽出します。後の章ではRailsがどのようにテンプレートを特定しているかを調べていきます。




# 2章 オプションよりレンダー

前章ではRailsがテンプレートを探索するにあったって`_normalize_render`メソッドによりオプションハッシュをつくるところまでを紹介してきました。そして`render`メソッドでは`render_to_body`へ作成したオプションハッシュを渡します。

```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    # Normalize arguments, options and then delegates render_to_body and
    # sticks the result in self.response_body.
    # :api: public
    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
```

`render_to_body`はオプションハッシュの値をもとにテンプレートを選択します。`AbstractController::Rendering#render_to_body`を見てみると何もありません。例のごとく他のモジュールがオーバーライドしているのです。

```rb:rails/actionpack/lib/abstract_controller/rendering.rb
module AbstractController

  module Rendering

    # Performs the actual template rendering.
    # :api: public
    def render_to_body(options = {})
    end

  end

end
```

前章の通り`ApplicationController.ancestors_that_implement_instance_method`を実行してメソッドが実装されているクラスまたはモジュールを見つけていきましょう。

```rb
2.0.0-p353 :008 > ApplicationController.ancestors_that_implement_instance_method(:render_to_body)
 => [ActionController::Renderers, ActionController::Rendering, ActionView::Rendering, AbstractController::Rendering]
```

`ActionController::Renderers`と`ActionController::Rendering`、`ActionView::Rendering`で実装されていることが分かります。一つずつ見ていきましょう。

### ActionController::Renderers#render_to_body

`ActionController::Renderers#render_to_body`ではレンダラーを登録しており、オプションがレンダラーのキーを持っていればレンダラーを呼びます。もしレンダラーが見つからない場合は、`super`を呼びます。

```rb:rails/actionpack/lib/action_controller/metal/renderers.rb
module ActionController

  module Renderers

    def render_to_body(options)
      _render_to_body_with_renderer(options) || super
    end

    def _render_to_body_with_renderer(options)
      _renderers.each do |name|
        if options.key?(name)
          _process_options(options)
          method_name = Renderers._render_with_renderer_method_name(name)
          return send(method_name, options.delete(name), options)
        end
      end
      nil
    end

  end

end
```

これは主にrenderに`:json`や`:xml`のようなパラメータを渡す場合のためのもので、例えば以下のようなコードです。

```rb
class ArticlesController < ApplicationController

  def index
    @articles = Articles.all
    render json: @articles
  end
end
```

`:json`は`ActionController::Renderers`で登録されたレンダラーとなるので、このレンダーが呼ばれます。`ActionController::Renderers.add`でオリジナルのレンダラーを追加することもできます。

### ActionController::Rendering#render_to_body

`ActionController::Renderers#render_to_body`でレンダラーが見つからない場合`super`が呼ばれますが、これは`ActionController::Rendering#render_to_body`です。このメソッドで何をするかを確認していきましょう。

```rb:rails/actionpack/lib/action_controller/metal/rendering.rb
module ActionController

  module Rendering

    RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]

    def render_to_body(options = {})
      super || _render_in_priorities(options) || ' '
    end

    private

    def _render_in_priorities(options)
      RENDER_FORMATS_IN_PRIORITY.each do |format|
        return options[format] if options.key?(format)
      end

      nil
    end

  end

end
```

このメソッドは`super`を先に呼びますが、`super`が何も返さない場合のみ`_render_in_priorities`が呼ばれることに注目してください。

`_render_in_priorities`では`RENDER_FORMATS_IN_PRIORITY`を一つずつ探していき、フォーマットを見つけた場合にそのオプション値を返します。

このモジュールで`super`を呼ぶと`ActionView::Rendering#render_to_body`が呼ばれます。見ていきましょう。

### ActionView::Rendering#render_to_body

```rb:rails/actionview/lib/action_view/rendering.rb
module ActionView

  module Rendering

    def render_to_body(options = {})
      _process_options(options)
      _render_template(options)
    end

    # Returns an object that is able to render templates.
    # :api: private
    def view_renderer
      @_view_renderer ||= ActionView::Renderer.new(lookup_context)
    end

    private

      # Find and render a template based on the options given.
      # :api: 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
```

これは探していた肝心な部分です。`render_to_body`は`_render_template`を呼び、`_render_template`は`view_renderer.render(view_context, options)`を呼びます。

`view_renderer`は`ActionView::Renderer`のインスタンスで、初期化時に`ActionView::LookupContext`のインスタンスである`lookup_context`を渡します。`ActionView::LookupContext`はオプションに基づきテンプレートを探すための情報全てを保持します。さて次章ではこのクラスを詳しく見ていき、`LookupContext`や`ViewPaths`、`PathSet`がどのように組み合わさってテンプレートを探すのかを調べていきましょう。


# 3章 レンダラーによるレンダー

前章ではテンプレートは`ActionView::Renderer#render`メソッドでレンダーされることを見てきました。そしてこの章ではこのクラスを詳しく見ていきましょう。


それでは見ていきましょう。

### ActionView::Renderer

```rb:rails/actionview/lib/action_view/renderer/renderer.rb
module ActionView

  # This is the main entry point for rendering. It basically delegates
  # to other objects like TemplateRenderer and PartialRenderer which
  # actually renders the template.
  #
  # The Renderer will parse the options from the +render+ or +render_body+
  # method and render a partial or a template based on the options. The
  # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
  # the setup and logic necessary to render a view and a new object is created
  # each time +render+ is called.
  class Renderer
    attr_accessor :lookup_context

    def initialize(lookup_context)
      @lookup_context = lookup_context
    end

    # Main render entry point shared by AV and AC.
    def render(context, options)
      if options.key?(:partial)
        render_partial(context, options)
      else
        render_template(context, options)
      end
    end

    # Direct accessor to template rendering.
    def render_template(context, options) #:nodoc:
      TemplateRenderer.new(@lookup_context).render(context, options)
    end
  end
end
```

このクラスは`ActionView::LookupContext`クラスの`lookup_context`を引数にとって初期化されます。`render`メソッドが呼ばれた時に通常のテンプレートではオプションに`:partial`キーを持たないため`render_template`が呼ばれます。`render_template`メソッドは`TemplateRenderer`クラスのインスタンスを作成し`render`メソッドを作成します。

それでは`TemplateRenderer`クラスを見てみましょう。


```rb:rails/actionview/lib/action_view/renderer/template_renderer.rb
module ActionView

  class TemplateRenderer < AbstractRenderer

    def render(context, options)
      @view    = context
      @details = extract_details(options)
      template = determine_template(options)

      prepend_formats(template.formats)

      @lookup_context.rendered_format ||= (template.formats.first || formats.first)

      render_template(template, options[:layout], options[:locals])
    end

    private

    # Determine the template to be rendered using the given options.
    def determine_template(options)
      keys = options.has_key?(:locals) ? options[:locals].keys : []

      if options.key?(:body)
        Template::Text.new(options[:body])
      elsif options.key?(:text)
        Template::Text.new(options[:text], formats.first)
      elsif options.key?(:plain)
        Template::Text.new(options[:plain])
      elsif options.key?(:html)
        Template::HTML.new(options[:html], formats.first)
      elsif options.key?(:file)
        with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
      elsif options.key?(:inline)
        handler = Template.handler_for_extension(options[:type] || "erb")
        Template.new(options[:inline], "inline template", handler, :locals => keys)
      elsif options.key?(:template)
        if options[:template].respond_to?(:render)
          options[:template]
        else
          find_template(options[:template], options[:prefixes], false, keys, @details)
        end
      else
        raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
      end
    end
  end
end
```

このクラスは`ActionView::AbstractRenderer`クラスを継承しており、テンプレートは`determinate_template`メソッドによって決定され、このメソッドでは例えば`:body`や`:text`といったキーがオプションハッシュにあるかどうかを検証します。第1章で学んだ通り`:template`キーがない場合は`options[:template]`にアクション名が入ります。そして`options[:prefixes]`は配列になります。例えば、`ArticlesController`の`index`アクションにアクセスがあった場合に`:template`及び`:prefixes`は以下のようになります。


:prefixes : array [“articles”, “application”]
:template : string “index”

そしてテンプレートは`find_template`メソッドによって見つけられます。このメソッドは親クラスの`ActionView::AbstractRenderer`で定義されていますので、このクラスを見ていきましょう。

### ActionView::AbstractRenderer

```rb:rails/actionview/lib/action_view/renderer/abstract_renderer.rb
module ActionView

  class AbstractRenderer

    delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context


    def initialize(lookup_context)
      @lookup_context = lookup_context
    end

    def render
      raise NotImplementedError
    end

    protected

    def extract_details(options)
      @lookup_context.registered_details.each_with_object({}) do |key, details|
        value = options[key]

        details[key] = Array(value) if value
      end
    end
  end

end
```

`find_template`メソッドは実際には`lookup_context`に委任されており、このメソッドは後で確認しますが、`find_template`メソッドが呼ばれた時に`extract_details`メソッドの戻り値であるdetailsオブジェクトが渡されることを覚えておいてください。

上記`extract_details`メソッドを確認すると、このメソッドがハッシュを返すことが分かります。`lookup_context`から`registered_details`を取得し、オプションがキーを持っているかを確認し、もしあればその組を持つハッシュを返します。(バリューは配列に変換されます)

それでは`ActionView::LookupContext#registered_details`の中身はどうなっているでしょうか。見ていきましょう。

### ActionView::LookupContext#registered_details

```rb:rails/actionview/lib/action_view/lookup_context.rb
module ActionView

  # LookupContext is the object responsible to hold all information required to lookup
  # templates, i.e. view paths and details. The LookupContext is also responsible to
  # generate a key, given to view paths, used in the resolver cache lookup. Since
  # this key is generated just once during the request, it speeds up all cache accesses.
  class LookupContext #:nodoc:

    mattr_accessor :registered_details
    self.registered_details = []

    def self.register_detail(name, options = {}, &block)
      self.registered_details << name
      initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }

      Accessors.send :define_method, :"default_#{name}", &block
      Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
        def #{name}
          @details.fetch(:#{name}, [])
        end

        def #{name}=(value)
          value = value.present? ? Array(value) : default_#{name}
          _set_detail(:#{name}, value) if value != @details[:#{name}]
        end

        remove_possible_method :initialize_details
        def initialize_details(details)
          #{initialize.join("\n")}
        end
      METHOD
    end

    # Holds accessors for the registered details.
    module Accessors #:nodoc:
    end

    register_detail(:locale) do
      locales = [I18n.locale]
      locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
      locales << I18n.default_locale
      locales.uniq!
      locales
    end

    register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css,  :xml, :json] }
    register_detail(:variants) { [] }
    register_detail(:handlers){ Template::Handlers.extensions }
  end

end
```

`detail name`の配列であるモジュール属性`registered_details`を持っていることがわかります。

例えば、railsコンソールで`ActionView::LookupContext.registered_details`を呼ぶことでデフォルトのdetailsキーを見ることができます。

```rb
$ rails c
Loading development environment (Rails 4.2.0)
2.0.0-p353 :001 > ActionView::LookupContext.registered_details
 => [:locale, :formats, :variants, :handlers]
```

デフォルトでは`:locale`、`:formats`、`:variants`、`:handlers`の4つのdetailsが登録されており、上記にあるように`register_detail`が4回呼ばれ、detailsキーが1つずつ渡されます。

同時にこのクラスはハッシュであるインスタンス変数`@details`も管理します。このハッシュはキーにdetailキーを取り、`initialize_details`によって初期化されたデフォルト値である配列をバリューに取ります。(`initialize_details`については後で言及します)

`register_detail`が呼ばれるたびに幾つかのインスタンスメソッドがクラスに追加されます。例えば、`:locale`が渡ってきたとき、以下のメソッドを定義します。

default_locale : 渡されたブロックから返る結果であるデフォルトlocaleを返す
locale:  インスタンスメソッド`@details`に保持しているlocale値を返す
locale=: `@details`にlocale値をセットする

そして`registered_detail`が呼ばれるたびに`initialize_details`が再定義されます。例えば、初めて`register_detail(:locale)`が呼ばれるとき`initialize_details`は以下のようになります。

```rb
module ActionView

  class LookupContext #:nodoc:

    def initialize_details(details)
      @details[:locale] = details[:locale] || default_locale
    end
  end

end
```

そして`register_detail(:formats)`が呼ばれると、`initialize_details`は以下のように再定義します。

```rb
module ActionView

  class LookupContext #:nodoc:

    def initialize_details(details)
      @details[:locale] = details[:locale] || default_locale
      @details[:formats] = details[:formats] || default_formats
    end
  end

end
```

`initialize_details`は登録されたdetailキーそれぞれに対してのdetailバリューを取得し、もしキーが見つからなければデフォルトのdetailバリューがセットされます。


### ActionView::LookupContext#find_template

`ActionView::LookupContext#find_template`メソッドを確認してみましょう。

```rb:rails/actionview/lib/action_view/lookup_context.rb
module ActionView

  class LookupContext

    include ViewPaths

    # Helpers related to template lookup using the lookup context information.
    module ViewPaths
      attr_reader :view_paths, :html_fallback_for_js

      # Whenever setting view paths, makes a copy so that we can manipulate them in
      # instance objects as we wish.
      def view_paths=(paths)
        @view_paths = ActionView::PathSet.new(Array(paths))
      end

      def find(name, prefixes = [], partial = false, keys = [], options = {})
        @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
      end
      alias :find_template :find

      def find_all(name, prefixes = [], partial = false, keys = [], options = {})
        @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
      end

      def exists?(name, prefixes = [], partial = false, keys = [], options = {})
        @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
      end
      alias :template_exists? :exists?

      # Adds fallbacks to the view paths. Useful in cases when you are rendering
      # a :file.
      def with_fallbacks
        added_resolvers = 0
        self.class.fallbacks.each do |resolver|
          next if view_paths.include?(resolver)
          view_paths.push(resolver)
          added_resolvers += 1
        end
        yield
      ensure
        added_resolvers.times { view_paths.pop }
      end

    protected

      def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
        name, prefixes = normalize_name(name, prefixes)
        details, details_key = detail_args_for(details_options)
        [name, prefixes, partial || false, details, details_key, keys]
      end

      # Compute details hash and key according to user options (e.g. passed from #render).
      def detail_args_for(options)
        return @details, details_key if options.empty? # most common path.
        user_details = @details.merge(options)

        if @cache
          details_key = DetailsKey.get(user_details)
        else
          details_key = nil
        end

        [user_details, details_key]
      end

      # Support legacy foo.erb names even though we now ignore .erb
      # as well as incorrectly putting part of the path in the template
      # name instead of the prefix.
      def normalize_name(name, prefixes) #:nodoc:
        prefixes = prefixes.presence
        parts    = name.to_s.split('/')
        parts.shift if parts.first.empty?
        name     = parts.pop

        return name, prefixes || [""] if parts.empty?

        parts    = parts.join('/')
        prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]

        return name, prefixes
      end
    end
  end

end
```

`find_template`はこのモジュールにインクルードしているモジュール`ActionView::LookupContext::ViewPaths`と`ActionView::LookupContext`で実装されています。`find_template`は`find`メソッドのエイリアスであることが分かります。`find`メソッドは`ActionView::PathSet`のインスタンスである`@view_paths`に委任します。そのため`ActionView::PathSet#find`が何をしているのかを理解する必要があります。実際には`ActionView::PathSet#find`は`PathResolver`の組みに対して探索を委任していますが、この点について次章で確認していきます。


# 4章 オプションよりテンプレートパスを見つける

前章では`ActionView::LookupContext#find_template`は単に`ActionView::PathSet#find`に委任していることがわかりました。それでは`ActionView::PathSet#find`が何をしているのかを確認しましょう。


### ActionView::PathSet

`PathSet`というクラス名より、このクラスがテンプレートが保持されているパスの組みを管理するであろうことが分かります。さてこのクラスの定義を見ていきましょう。

```rb:rails/actionview/lib/action_view/path_set.rb
module ActionView #:nodoc:
  # = Action View PathSet
  #
  # This class is used to store and access paths in Action View. A number of
  # operations are defined so that you can search among the paths in this
  # set and also perform operations on other +PathSet+ objects.
  #
  # A +LookupContext+ will use a +PathSet+ to store the paths in its context.
  class PathSet #:nodoc:
    include Enumerable

    attr_reader :paths

    delegate :[], :include?, :pop, :size, :each, to: :paths

    def initialize(paths = [])
      @paths = typecast paths
    end

    def find(*args)
      find_all(*args).first || raise(MissingTemplate.new(self, *args))
    end

    def find_all(path, prefixes = [], *args)
      prefixes = [prefixes] if String === prefixes
      prefixes.each do |prefix|
        paths.each do |resolver|
          templates = resolver.find_all(path, prefix, *args)
          return templates unless templates.empty?
        end
      end
      []
    end

    def exists?(path, prefixes, *args)
      find_all(path, prefixes, *args).any?
    end

    private

    def typecast(paths)
      paths.map do |path|
        case path
        when Pathname, String
          OptimizedFileSystemResolver.new path.to_s
        else
          path
        end
      end
    end
  end
end
```

`initialize`メソッドは文字列配列である`paths`引数を受け取ることが分かります。そして`initialize`メソッドでは、`typecast`メソッドに`paths`を渡し、`paths`の各要素において型変換した値をインスタンス変数`@paths`に保持します。`typecast`メソッドは`OptimizedFileSystemResolver`のインスタンスに変換します。
`find_all`メソッドは`paths`の中の各resolverインスタンス対して繰り返しを実施し、空でない最初の値を返します。
`find`メソッドは`find_all`メソッドに委任しているだけで、もしテンプレートが見つからない場合は例外`MissingTemplate`を発生させます。
`LookupContext`クラスでは、`ActionView::PathSet`がどのように生成されているのかを確認することができます。


```rb:rails/actionview/lib/action_view/lookup_context.rb
module ActionView
  class LookupContext

    # Helpers related to template lookup using the lookup context information.
    module ViewPaths
      attr_reader :view_paths

      # Whenever setting view paths, makes a copy so that we can manipulate them in
      # instance objects as we wish.
      def view_paths=(paths)
        @view_paths = ActionView::PathSet.new(Array(paths))
      end
    end

    include ViewPaths
  end
end
```

`view_paths=`メソッドは`ActionView::LookupContext::ViewPaths`で定義されており、`ActionView::LookupContext`はこのモジュールをインクルードしています。`view_paths=`メソッドが呼ばれると`ActionView::PathSet`のインスタンスが生成され、`ActionView::LookupContext`のインスタンス変数`@view_paths`にセットされます。

さて、どのように`view_paths=`メソッドが呼ばれるのでしょうか？それは`ActionView::LookupContext#initialize`メソッドの中にあります。

```rb:rails/actionview/lib/action_view/lookup_context.rb
module ActionView
  class LookupContext

    def initialize(view_paths, details = {}, prefixes = [])
      @details, @details_key = {}, nil
      @skip_default_locale = false
      @cache = true
      @prefixes = prefixes
      @rendered_format = nil

      self.view_paths = view_paths
      initialize_details(details)
    end

  end
end
```

`view_paths`は`ActionView::LookupContext`インスタンスの初期化で渡されます。そして、`ActionView::ViewPaths`で`ActionView::LookupContext`を初期化します。


```rb:rails/actionview/lib/action_view/view_paths.rb
module ActionView
  module ViewPaths
    extend ActiveSupport::Concern

    included do
      class_attribute :_view_paths
      self._view_paths = ActionView::PathSet.new
      self._view_paths.freeze
    end

    # LookupContext is the object responsible to hold all information required to lookup
    # templates, i.e. view paths and details. Check ActionView::LookupContext for more
    # information.
    def lookup_context
      @_lookup_context ||=
        ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
    end

    def append_view_path(path)
      lookup_context.view_paths.push(*path)
    end

    def prepend_view_path(path)
      lookup_context.view_paths.unshift(*path)
    end

    module ClassMethods
      # Append a path to the list of view paths for this controller.
      #
      # ==== Parameters
      # * <tt>path</tt> - If a String is provided, it gets converted into
      #   the default view path. You may also provide a custom view path
      #   (see ActionView::PathSet for more information)
      def append_view_path(path)
        self._view_paths = view_paths + Array(path)
      end

      # Prepend a path to the list of view paths for this controller.
      #
      # ==== Parameters
      # * <tt>path</tt> - If a String is provided, it gets converted into
      #   the default view path. You may also provide a custom view path
      #   (see ActionView::PathSet for more information)
      def prepend_view_path(path)
        self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
      end

      # A list of all of the default view paths for this controller.
      def view_paths
        _view_paths
      end

  end
end
```

この`ActionView::ViewPaths`はコントローラクラスにもインクルードされます。デフォルトでは`view_paths`は空配列です。しかし、`append_view_path`や`prepend_view_path`がビューパスをビューパス配列の最初または最後に追加します。この`prepend_view_path`と
`append_view_path`はクラスメソッドとしてもインスタンスメソッドとしても定義されていることに注意してください。

ではビューパスはどこで初期化されているでしょうか？実は`Rails::Engine`クラスなのです。

```rb:rails/railties/lib/rails/engine.rb
module Rails

  class Engine

    initializer :add_view_paths do
      views = paths["app/views"].existent
      unless views.empty?
        ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) }
        ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) }
      end
    end

  end

end
```

イニシャライザーはアプリケーション開始時に呼び出されるブロックとして定義されています。`:action_controller`が読み込まれた時に`prepend_view_path`が呼ばれ、`view_paths`はただ1つの要素である`app/views`ディレクトリを含むことになります。


### OptimizedFileSystemResolver

さて、`ActionView::PathSet`がどのように初期化されるのかを理解しましたが、実際のところresolverに`find`メソッドを委任していることも知りました。なのでresolverでどのようにメソッドが実装されているのかを見ていきましょう。

![resolver.png](https://qiita-image-store.s3.amazonaws.com/0/17373/6b3efc79-63ad-c72c-b808-3687a6ef13ed.png)

Resolverは渡ってくるdetailsを受け、テンプレートを解決するためにあります。クラスの関係は上記図のとおりです。

通常、resolverは`ActionView::PathSet`で使われ、`ActionView::OptimizedFileSystemResolver`です。このクラスは`ActionView::FileSystemResolver`を継承し、さらにこのクラスは`ActionView::PathResolver`を継承し、さらにこのクラスは`ActionView::Resolver`を継承しており、このクラスはすべてのresolverの基底となっています。

We can see that by default the resolver used in ActionView::PathSet, which is ActionView::OptimizedFileSystemResolver, extends from ActionView::FileSystemResolver, which in terms extends from ActionView::PathResolver, which extends from ActionView::Resolver, which is the base class for all resolvers.


基底クラスの`ActionView::Resolver`はすべてのサブクラスのresolverで使われるキャッシュのようないくつかの共通な機能性を提供しています。しかしここでは、`ActionView::PathResolver`において、パスの中でテンプレートをどのように見つけるのかというロジックがどのように実装されているかについて注目していきましょう。


```rb:rails/actionview/lib/action_view/template/resolver.rb
module ActionView

  class PathResolver < Resolver

    EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
    DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"

    def initialize(pattern=nil)
      @pattern = pattern || DEFAULT_PATTERN
      super()
    end

    private

    def find_templates(name, prefix, partial, details)
      path = Path.build(name, prefix, partial)
      query(path, details, details[:formats])
    end

    def query(path, details, formats)
      query = build_query(path, details)

      template_paths = find_template_paths query

      template_paths.map { |template|
        handler, format, variant = extract_handler_and_format_and_variant(template, formats)
        contents = File.binread(template)

        Template.new(contents, File.expand_path(template), handler,
          :virtual_path => path.virtual,
          :format       => format,
          :variant      => variant,
          :updated_at   => mtime(template)
        )
      }
    end

    def find_template_paths(query)
      Dir[query].reject { |filename|
        File.directory?(filename) ||
          # deals with case-insensitive file systems.
          !File.fnmatch(query, filename, File::FNM_EXTGLOB)
      }
    end

    # Helper for building query glob string based on resolver's pattern.
    def build_query(path, details)
      query = @pattern.dup

      prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
      query.gsub!(/\:prefix(\/)?/, prefix)

      partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
      query.gsub!(/\:action/, partial)

      details.each do |ext, variants|
        query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
      end

      File.expand_path(query, @path)
    end

    def escape_entry(entry)
      entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
    end
  end

end
```

もっとも重要なメソッドは`build_query`と`find_template_paths`です。

- `PathResolver`はパス中のテンプレート探索のためにパターンを使用し、デフォルトパターンは`DEFAULT_PATTERN`です

- `build_query`: このメソッドはパスやdetailsからクエリを作成します。前章であった通り`ActionView::AbstractRenderer#extract_details`はオプションからdetailsを抽出します。例えば、`Articles`コントローラの`index`アクションの場合クエリは以下のようになります

```
articles/index{.en,}{.html,}{+:variants,}{.haml,}
```

このクエリでは、`find_template_paths`で`Dir[query]`が呼ばれますが、これは実際のところ`glob`のエイリアスで、クエリを展開します。例えば、上記クエリだと`{p, q}`は`p`または`q`のどちらかがマッチします。なので例えば`{.en,}`の場合`.en`または空文字列にマッチします。テンプレート`articles/index.html.haml`を定義する場合このクエリにマッチします。詳細は[Ruby doc](http://ruby-doc.org/core-2.2.0/Dir.html#method-c-glob)を確認してください。

マッチした各テンプレートにおいて`query`メソッドではテンプレートファイルをラップするTemplateインスタンスを作成します。

# まとめ

さて、まとめとしてRailsがテンプレートを見つける流れで分かったことは以下のとおりです。

- テンプレートを解決するためにPathsの組みを初期化し、デフォルトでは`app/views`のみが含まれる
- コントローラのアクションにアクセスがあった時、レンダーは明示的または暗黙的に呼ばれる
- レンダーメソッドに渡されたオプションからactionやdetails、prefixesを抽出
- prefixesやaction、detailsから構成されたクエリを用いてパスセットを検索することによってテンプレート探索する