scaffoldでは、Controller、Helper、View(RHTML)が生成される。
Controller
itemモデルから生成した、ItemsController。ApplicationControllerを継承。Railsでは、URIに対応したメソッドが呼ばれる、ということは知っている。たとえば…
indexメソッド
コントローラー名のみを指定された場合(items/)に呼び出される。一応、items/index でも呼び出された。
処理内容はというと、listメソッドとrenderメソッドを順番に呼んでいるだけ。listは後で片づけるとして、renderが何をやっているのかがよくわからないので調べてみる。メソッドが定義されているActionController::Baseのコメントを読んでみると、どうやら :action => 'xxx' で指定したactionに対応したテンプレートを使ってレスポンスをレンダリングするらしい。今回の例では、list.rhtmlを使ってレンダリングする。
listメソッドを呼んだら、勝手にlistのテンプレートを使ってレンダリングされるようになってるのかも、と漫然と考えていたが、どうもそうではないらしい。基本的には呼び出されたアクションに対応したテンプレートが使われ、を変更するためにはrenderメソッドを使った明示的な切り替えが必要、ということらしい。
indexメソッドでrenderメソッドを呼び出さなければ、index.rhtmlが使われる、というわけだ。generate scaffoldはindex.rhtmlを生成しないので、動かないが。
verifyメソッド呼び出し
このverifyメソッド、ActionController::Verification::ClassMethodsにあって、リクエストヘッダレベルの検証をやっているらしい。この例では、destroy, create, updateメソッドはPOSTで呼び出されないといけないらしい。GETで呼び出された場合には、listアクションにリダイレクトされる。
やっていることはわかったが、なぜ呼び出せるのかがわからない。コメントには「すべてのControllerのクラスメソッドになる」と書いてあるので、どこかでevalしてるんだろうけど、いつ、どこでやってるのか現時点ではわからない。このあたりは、別途Rails起動時のシーケンスを追う中で明らかにしたい。
listメソッド
paginateメソッドを呼び出し、インスタンス変数itemsとitem_pagesをセットする。戻り値が配列になっているため、微妙にわかりづらい。paginateメソッドもverifyメソッドと同じく、すべてのControllerのクラスメソッドになっている。
paginateメソッドは、指定されたモデルの今回のページ分のコレクションとPaginatorオブジェクトを返している。Paginatorオブジェクトは、ページング用のプロパティを持っている。
showメソッド
モデルのfindクラスメソッドにidを渡してモデルオブジェクトを一件取得し、インスタンス変数に保存するだけのシンプルな処理。「show/x」みたいな形で渡されたものがidとみなされるのは、特別扱いだろうか。このあたりも、正常形のシーケンスを追う中で確かめたい。
newメソッド
モデルオブジェクトのインスタンスを新規作成し、インスタンス変数に保存して終了。
createメソッド
渡されたパラメータをもとにItemオブジェクトを新規作成して、保存。保存に成功したらリダイレクトでlistアクションを呼び出す。失敗したら、newアクションのテンプレートを呼び出す。ここでちょっと驚いたのが、何もしていないのにリクエストパラメータが「params[:item]」という風にまとめられていること。どういうルールでまとまっているのかわからなかったので、createアクションを呼び出すHTMLをみてみた。
name属性値がすべて、item[xxxxxx]という形になっている。railsはおそらく、こういう形でかかれたリクエストをモデルの属性として扱う仕様なのだろう。
editメソッド
IDをもとにモデルオブジェクトを一件検索し、インスタンス変数に保存して終了。showと同じだ。
updateメソッド
idをもとにモデルオブジェクトを取得し、リクエストパラメータで更新。更新したモデルオブジェクトの保存が成功すれば、リダイレクトでshowアクションを呼び出す。その際、idとしてモデルのインスタンス変数を渡している。保存が失敗した場合は、editアクションのテンプレートを呼び出す。
ここで目をひかれたのは、:idに@itemを入れてるところ。redirect_toは、文字通りリダイレクトするはずなので、次のアクションにオンメモリでItemオブジェクトを渡すことはできない。こうやって渡されたオブジェクトは、次のアクションにどういう形で引き渡されるんだろうか?そもそも、showで必要なのはItemオブジェクト全体じゃなく、idだけのはずだけど。
destroyメソッド
idをもとにモデルオブジェクトを取得し、destroyで破棄する。その後、リダイレクトでlistアクションを呼び出す。
Helper
空。空です。まわりの説明をみていると、どうやらこのクラスはViewHelperの置き場所として用意されているようで、Viewから呼びたいメソッドを定義したくなったらここに書くらしい。
rhtml
rhtmlで生成されたものは、以下の6つ。
items.rhtml
すべてのテンプレートを内部にレンダリングする、親テンプレート。
中身以外の部分が全部書いてある。中身はyieldで差し込まれるようだ。あとは、Controllerで何回か出てきた「flash[:notice]」がある。Controllerでは、保存が成功した際にメッセージを格納していたが、ここに出力されるわけだ。
list.rhtml
listアクションで使われるテンプレート。
まず、この部分でヘッダを作っている。
モデルクラスには、content_columnsなんてものがあるらしい。そして、columnにはfuman_nameという、おそらくは表示用の名前が格納されているようだ。このあたりはActiveRecordをみていく中で確認したい。
次に、テーブル本体。
@itemsを順番に取り出した後に、content_columnごとにitemオブジェクトの中身を表示している模様。ここらあたりも、sendメソッドの仕様を確認しないと細かいところはわからない。後で確認する。
最後には、link_toというメソッドで各actionを呼び出すリンクを作成している。
ラスト、ページング。
前ページと次ページのリンクを作っているだけだが、pageパラメータとしてPaginatorオブジェクトのcurrent.previousやcurrent.nextを渡している。Paginate処理はControllerの開始時にフィルタとして処理されており、pageパラメータはPaginatorオブジェクトを復元する際に使われている。
ちなみに、previousやnextプロパティは、それぞれ前ページ・次ページが存在しない場合には存在しないため、リンクも表示されなくなる。
link_toにはactionもURLも書かれていないが、listアクションが呼び出される。action_view -> url_helper.rbのActionView::Helpers::UrlHelperのlink_toをみてみると、実際の変換はActionController::Baseのurl_forがやっているらしい。こちらのコメントによると、actionもurlも指定されていない場合は今と同じURLが使われるらしい。
show.rhtml
モデルのカラムごとに中身を表示している。listとほぼ同じ。
new.rhtml
form_tagメソッドは、ActionView::Helpers::FormTagHelperで定義されていて、その名のとおりformタグを定義するためのメソッド。
render :partialは、最終的にはActionView::Partials::render_partialが呼ばれており、このメソッドは :partialオプションの値の先頭に「_(アンダースコア)」をつけたテンプレート(rhtml)を使う仕様になっている。
_form.rhtml
new.rhtmlからrender :partial で呼び出されていたテンプレート。
いくつか知らないメソッドがあるが、みてわかる通りな感じ。text_fieldはテキストフィールドを作成するのだろう。引数の書き方は見慣れない形だが、前から順番にプロパティがたどられるようだ。
edit.rhtml
new.rhtmlとほとんど同じだ。違うのは、formタグのアクションが「update」んあっており、idとしてitemインスタンスが指定されている点。パラメータにモデルインスタンスを指定すると、HTML上ではいったいどうなるのだろう?実際に表示させてHTMLファイルをみてみると…
なるほど、という感じだ。リクエストURIとして出力されている。
というわけで、scaffoldが生成した処理はほぼ把握した。
次のステップ
次は、リクエスト発生からレスポンス作成までの流れを一通り追い、Railsの正常形の流れをつかみたい。
Controller
itemモデルから生成した、ItemsController。ApplicationControllerを継承。Railsでは、URIに対応したメソッドが呼ばれる、ということは知っている。たとえば…
- items/list -> ItemsControllerのlistメソッドが呼ばれる
- hoge/fuga -> HogeControllerのfugaメソッドが呼ばれる
- index
- list
- show
- new
- create
- edit
- update
- destroy
indexメソッド
def index
list
render :action => 'list'
end
コントローラー名のみを指定された場合(items/)に呼び出される。一応、items/index でも呼び出された。
処理内容はというと、listメソッドとrenderメソッドを順番に呼んでいるだけ。listは後で片づけるとして、renderが何をやっているのかがよくわからないので調べてみる。メソッドが定義されているActionController::Baseのコメントを読んでみると、どうやら :action => 'xxx' で指定したactionに対応したテンプレートを使ってレスポンスをレンダリングするらしい。今回の例では、list.rhtmlを使ってレンダリングする。
listメソッドを呼んだら、勝手にlistのテンプレートを使ってレンダリングされるようになってるのかも、と漫然と考えていたが、どうもそうではないらしい。基本的には呼び出されたアクションに対応したテンプレートが使われ、を変更するためにはrenderメソッドを使った明示的な切り替えが必要、ということらしい。
indexメソッドでrenderメソッドを呼び出さなければ、index.rhtmlが使われる、というわけだ。generate scaffoldはindex.rhtmlを生成しないので、動かないが。
verifyメソッド呼び出し
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }
このverifyメソッド、ActionController::Verification::ClassMethodsにあって、リクエストヘッダレベルの検証をやっているらしい。この例では、destroy, create, updateメソッドはPOSTで呼び出されないといけないらしい。GETで呼び出された場合には、listアクションにリダイレクトされる。
やっていることはわかったが、なぜ呼び出せるのかがわからない。コメントには「すべてのControllerのクラスメソッドになる」と書いてあるので、どこかでevalしてるんだろうけど、いつ、どこでやってるのか現時点ではわからない。このあたりは、別途Rails起動時のシーケンスを追う中で明らかにしたい。
listメソッド
def list
@item_pages, @items = paginate :items, :per_page => 10
end
paginateメソッドを呼び出し、インスタンス変数itemsとitem_pagesをセットする。戻り値が配列になっているため、微妙にわかりづらい。paginateメソッドもverifyメソッドと同じく、すべてのControllerのクラスメソッドになっている。
paginateメソッドは、指定されたモデルの今回のページ分のコレクションとPaginatorオブジェクトを返している。Paginatorオブジェクトは、ページング用のプロパティを持っている。
showメソッド
def show
@item = Item.find(params[:id])
end
モデルのfindクラスメソッドにidを渡してモデルオブジェクトを一件取得し、インスタンス変数に保存するだけのシンプルな処理。「show/x」みたいな形で渡されたものがidとみなされるのは、特別扱いだろうか。このあたりも、正常形のシーケンスを追う中で確かめたい。
newメソッド
def new
@item = Item.new
end
モデルオブジェクトのインスタンスを新規作成し、インスタンス変数に保存して終了。
createメソッド
def create
@item = Item.new(params[:item])
if @item.save
flash[:notice] = 'Item was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
渡されたパラメータをもとにItemオブジェクトを新規作成して、保存。保存に成功したらリダイレクトでlistアクションを呼び出す。失敗したら、newアクションのテンプレートを呼び出す。ここでちょっと驚いたのが、何もしていないのにリクエストパラメータが「params[:item]」という風にまとめられていること。どういうルールでまとまっているのかわからなかったので、createアクションを呼び出すHTMLをみてみた。
<p><label for="item_url">Url</label>
<input id="item_url" name="item[url]" size="30" value="" type="text"></p>
<p><label for="item_title">Title</label>
<input id="item_title" name="item[title]" size="30" value="" type="text"></p>
<p><label for="item_description">Description</label>
<textarea cols="40" id="item_description" name="item[description]" rows="20"></textarea></p>
name属性値がすべて、item[xxxxxx]という形になっている。railsはおそらく、こういう形でかかれたリクエストをモデルの属性として扱う仕様なのだろう。
editメソッド
def edit
@item = Item.find(params[:id])
end
IDをもとにモデルオブジェクトを一件検索し、インスタンス変数に保存して終了。showと同じだ。
updateメソッド
def update
@item = Item.find(params[:id])
if @item.update_attributes(params[:item])
flash[:notice] = 'Item was successfully updated.'
redirect_to :action => 'show', :id => @item
else
render :action => 'edit'
end
end
idをもとにモデルオブジェクトを取得し、リクエストパラメータで更新。更新したモデルオブジェクトの保存が成功すれば、リダイレクトでshowアクションを呼び出す。その際、idとしてモデルのインスタンス変数を渡している。保存が失敗した場合は、editアクションのテンプレートを呼び出す。
ここで目をひかれたのは、:idに@itemを入れてるところ。redirect_toは、文字通りリダイレクトするはずなので、次のアクションにオンメモリでItemオブジェクトを渡すことはできない。こうやって渡されたオブジェクトは、次のアクションにどういう形で引き渡されるんだろうか?そもそも、showで必要なのはItemオブジェクト全体じゃなく、idだけのはずだけど。
destroyメソッド
def destroy
Item.find(params[:id]).destroy
redirect_to :action => 'list'
end
idをもとにモデルオブジェクトを取得し、destroyで破棄する。その後、リダイレクトでlistアクションを呼び出す。
Helper
空。空です。まわりの説明をみていると、どうやらこのクラスはViewHelperの置き場所として用意されているようで、Viewから呼びたいメソッドを定義したくなったらここに書くらしい。
rhtml
rhtmlで生成されたものは、以下の6つ。
- items.rhtml
- list.rhtml
- show.rhtml
- new.rhtml
- _form.rhtml
- edit.rhtml
items.rhtml
すべてのテンプレートを内部にレンダリングする、親テンプレート。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" lang="en">
<head>
<meta equiv="content-type" content="text/html;charset=UTF-8">
<title>Items: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
<p style="color: green"><%= flash[:notice] %></p>
<%= yield %>
</body>
</html>
中身以外の部分が全部書いてある。中身はyieldで差し込まれるようだ。あとは、Controllerで何回か出てきた「flash[:notice]」がある。Controllerでは、保存が成功した際にメッセージを格納していたが、ここに出力されるわけだ。
list.rhtml
listアクションで使われるテンプレート。
<h1>Listing items</h1>
<table>
<tbody><tr>
<% for column in Item.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>
<% for item in @items %>
<tr>
<% for column in Item.content_columns %>
<td><%=h item.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => item %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => item %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => item }, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</tbody></table>
<%= link_to 'Previous page', { :page => @item_pages.current.previous } if @item_pages.current.previous %>
<%= link_to 'Next page', { :page => @item_pages.current.next } if @item_pages.current.next %>
<%= link_to 'New item', :action => 'new' %>
まず、この部分でヘッダを作っている。
<% for column in Item.content_columns %>
<%= column.human_name %>
<% end %>
モデルクラスには、content_columnsなんてものがあるらしい。そして、columnにはfuman_nameという、おそらくは表示用の名前が格納されているようだ。このあたりはActiveRecordをみていく中で確認したい。
次に、テーブル本体。
<% for item in @items %>
<% for column in Item.content_columns %>
<%=h item.send(column.name) %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => item %>
<%= link_to 'Edit', :action => 'edit', :id => item %>
<%= link_to 'Destroy', { :action => 'destroy', :id => item }, :confirm => 'Are you sure?', :method => :post %>
<% end %>
@itemsを順番に取り出した後に、content_columnごとにitemオブジェクトの中身を表示している模様。ここらあたりも、sendメソッドの仕様を確認しないと細かいところはわからない。後で確認する。
最後には、link_toというメソッドで各actionを呼び出すリンクを作成している。
ラスト、ページング。
<%= link_to 'Previous page', { :page => @item_pages.current.previous } if @item_pages.current.previous %>
<%= link_to 'Next page', { :page => @item_pages.current.next } if @item_pages.current.next %>
前ページと次ページのリンクを作っているだけだが、pageパラメータとしてPaginatorオブジェクトのcurrent.previousやcurrent.nextを渡している。Paginate処理はControllerの開始時にフィルタとして処理されており、pageパラメータはPaginatorオブジェクトを復元する際に使われている。
ちなみに、previousやnextプロパティは、それぞれ前ページ・次ページが存在しない場合には存在しないため、リンクも表示されなくなる。
link_toにはactionもURLも書かれていないが、listアクションが呼び出される。action_view -> url_helper.rbのActionView::Helpers::UrlHelperのlink_toをみてみると、実際の変換はActionController::Baseのurl_forがやっているらしい。こちらのコメントによると、actionもurlも指定されていない場合は今と同じURLが使われるらしい。
show.rhtml
<% for column in Item.content_columns %>
<p>
<b><%= column.human_name %>:</b> <%=h @item.send(column.name) %>
</p>
<% end %>
<%= link_to 'Edit', :action => 'edit', :id => @item %> |
<%= link_to 'Back', :action => 'list' %>
モデルのカラムごとに中身を表示している。listとほぼ同じ。
new.rhtml
<h1>New item</h1>
<% form_tag :action => 'create' do %>
<%= render :partial => 'form' %>
<%= submit_tag "Create" %>
<% end %>
<%= link_to 'Back', :action => 'list' %>
form_tagメソッドは、ActionView::Helpers::FormTagHelperで定義されていて、その名のとおりformタグを定義するためのメソッド。
render :partialは、最終的にはActionView::Partials::render_partialが呼ばれており、このメソッドは :partialオプションの値の先頭に「_(アンダースコア)」をつけたテンプレート(rhtml)を使う仕様になっている。
_form.rhtml
new.rhtmlからrender :partial で呼び出されていたテンプレート。
<%= error_messages_for 'item' %>
<!--[form:item]-->
<p><label for="item_url">Url</label>
<%= text_field 'item', 'url' %></p>
<p><label for="item_title">Title</label>
<%= text_field 'item', 'title' %></p>
<p><label for="item_description">Description</label>
<%= text_area 'item', 'description' %></p>
<!--[eoform:item]-->
いくつか知らないメソッドがあるが、みてわかる通りな感じ。text_fieldはテキストフィールドを作成するのだろう。引数の書き方は見慣れない形だが、前から順番にプロパティがたどられるようだ。
edit.rhtml
<h1>Editing item</h1>
<% form_tag :action => 'update', :id => @item do %>
<%= render :partial => 'form' %>
<%= submit_tag 'Edit' %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => @item %> |
<%= link_to 'Back', :action => 'list' %>
new.rhtmlとほとんど同じだ。違うのは、formタグのアクションが「update」んあっており、idとしてitemインスタンスが指定されている点。パラメータにモデルインスタンスを指定すると、HTML上ではいったいどうなるのだろう?実際に表示させてHTMLファイルをみてみると…
<form action="/items/update/1" method="post">
なるほど、という感じだ。リクエストURIとして出力されている。
というわけで、scaffoldが生成した処理はほぼ把握した。
次のステップ
次は、リクエスト発生からレスポンス作成までの流れを一通り追い、Railsの正常形の流れをつかみたい。
コメント