Rails: レスポンスのライフサイクルとETagを理解する(翻訳)
原注
本記事はRailsConf 2020での発表内容を元にしています。当日のスライドと動画は以下でご覧いただけます。
昨年、Skylightチームは「リクエストのライフサイクル」という題で発表いたしました。この発表では、ブラウザにURLを入力してからリクエストがRailsコントローラのアクションに到達する間に起こるすべてを扱いましたが、時間の都合で以下のセリフで終わってしまいました。
Q: "コントローラのアクションに到達したら、Railsはどのようにレスポンスをブラウザに送り返すでしょうか?"
リクエストとレスポンスの両方について解説することで、ブラウザのリクエスト/レスポンスサイクルの全体像が見えてきます。しかしこれらを理解するのに講演動画を見る必要はありませんのでご安心ください。まずは重要なコンセプトを少しおさらいしておきましょう。
それでは皆さまベルトをお締めください。当機Safariはこれよりレスポンスのライフサイクルに突入いたします。
最初におさらいを少々
それではサファリジープに乗って、skylight.io/safari
1に向かうことにしましょう。このページにアクセスすると、"Hello World"が表示されるはずです。
大変!サファリのWebサーバーがライオンに占領されてしまったようです。"Hello World"の代わりに"Roar Savanna"(ガオー、サバンナ)と表示されています。何が起きたのか調べてみましょう。最初に以下の疑問を解消する必要があります。
Q: "ブラウザがサーバーに接続したとき、サーバーはブラウザからの指示をどうやって知るのでしょうか?"
ブラウザとサーバーは、互いに「会話する」ときの言語について事前に合意し、相手が何を求めているかを理解できるようにしておく必要があります。この一連のルールを「HTTP」(Hypertext Transfer Protocol)と呼び、ブラウザとサーバーはどちらもこのHTTPという言語を理解します。ここで言うプロトコルとは会話の「ルールセット」を指します。
skylight.io/safari
にアクセスするためのリクエストは、最もシンプルに行える形を取っています。ここでは/safari
パスに対してGET
リクエストを指定し、HTTPプロトコルバージョンは1.1を用い、ホストは"skylight.io"です。
GET /safari HTTP/1.1
Host: skylight.io
サーバーから送り返される、HTTPに準拠するレスポンスは以下のような感じになります。
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
Date: Thu, 25 Apr 2019 18:52:54 GMT
Roar Savanna
このレスポンスではリクエストが成功したことが示されており、その他にもContent-Type
、Content-Length
、Date
というヘッダー情報が添えられています。そして最後にやっと"Roar Savanna"という文字列が出現します。これはレスポンスのbody(本文)です。
Q: "リクエストを送信してからレスポンスを受信するまでの間に、どんなことが起きるのでしょうか?"
リクエストはブラウザから送信されると、インターネットを経由してWebサーバーに到達します(はいはい👋👋、詳しくは前回の記事をどうぞ)。
ここで別のプロトコルが登場します。"Rackプロトコル"は、HTTP準拠のリクエストをRack準拠のRubyアプリ(Railsなど)が理解できる形に変換するルールのセットです。
PumaやunicornなどのWebサーバーは、HTTPリクエストを解釈してenv
ハッシュに変換してから、このハッシュを引数としてRailsアプリを呼び出します。
env = {
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/safari',
'HTTP_HOST' => 'skylight.io',
# ...
}
MyRailsApp.call(env)
Railsはenv
ハッシュを受け取ると、これをいくつもの「ミドルウェア」を経由してコントローラのアクションに渡します(はいはい👋👋、これも詳しくは前回の記事をどうぞ)。
私たちのコントローラでは、ライオンが以下のようなコードを書いていました。
class SafariController < ApplicationController
def hello
# サバンナは平野(plain)の一種で...(このシャレわかりますか?)
render plain: "Roar Savanna"
end
end
このSafariコントローラにはhello
アクションがあり、これは"Roar Savanna"という平文テキストをレンダリングするようRailsに指示しています。
Railsがこのコントローラのコードを実行すると、結果をすべてのミドルウェアを経由してから3つの値(ステータスコード、ヘッダーのハッシュ、レスポンスのbody)を持つ配列を返します。本記事では、これを"レスポンス配列"と呼ぶことにします。
env = {
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/safari',
'HTTP_HOST' => 'skylight.io',
# ...
}
status, headers, body = MyRailsApp.call(env)
# レスポンス配列:
status # => 200
headers # => { 'Content-Type' => 'text/plain', 'Content-Length' => '12', ... }
body # => ['Roar Savanna']
Rack準拠のWebサーバーがこの配列を受け取ると、HTTP準拠の平文テキストに変換してからブラウザに送り返します。「ガオー、サバンナ!」
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
Date: Thu, 25 Apr 2019 18:52:54 GMT
Roar Savanna
難しくありませんよね?
Q: "でも、Railsは配列に入れるべきものをどうやって知るのでしょうか?ブラウザはレスポンスを受け取ったときに何をすべきかをどうやって知るのでしょうか?"
その答えは以下の続きをどうぞ。
ステータスコード
レスポンス配列の第1項目はステータスコードです。ステータスコードは3桁の数値で、リクエストが成功した場合や失敗した場合、そして失敗した場合の理由を表します。
レスポンスのステータスコードは以下の5つのクラスに分類されています。
1xx
- 情報(利用頻度は低いのでここでは立ち入りません)
2xx
- 成功
3xx
- リダイレクト
-
4xx
- クライアントエラー(リクエストを送信したクライアント側に原因があるエラー)
-
5xx
- サーバーエラー(サーバー側に原因があるエラー)
ステータスコードが標準化されているおかげで、英語がわからなくても(本文がどんな自然言語で書かれていても)レスポンスの意味を理解できます。ブラウザはこれを利用して、たとえばユーザーや開発ツールに適切なUI要素を表示したりできます。
Googleのクローラもステータスコードの指示に従います。ページのレスポンスが200 OK
ステータスならそのページをインデックスに登録し、ページのレスポンスが500
エラーの場合は後でアクセスします。ページのレスポンスが300
系のステータスコードの場合、クローラはリダイレクト指示に従います。
このような理由から、開発者はできるかぎり正確なステータスコードを選ぶことが望まれます。
(プロ向け: ステータスコードについてはhttps://www.webfx.com/web-development/glossary/http-status-codes/で多くのことを学べます)。
最もシンプルなレスポンスでは、bodyは必須ではありません。さらに、適切なステータスコードを選ぶことで、ブラウザにレスポンスのbodyが存在しない前提で処理するよう指示することも可能です。
たとえば、Safariコントローラにeat_hippo
アクションがあるとしましょう。
class SafariController < ApplicationController
def eat_hippo
consume_hippo if @current_user.lion?
head :no_content # 204
end
end
このアクションは、カレントユーザーがライオンの場合にのみカバ(hippo)を食べることを許可し、204レスポンスを返します。204は「サーバーは無事にリクエストを処理でき、レスポンスのbodyで送り返す追加のコンテンツは存在しない」という意味です。サファリ語で言うと「ライオンはカバをすっかり平らげ、送り返す死体(body)は残っていない」みたいな意味になります。
Railsのhead
メソッドは、「bodyのないステータスコードのみのレスポンスを返す」ショートハンドです。head
メソッドはステータスコードのシンボルを受け取ります。ここで使った:no_content
はステータスコード204に相当します。
リダイレクト
この他によく使われるステータスコードには、リダイレクト用の300系ステータスコードがあります。
たとえば、skylight.io/find-hippo
にGET
リクエストを送信すると、find_hippo
アクションがoasis_url
にリダイレクトするとしましょう(その時期が乾季で、カバが水を求めてオアシスに向かうという理由で)。
Railsのredirect_to
メソッドは、デフォルトで302 Found
を返し、デフォルトでブラウザのリダイレクト先URLを示すLocation
ヘッダーを含めます(その他のヘッダーについては後述)。この302ステータスは「カバは一時的にoasis_url
に住んでいる。カバは他の場所に移住する可能性もあるので、このLocation
ヘッダーを常にチェックすること」という意味です。
しかし、カバが(地球温暖化か何かが理由で)oasis_url
に一時的ではなく恒久的に移住した場合、以下のようにstatus
にredirect_to
を指定できます。
この:moved_permanently
というシンボルはステータスコード301に相当し、「カバがオアシスに恒久的に移住したので、カバを探したければ常にオアシスを探せ」とブラウザに指示します。次回/find-hippo
にアクセスすると、ブラウザは自動的に/oasis
にアクセスします。このときブラウザは、/find-hippo
に余分なリクエストを送信しません。
または、ルーティングファイルに以下を追加することも可能です。
リダイレクトをルーティングファイルで扱えば、コントローラのアクションを削除しても動くようになります。ルーター内のredirect
ヘルパーも301レスポンスを返します。
注意!redirect_to
メソッドにはひとつ重大な注意点があります。redirect_to
メソッドを呼び出した後であっても、コントローラは引き続きコードを実行します。たとえば以下のfind_hippo
アクションを見てください。
redirect_to
を呼び出してもreturn
に相当する処理は行われないので、実際にはリダイレクトとレンダリングが両方行われてしまいます。Railsは301と200のどちらのレスポンスを返せばよいのかを認識できず、Double Render Errorが発生します。
これを修正しました。リダイレクト処理をbefore_action
フックに移動することで、コントローラのアクション全体をスキップし、render
処理が呼ばれないようになりました。
ステータスコードは、ブラウザに重要な情報をきわめて簡潔に伝える方法ですが、その他にブラウザがレスポンスをどう扱うべきかを追加で指示する必要もしばしば生じます。この追加指示がヘッダーです。
ヘッダー
ヘッダーは、RailsがWebサーバーに返すレスポンス配列内の第2項目に、ハッシュの形で含まれます。
ヘッダーはレスポンスに追加情報を添えます。追加される情報は、たとえばレスポンスをキャッシュすべきか、するならどのぐらいの期間キャッシュするかをブラウザに指示したり、JavaScriptクライアントアプリで使うメタデータを提供したりするのに使われます。
Location
ヘッダーについては既に解説しましたね。このヘッダーは、リダイレクト先をブラウザに指示するのに使われます。
Railsアプリでよく見かけるヘッダーの一部を以下に示します。
Content-Type
- このレスポンスヘッダーは、実際に返されるContent-Type(コンテンツの種類)をブラウザに指示します。Content-Typeには、画像、HTMLドキュメント、フォーマットのない平文テキストなどがあります。ブラウザはこのヘッダーをチェックすることで、レスポンスをUI上に表示する方法を認識します。
Content-Length
- このレスポンスヘッダーは、レスポンスのサイズ(単位はバイト)をブラウザに指示します。たとえば、あるエンドポイントに
HEAD
リクエストを送信すると、エンドポイントはhead :ok
とContent-Length
をレスポンスとして返すので、これを元にダウンロードの進捗をパーセント表示したりできます(この場合レスポンスのbodyがすべて返されるまで待たなくてもレスポンスのサイズがわかるので、ダウンロードのパーセント値の有用性は落ちますが)。 Set-Cookie
- このヘッダーは、Webサーバーとクライアントの間で共有するcookieを表します。cookieは、セミコロンで区切られた文字列の形のキーバリューを含みます。たとえば、Railsではcookieを用いて複数のセッションにまたがるユーザーのリクエストをトラッキングします。Railsのcookieは
CookieJar
(クッキーを入れる瓶)という冗談のような名前のクラスで管理されます。
response.headers['HEADER NAME'] = 'header value'
HTTPキャッシュ
ヘッダーは、ブラウザにキャッシュ関連の指示を出すのにも使われます。"HTTPキャッシング"とは、ブラウザ(またはブラウザのプロキシ)がHTTPレスポンス全体をどんなタイミングで保存するかを意味します。次回そのエンドポイントにリクエストを送信すると、キャッシュに保存されたレスポンスは通常よりも素早く表示されるようになります。
キャッシュの振る舞いは、レスポンスで返されるステータスコードによって変わります。これもステータスコードが重要な「もうひとつの」理由です。
キャッシュの振る舞いを制御する主なヘッダーは、その名の通りCache-Control
ヘッダーと呼ばれます。いくつか例を見てみましょう。
(プロ向け: development環境でrails dev:cache
コマンドを実行するとキャッシュをオンオフできます)
カバは大きくてレンダリングが大変なので、カバのレンダリングは1回だけにして、恒久的にキャッシュしておきたいとしましょう。これは、コントローラでhttp_cache_forever
メソッドを使えば可能です。
http_cache_forever
メソッドは、Cache-Control
ヘッダーのmax-age
ディレクティブに3155695200秒(約31億秒)を設定します(これは1世紀に相当し、コンピュータ時間としては「基本的に」永遠です)。その他にprivate
ディレクティブも設定されます。これはブラウザやブラウザプロキシに「このカバはプライベートなデータなので、共有キャッシュではなく、このユーザーのブラウザだけにキャッシュすることが"望ましい"」と指示します。
このprivate
ディレクティブは、アカウントの所有者だけがレスポンスにアクセス可能にすることを意味しています。ブラウザはこのレスポンスをキャッシュしますが、CDN(Content Distribution Network)のようなサーバーとクライアントの間に置かれるキャッシュはこのレスポンスを保存すべきではありません。このような共有キャッシュにレスポンスをキャッシュしたい場合は、http_cache_forever
にpublic: true
を渡します。これにより、ブラウザに送信されるカバのレスポンスをキャッシュしてもよいということをブラウザプロキシに指示できます。
期限なしのキャッシュの別の例として、テンプレートにカバ画像を含めてみましょう。このページにアクセスしてHTMLソースの画像を調べてみると、画像は単なる/assets/hippo.png
ではなく/assets/hippo-なんちゃらかんちゃら.png
というファイル名で配信されていることがわかります。
Q: "一体これは何ですか?"
サーバーが画像を配信するときは、Cache-Control
ヘッダーにhttp_cache_forever
のときと同等の値を設定します。この場合、ブラウザ(またはCDNなどのブラウザプロキシ)はカバ画像を「永久に」キャッシュします。
しかしカバ画像を差し替えた場合はどうなるのでしょうか?ユーザーはネット上で最新のカバ画像にどうやってアクセスできるのでしょうか?
その答えが「フィンガープリント」です。先ほど画像ファイル名に追加された"なんちゃらかんちゃら"は実際には画像のフィンガープリントであり、Railsのアセットパイプラインが画像の内容に応じて画像をコンパイルするたびにフィンガープリントが新たに生成されます。画像が変更されると、HTML内でリンクされるフィンガープリントもがらりと変更され、ブラウザは以前キャッシュされていた古い画像ではなく新しい画像をサーバーから取得します。
レスポンスのキャッシュに話を戻します。
ところで、カバ画像を「永久に」キャッシュするのは方法としていかがなものでしょうか?カバの寿命は40年程度だそうですし、姿かたちは生きている間変わり続けるので、おそらくカバ画像のキャッシュ期間はせいぜい1時間程度にするのがよさそうです。
コントローラでexpires_in
メソッドを使うと、Cache-Control
ヘッダーのmax-age
ディレクティブにキャッシュ期間を指定できます。これで、1時間以内にブラウザでカバを再度読み込むとカバ画像がキャッシュから再読み込みされます。
Q: "でも、1時間以内にカバ画像が変更されていないことをどうやって知ればいいのでしょうか?"
これを保証するのは簡単ではありません。カバが更新されたかどうかをサーバーに問い合わせて、カバが変更されていないときだけキャッシュを使えればいいのですが。
さて、ここで皆さんに嬉しいお知らせです!これは、キャッシュに特化したコードを一切使わないデフォルトの振る舞いです。
Railsは、Cache-Control
にmust-revalidate
ディレクティブを追加します。このディレクティブは、「ブラウザは、キャッシュされたレスポンスを表示する前にキャッシュが有効かどうかを再検証すること」という意味です。また、Railsはmax-age
にゼロ秒を設定します。これは、キャッシュされたレスポンスは即座に古くなる(stale)という意味です。ブラウザはこの2つのディレクティブによって、キャッシュされたレスポンスを表示する前に常にキャッシュを再検証するようになります。
Q: "この'再検証'ってどんな仕組みですか?"
/find-hippo
エンドポイントに初めてアクセスすると、Railsはレスポンスbodyを生成するコードを実行し、カバの探索とレンダリングに関連する作業もすべて行います。Railsがbodyをサーバーに渡す前に、Rack::ETag
というRackミドルウェアがレスポンスbodyを"ダイジェスト化"して一意の"エンティティタグ"を生成します。このエンティティタグは、上述したアセットのフィンガープリントと似ています。
# Rack::ETagをシンプルにしたもの
module Rack
class ETag
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if status == 200
digest = digest_body(body)
headers[Etag] = %(W/"#{digest}")
end
[status, headers, body]
end
private
#...
end
end
するとRack::Etag
は、このエンティティタグを持つETag
レスポンスヘッダを以下のように設定します。
Cache-Control: max-age=0, private, must-revalidate
ETag: W/"48a7e47309e0ec54e32df3a272094025"
ブラウザはこのレスポンスをヘッダーごとキャッシュします。ブラウザがこのページに再度アクセスすると、キャッシュされたレスポンスがstaleしている(max-age=0
)ことに気づき、レスポンスの"再検証"を要求されていることを認識します。そこでブラウザは、If-None-Match
リクエストヘッダーを含むGET
リクエストをサーバーに送信します。このIf-None-Match
リクエストヘッダーには、先ほどキャッシュされたレスポンスに関連付けられているエンティティタグを含めています。
GET /find_hippo HTTP/1.1
Host: skylight.io
If-None-Match: W/"48a7e47309e0ec54e32df3a272094025"
サーバーは、レスポンスbodyを生成するコードを再び実行し(カバの探索とレンダリングに関連する作業もすべて行います)、bodyを再びRack::ETag
に渡します。Rack::ETag
はレスポンスbodyを再度ダイジェスト化して一意のエンティティタグを生成し、それをEtag
レスポンスヘッダーに設定します。
すると、次に控えているRack::ConditionalGet
ミドルウェアが、新しいETag
ヘッダーがIf-None-Match
リクエストヘッダーのエンティティタグと一致するかどうかをチェックします。
# Rack::ConditionalGetをシンプルにしたもの
module Rack
class ConditionalGet
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
if status == 200 && etag_matches?
status = 304
body = []
end
[status, headers, body]
end
private
def etag_matches?
headers['ETag'] == env['HTTP_IF_NONE_MATCH']
end
end
end
新しいEtag
が一致する場合は、Rack::ConditionalGet
がステータスコードを304 Not Modified
に変更してbodyを捨てます。これでブラウザは余分なbodyをダウンロードするまで待つ必要もなくなり、ステータス304でキャッシュ済みレスポンスを使うようブラウザに指示します。
新しいEtag
が一致しない場合、サーバーは元のステータスコードのまま完全なレスポンスを送り返します。これでブラウザは新しいカバをレンダリングするようになります。
この挙動は、サーバーがETag
を生成して比較するだけのためにカバ全体をレンダリングするという力仕事を引き続き行っているように思われます。レスポンスbodyが変更される理由が、カバそのものが変更されたという理由しかないとわかっていれば、もっといい方法があるはずです。
次はstale?
メソッドです。
これで、このアクションはカバがstaleしている場合にのみカバをrender
します。これは引き続きデフォルトのアクションと同様に同じキャッシュヘッダーを取得しますが、レスポンスbodyが完全に同一であってもEtag
は変化します。一体何が変わったのでしょうか?
このstale?
メソッドは、ETag
をビルドするためにわざわざレスポンスbody全体をレンダリングしないようRailsに指示します。代わりに、カバ自身が変更されたかどうかだけをチェックし、それに基づいてETag
をビルドします。このときRailsの背後では、「モデル名」「id
」「updated_at
」を組み合わせた文字列(この場合"hippo/1-20071224150000"
)だけを生成してから、ETag
のダイジェストアルゴリズムを通じて実行します。これによって、サーバーはETag
を生成するためにbody全体をレンダリングするという手間を省けます。
最後に、「このカバはプライベートなデータなので決してキャッシュしたくない」場合はどうすればいいでしょうか?Railsにはそのための組み込みメソッドがまだないので、自分でCache-Control
ヘッダーを直接設定する必要があります。
このno-store
ディレクティブは、「このレスポンスは、ブラウザやプロキシなど、いかなるキャッシュにも保存してはならない」という意味です。
なお、名前のひどさで有名なno-cache
ディレクティブと混同しないようご注意ください。no-cache
ディレクティブはその名に反して、レスポンスは任意のキャッシュに保存可能であるにもかかわらず、保存されたレスポンスを使う前には毎回必ず再検証しなければなりません。
これで、ステータスコードやヘッダーを用いてブラウザがレスポンスに対して何を行うかを指示できるようになったので、そろそろレスポンスで最も重要な部分であるbodyについて話す必要があるでしょう。
レスポンスbody
レスポンス配列の最後にあるのがbodyです。bodyは、ユーザーがリクエストした実際の情報を文字列形式で表現したものです。
Railsで/find-hippo
パスにリクエストを送信すると、コントローラやビューに書かれたコードはどのようにしてカバのHTMLページに変換されるのでしょうか?調べてみましょう!
コンテンツのネゴシエーション
ブラウザで"skylight.io/find-hippo"にアクセスすると、RailsアプリはHTMLレスポンスを返します。これは、レスポンスのContent-Type
ヘッダーを見ることで確かめられます。
Q: "RailsはこれがHTMLであることをどうやって知るのでしょうか?"
Railsは最初に、リクエストの明示的なファイル拡張子をチェックします("skylight.io/find-hippo.html"など)。"skylight.io/find-hippo"リクエストにファイル拡張子が含まれていない場合は、リクエストのAccept
ヘッダーをチェックします。
Safariブラウザはデフォルトで、リクエストのAccept
ヘッダーをtext/html
として扱います。これは、レスポンスのContent-Type
("MIMEタイプ"としてフォーマットされる)としてHTMLを受け取ることを期待していることを示します。HTMLバージョンがない場合、SafariはXMLバージョンを受け付けます。
コントローラ内のrender
メソッドは、リクエストされたContent-Type
に一致する拡張子を持つテンプレート(ここではsafari/hippo.html.erb
)を探索します。また、レンダリングされるbodyに一致するContent-Type
ヘッダーも設定します。
JSON版のカバも欲しくなったので、/find-hippo.json
にリクエストを送信してみましょう。
おっと、JSONカバ用のテンプレートがありませんでした。テンプレートを追加するか、respond_to
ブロックで別のフォーマットを扱うようにすれば表示できます。
これで、/find-hippo.json
にリクエストを送信すればJSONカバを取得できます。
興味深いことに、ブラウザはContent-Type
ヘッダーの指示に絶対従わないといけないというものではなく、ファイルの中身を「嗅ぎ分けて」ファイル種別を勝手に判定しようとする可能性もあります。そのため、RailsではX-Content-Type-Options: nosniff
を指定して、この挙動をブラウザで禁止しています。
テンプレートのレンダリング
Railsコントローラでは3とおりの方法でレスポンスを生成できます。3つのうち、コントローラのredirect_to
メソッドとhead
メソッドを用いて、bodyのみ空でステータスコードとヘッダーを持つレスポンスを生成する方法については既に解説しました。bodyを含む完全なレスポンスを生成できるコントローラメソッドはrender
だけです。
先ほどの/find-hippo
の例で、テンプレートが以下のようになっているとしましょう。
"skylight.io/find-hippo"にアクセスしてrender :hippo
を呼び出すと、render
メソッドは適切なテンプレートを探索し、すべての空欄をインスタンス変数で埋め、レスポンスbodyを生成してブラウザに送り返します。
Railsではこれを行うために、コントローラごとに固有の「ビューコンテキスト」クラスを生成します。以下は、Safariコントローラのビューコンテキストクラスを簡略化した例です。
class SafariControllerViewContext < ActionView::Base
include Rails::AllTheHelpers
# link_toなど
include MyApp::AllTheHelpers
# current_userなど
def initialize(assigns)
assigns.each { |k, v| instance_variable_set("@#{k}", v) }
end
private
# Hey <%= current_user.name %>, meet <%= link_to @hippo.name, @hippo %>!
def __compiled_app_templates_hippo_erb
output = ""
output << "Hey "
output << html_escape(current_user.name)
output << ", meet"
output << link_to(html_escape(@hippo.name), @hippo)
output << "!"
output
end
end
原注
実際のコードを見たい場合は、以下をご覧ください。
ビューコンテキストが初期化されると、Railsはコントローラ内で設定したすべてのインスタンス変数(ここでは@hippo
)をループで回してビューコンテキストオブジェクトにコピーし、テンプレート内でインスタンス変数を使えるようにします。これらのインスタンス変数はassigns
として知られています。
ビューコンテキストクラスは、Action Viewで利用できるすべてのヘルパー(link_to
など)と、アプリで定義されているすべてのヘルパー(current_user
など)をincludes
します。
各テンプレートは、そのビューコンテキストクラス上のインスタンス変数にコンパイルされます。各テンプレートのメソッドは、基本的に以下のように文字列結合を強力にしたものです。
- 最初に"Hey "という文字列を出力する
self.current_user
の結果を取得する- このメソッドを利用できるのは、アプリのすべてのヘルパーがビューコンテキストクラスにインクルードされるため
current_user.name
をエスケープする- ユーザー入力が使われる可能性があるため
- これらの結果を出力文字列に追加する
- ", meet"を追加する
- ビューコンテキスト初期化時に設定した
@hippo
インスタンス変数を取得する self.link_to
でカバのページへのリンクを生成する- このメソッドを利用できるのは、Action Viewのすべてのヘルパーがモジュールとしてインクルードされるため
- "!"を出力文字列に追加する
- 出力文字列を返す
以上をまとめた結果を以下に示します。
ついに珍種のカバPhyllisくんを見つけました!ステータスは200 OK
です。
ここにたどり着くまでに、世にも珍しいアクション、想像を絶するスケール、信じられない秘境をいろいろ巡り、とうとうRailsの最深部に潜り込んで奥義を会得しました。偉大なるtext/plainを渡り歩き、壮大なAction Viewを探検し、ついにブラウザまで生還したのです。
「Railsレスポンスの素晴らしいライフサイクル」発掘ツアーにお付き合いいただき、ありがとうございました。
関連記事
- このURLは現在は無効です。 ↩
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
本記事は以下の続編です。