うちのいぬ Tech Blog

Tech Blog of Uchinoinu/My dog

Crafting Rails 4 Applications - capter 1-3 - Understanding the Rails Rendering Stack

こちらの続きです。

tsumekoara.hateblo.jp

f:id:susanne:20150411160445j:plain

1-3. Rails Rendering Stackを理解する

Action MailerとAction Controller はいくつかの共通機能を持っています。例えば、テンプレートレンダリング、ヘルパー、レイアウトがそれにあたります。コードの重複を避けるためにも、これらはAbstract Controllerに責務を集約し、Action MailerもAction Controllerも基礎機能としてAbstract Controllerを利用します。同時に、いくつかの機能は2つのライブラリのどちらかを必要とします。これらの要件があることもあり、Abstract Controllerは開発者がよきように使えるような設計をされています。例えば、テンプレートをレンダリングするけどレイアウトをincludeしない様な、基本レンダリング機能を持つオブジェクトを持ちたいとき、オブジェクトのAbstratController::Renderingモジュールをincludeし、AbstractController::layoutsを省きます。

オブジェクト内のAbstractController:renderingをincludeする時、レンダリングスタックは図1の様になります。これは10ページごとにrender()メソッドをAbstractController::Renderingを使って呼び出す際のレンダリングスタックを表現しています。

|     METHOD                            |      CLASS                          |
render --------------------------- AbstarctContrller::Rendering
+->_normalize_render --------- AbstarctContrller::Rendering
      +-> _normalize_args ------ AbstarctContrller::Rendering
      +-> _normalize_options --- AbstarctContrller::Rendering
+-> render_to_body ------------ AbstarctContrller::Rendering
      +-> _process_options ----- AbstarctContrller::Rendering
      +-> _render_temlate ------- AbstarctContrller::Rendering
             +-> render ------------- ActionView:renderer

このスタックはAbstractController::Renderingの実装を見ると確認できます。

rails/actionpack/lib/abstract_controller/rendering.rb

def render(*arg, &block)
  options = _normalize_render(*arg, &block)
  self.response_body = render_to_body(options)
end

def _normalize_render(*args, &block)
  options = _normalize_args(*args, &block)
  _normalize_options(options)
  options
end

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

Abstract Controllerのレンダリングスタックは引き渡された引数やオプションの標準化や、それらを、テンプレートを最終的にレンダリングするActionView::Renderer#render()が許可したオプションのハッシュに変換することに責務を負います。スタック内のそれぞれのメソッドは歴無をカバーする特定の役割を持ちます。これらのメソッドは、アンダースコアで始まるメソッドの様にプライベートにもパブリックなAPIの一部にもなりえます。

このスタックの最初の関連メソッドnomalize_args()であり、normailzed_render()により呼びだされ、ユーザーが与えた引数をハッシュに変換します。これにより、render()メソッドがrender(:new)として呼び出されることができ、nomalized_args()はrender(action: "new")に変換されます。normalize_args()が返すハッシュは normalize_options()により更に標準化されます。ここにはAbstractController:rendering#_normalize_options内での標準化は多くありませんが、render(partial: true)をrender(partial: action_name)に変換します。だから、いつpartial: trueをshow()アクションの中であたえても、partial: "show"のスタックになるわけです。

標準化の後は、render_to_body()が呼び出されます。ここからが本当のレンダリングの始まりです。まず、viewに対して無意味なすべてのオプションを、process_options()メソッドを使って処理します。AbstractController:rendering#process_options()葉からメソッドですが、ActionController:rendering#_proces_options()はこのメソッドで何が行われるかを知るための事例のために見ることができます。例えば、以下を呼び出すlkおとを許可するControllerがあります。

render template: "shared?not_authenticated", sattus: 401

ここでは:statusおpションがviewにとって無意味です。statusはHTTPレスポンスステータスを参照しているだけだからです。そこで、ActionController::Rendering#_process_optionsの責務として、このオプションや他のオプションの操作を中断させます。

オプションの処理が終わったら、_render_template()が呼び出され、異なるオブジェクトがコラボレートし始めます。特に、view_rendererに呼び出されるActionView::Rendererのインスタンスが生成され、render()メソッドが2つの引数とともに呼び出されます。2つの引数とはview_contextと標準化されたオプションのハッシュです。

rails/actionpack/lib/abstract_controller/rendering.rb

view_renderer.render(view_context, options)

view contextとはActionView::Baseのインスタンスであり、テンプレートが評価されるコンテキストです。例えば、テンプレート内でlink_to()を呼び出す際にこれが働くのは、ActionView::Baseの中で有効なメソッドだからです。初期化された時には、view contextはview_assigns()を引数として受け取ります。これによりviewでアクセスできるcontroller変数のグループの参照を割り当てられます。@posts = Post.allの様なController内のインスタンス変数をセットしたらいつでも、@postsは割り当てられ、viewで利用可能になります。

