11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ruby on RailsAdvent Calendar 2022

Day 14

RailsがJSONをレンダーするときに何が起きているのか

Posted at

はじめに

この記事はRuby on Rails Advent Calendar 2022の14日目の記事です。

概要

この記事ではRailsのrenderメソッドを読み、内部で何が起きているのかを理解していきます。

動機

私は個人でAlbaというJSONシリアライザを開発しています。その際、Railsの機能に合わせて以下のような動作をさせるようにしました

render json: FooResource.new(foo)

これは

render json: FooResource.new(foo).serialize

と同じ挙動になっています。

なぜこうなるのか?をまとめるのがこの記事の目的です。そしてその目的のため、Railsのソースコードを読んでいきます。

注意

以下のコードは全てRails7.0.4時点のものとなります。

本編

renderメソッドの定義箇所を特定する

コントローラ内に以下のスニペットを置くことでrenderメソッドの定義箇所がわかります。

pp method(:render).source_location

私は適当にrails newしたアプリに空っぽのコントローラを作ってそこに書きました。その結果、

がヒットしました。ここでは22行目でsuperを読んでいるので、そちらに処理が移っているようです。ファイル名からしても(計測用モジュールらしいので)、super先を見に行くべきでしょう。

ここでは先ほどのスニペットを修正してsuper先のメソッドの定義箇所を調べます。

pp method(:render).super_method.source_location

その結果、

が出ましたが、これも内容がないのでもう一回superを調べます。

pp method(:render).super_method.super_method.source_location

そうしたら

が出ました。ここにはsuperがないので、このメソッドを見ていきましょう。

renderメソッドの中身を調べる

上記メソッドの内容は以下の通りです。

# Normalizes arguments, options and then delegates render_to_body and
# sticks the result in <tt>self.response_body</tt>.
def render(*args, &block)
  options = _normalize_render(*args, &block)
  rendered_body = render_to_body(options)
  if options[:html]
    _set_html_content_type
  else
    _set_rendered_content_type rendered_format
  end
  _set_vary_header
  self.response_body = rendered_body
end

_normalize_renderrenderに渡されたオプションを処理する箇所のようです。

_normalize_argsが中心的な処理のようです。

render json: somethingにおいて、このメソッドのaction引数に渡されるのはjson: somethingであり、Hashであるのでaction、すなわちjson: somethingが返ります。返り値は_normalize_render内のoptions変数に代入され、_process_variantに渡されます。

_process_variantは空です。これまでの継承チェーンのどこかに同名のメソッドがあるでしょうか。

ありました。条件を満たしていれば:variantのキーに対して値をセットするようです。

次の_normalize_optionsも同様で、

:statusなどをoptionsに追加しています。

コメントに「render_to_bodyに委譲する」と書いてあります。先ほどまでで作られたoptionsがそれに渡されているようです。それも見てみましょう。

なんと中身は空です。ということは、これまで見てきたファイルの中の同名のメソッドがあるはずです。再びRailsアプリケーションにスニペットを仕込んでみましょう。

pp method(:render_to_body).source_location

すると、

が出てきます。このメソッドは_render_to_body_with_rendererを呼んでいます。

_renderersという変数が出てきました。これはどこでセットされているのでしょうか。

rendererの概念

このRenderersというファイルに何かありそうです。このファイルを_renderersで検索すると、

で代入しているのが見つかります。コメントを読むに怪しそうなのは前者です。Allというモジュールがincludeされるとこのコードが呼ばれるようです。また、代入されるオブジェクトはRENDERERSという定数になっています。AllでRailsのコードベースを検索すると、

が出てくるので、ActionController::BaseRenderers::Allincludeしていることがわかりました。では、RENDERERSには何が入っているのでしょうか。まず、

から、RENDERERSSetです。そして、

から、addというクラスメソッドを呼ぶとRENDERERSにオブジェクトが追加されるようです。このaddは同じファイル内でも使われていて、

でやっとお目当てのJSONレンダーにたどり着きました。

JSONレンダリングの中身

当該のaddの中身は以下です。

add :json do |json, options|
  json = json.to_json(options) unless json.kind_of?(String)

  if options[:callback].present?
    if media_type.nil? || media_type == Mime[:json]
      self.content_type = Mime[:js]
    end

    "/**/#{options[:callback]}(#{json})"
  else
    self.content_type = Mime[:json] if media_type.nil?
    json
  end
end

add内の1行目で引数が文字列でなければto_json(options)を呼ぶようになっています。これが今回私がAlbaの開発で探していた行でした。ここでのoptionsがくせ者で、これまで見てきた:statusのようなキーを含んだハッシュとなっています。必ず渡されるのと、Ruby3.0以降のキーワード引数の仕様変更の影響を受けるらしく、そのため

のような書き方をする必要がありました。

addからrenderに戻る

さて、addの処理に戻ると、渡したブロックの中身でメソッドが定義されます。このメソッドは

_render_to_body_with_rendererから呼ばれます。この部分に到達するには、

_renderers_に含まれる:jsonのシンボルがoptionsのキーに含まれている必要があり、先ほどまでの話と合致しています。ここで返された値はrender`メソッドまで戻り、

self.response_bodyに格納されます。まだ続きがありそうですが、長くなったので今日はここまでとします。

まとめ

当初の想定よりだいぶガッツリとしたコードリーディングになってしまいました。Railsにおいては一見単純に見える処理もだいぶ複雑な工程を経て処理されていることがわかりますね。

今回は特にそうだったのですが、Railsのコードリーディングにおいてはgrepするだけでは目的を達成できないことが多いです。source_locationなどを駆使する必要があるのですが、慣れれば結構楽しいので皆さんも気になる箇所を読んでみてはいかがでしょうか。

宣伝

コードを読むのが好きなあなたに、こんなコミュニティはいかがでしょうか。

主にgemのコードを読む会です。メインの読み手は私が務め、それに参加者の方がガヤを入れる形式となります。次回開催は未定ですが、よければメンバーになってみてください。

11
3
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
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?