この時点では、Rails2.3と3.0で起きたことの逆のことを強調することが大事になります。前者では、viewはControllerからの値の回収に責任を負い、後者はControllerはviewに使う値を教えていた。

Viewに何の値も引き渡さないControllerが欲しい場合、Rails2.3では、viewは自動的にControllerからインスタンス変数で引き渡されました。これはControllerでインスタンス変数の仕様を止めたり、Templateをレンダリングする前にすべてのインスタンス変数を削除することを達成しようとした為の仕様でした。Rails3以降では、これはControllerの責務になり、空ハッシュを返すview_assigns()メソッドをオーバーライドするだけで良くなりました。

class UserController << ApplicationController
  protected
  def view_assigns
    {}
  end
end

空ハッシュを返すことで、コントローラー内のどのアクションでもviewにassignを渡さないことを保証しました。

Viewコンテキストと標準化されたハッシュを使うことで、ActionView::RendererインスタンスがTemplateを見つけるために必要なものすべてをもつようになり、最終的にはViewコンテキスト内でレンダリングします。

このモジュラーとスタックにより、だれでもプロセスをレンダリングさせるフックや、自身の機能を追加することが出来るようになりました。AbstractController::Renderingの最初のAbstractController::Layoutsをincludeする際に、レンダリングスタックは以下の様になります。

図2: AbstractController::RenderingやAbstractController::Layoutsと一緒にrender()した時のレンダリングスタック

| METHOD                         | CLASS           |
render ------------------------ AbstractController::Rendering
+-> _normalize_render ------ AbstractController::Rendering
     +-> _normalize_args ----- AbstractController::Rendering
     +-> _normalize_options -- AbstractController::Rendering / AbstractControler:Layouts
+-> render_to_body ---------- AbstractController::Rendering
       +-> _process_options --- AbstractController::Rendering
       +-> _render_template --- AbstractController::Rendering
              +-> render ----------- ActionView::Renderer

AbstractController::Layoutsは:layoutオプションをサポートする_normalize_options()をオーバーライドします。render()が呼び出され:layoutオプションがセットされてない場合、Controllerクラスレベルで開発者が設定した値に基づき自動的にセットされます。ActionControllerは、スタックをレンダリングするAbstractControllerを拡張し、Controllerスコープ内だけで意味が通るオプションを追加、実行されます。それらのエクステンションは4つのMainモジュールに分解されます。

  • ActionController::Rendering: render()をオーバーライドして、二重で呼び出されていないかチェックして、DoubleRenderErrorを立ち上げます。その場合、_process_options()をオーバーライドし、:location, :status, :content_typeといったオプションを処理します。

  • ActionController::Renderers: このチャプターで使うAPIを追加し、:pdfといったキーが与えられたら特定の振る舞いを呼び起こします。

  • ActionController:instrumentation: render()メソッドオーバーロードレンダリングスタック内でどれくらいの時間が使われたか計測します。

  • ActionController::Streaming: 正しいHTTPヘッダーをセットすることで:streamを処理する process_options()メソッドや、Templateを処理するreder_template()メソッドオーバーロードします。

図3: AbstractControllerとActionControllerと一緒にrender()を呼び出した時のレンダリングスタック

render
+- _normalize_render

次に図3に注目してみます。render_to_string()はどの様に動くのでしょうか・AbstractController::Renderingの定義を見て行きましょう。

rails/actionpack/lib/abstract_controller/rendering.rb

def render_to_string(*args, &block)
  options = _normalize_render(*args, &block)
  render_to_body(options)
end

まずはじめにrender_to_string()メソッドはrender()にとても良く似ています。違いは、render_to_string()はレンダリングされたテンプレートをレスポンスbodyとして保存しない点です。しかし、レンダリングスタックを分析すると、いくつかのActionControllerモジュールがrender()をオーバーロードし、render_to_string()が処理されていない時に、振る舞いを追加していることがわかります。

例えば、render_to_string()をrenderer内で使うことで、ActionController::Instrumentationにより定義された測定イベントが二度トリガーされないことや、render()メソッドにだけ機能が追加され二重レンダリングエラーが呼ばれないことを保証します。

多くの場合、render_to_string()はオーバーロードされます。ActionController を使うとき、レスポンスbodyはstringでない、テンプレートを配信する際に発生する別のオブジェクトになりえます。これは、ActionController::Renderingがrender_to_string()をオーバーライドして、name指標としてstringを常に返す為です。

Work In Progress

1-1. Crafting Rails 4 Applications - capter 1-1 - Satomi's Daily Notes

1-2. Crafting Rails 4 Applications - capter 1-2 - Writing the Renderer - Satomi's Daily Notes