2006-10-03 (火)
■ Rails に(再)挑戦 2日目 - scaffold の修正
昨日は scaffold (足場) を作るところまでだった。 今日は scaffold をベースにして、手を加えていく。 とりあえず、今作られているファイルを確認する。
app/models/page.rb app/controllers/application.rb app/controllers/note_controller.rb app/views/note/_form.rhtml app/views/note/edit.rhtml app/views/note/list.rhtml app/views/note/new.rhtml app/views/note/show.rhtml app/views/layouts/note.rhtml app/helpers/application_helper.rb app/helpers/note_helper.rb
基本的に、 model, view, controller に分かれている。 note_controller.rb で定義されている、 NoteController のメソッド (action) に対して、それぞれ view が用意されている。 まずは、最初に表示される一覧表示 (list) の画面を修正しよう。 scaffold で作られたファイルの中を見てみる。まずはコントローラから。
$ vi app/controllers/note_controller.rb
class NoteController < ApplicationController
def list
@page_pages, @pages = paginate :pages, :per_page => 10
end
end
note_controller.rb には他のメソッド (action) も定義されていたけど、とりあえず list しか気にしない。 paginate という仕組みを使っているので少し複雑に見えるけど、
@pages = Page.find(:all)
と同じようなものと考えればよさそう。 paginate については後で調べることにして、先に進む。
$ vi app/views/note/list.rhtml
<% for page in @pages %>
<tr>
<% for column in Page.content_columns %>
<td><%=h page.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => page %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => page %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => page }, :confirm => 'Are you sure?', :post => true %></td>
</tr>
<% end %>
<%= link_to 'Previous page', { :page => @page_pages.current.previous } if @page_pages.current.previous %>
<%= link_to 'Next page', { :page => @page_pages.current.next } if @page_pages.current.next %>
ビューでは、 Page モデルの全ての属性を動的に出力するようになっている (page.send(column.name)) 。 また、最後の二行はやっぱり pagenate 絡みなので、今は気にしないことにする。
このビューを以下のように書き換える。 サンプルなので、スタイルシートもそのまま埋め込んでいる。
$ vi app/views/note/list.rhtml
<%= link_to 'create', :action => 'new' %>
<% for page in @pages %>
<div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
<div style="font-size: 80%; background-color: #fcc">
<%= link_to 'Show', :action => 'show', :id => page %>
<%= link_to 'Edit', :action => 'edit', :id => page %>
<%= link_to 'Destroy', { :action => 'destroy', :id => page }, :confirm => 'Are you sure?', :post => true %>
</div>
<%=h page.body %>
</div>
<% end %>
これだけでも、少しはそれっぽく見えてきたかな。
■ タイトルをつける
RandomNote は、テキストの1行目がタイトルになっている。 そこで、さっきのビューにもタイトルを付けてみる。 タイトルをクリックするとそのページだけを表示する (show アクションを呼び出す) ようにした。 タイトルが長いと大変なことになるので、 truncate メソッドを使って 20 文字に切り取ることにした。
<% for page in @pages %>
<div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
<h2 style="font-size: 110%; margin: 0.3em">
<%= link_to truncate(page.title, 20), :action => 'show', :id => page %>
</h2>
<div style="font-size: 80%; background-color: #fcc">
<%= link_to 'Edit', :action => 'edit', :id => page %>
<%= link_to 'Destroy', { :action => 'destroy', :id => page }, :confirm => 'Are you sure?', :post => true %>
</div>
<%=h page.body %>
</div>
<% end %>
実行すると、当然のように
undefined method `title' for #<Page:0x409a50d4>
というエラーが表示される。 page.title というメソッドを作った覚えは無いからね。 そこで、1行目の文字列を返す title メソッドをPage モデルに追加する。
$ vi app/models/page.rb
class Page < ActiveRecord::Base
def title
body.each {|title| return title.chomp }
end
end
…もう少し綺麗に書く方法がありそうだなぁ。 とにかく、これでタイトルが表示されるようになった。
でも、よく見たら文字の切れ目で文字化けしている orz
■ $KCODE の指定と落とし穴(UTF-8 対応)
文字化け対策として、 Rails に対して $KCODE を使って文字コードを指定してあげればいいみたい。 ($KCODE を指定しないと文字をシングルバイトとして扱って、 truncate で問題がでてる)
環境ファイル (config/environment.rb) に $KCODE の指定を追加する。
$ vi config/environment.rb # Be sure to restart your web server when you modify this file. $KCODE = 'utf8'
これで解消…と思いきや、何故か $KCODE を指定しても前と変わらない。 コンソールを起動して、 $KCODE を確認しても UTF-8 になっているのに。
$ script/console >> $KCODE => "UTF8"
散々悩んだ挙句、ビューにデバッグを埋め込んでみた。
<%= debug($KCODE) %>
すると、ブラウザからアクセスすると
--- EUC
と表示される。 NONE ならともかく、 EUC とは…。
結局、 ホームディレクトリにある .irbrc に $KCODE の指定をしていたことが原因だった。
$ vi ~/.irbrc $KCODE = 'euc'
なぜ Rails がこんなファイルを見ているのか謎だ…。 とりあえず、この指定を削除すると、 trunscate での文字化けも解消された。 でも謎だ…。
■ デザインを共有する
一覧表示 (list) は少し形になってきたけど、タイトルをクリックして表示される個別表示 (show) は scaffold のままになってる。 個別表示のビュー (show.rhtml) も同じように書き換えればいいんだけど、それだと似たようなソースが list.rhtml と show.rhtml の両方にできてしまう。
そこで、 render(:partial) という仕組みを使って、2つのビューでデザインを共有するようにした。 まず、 list.rhtml に書いていたページ描画部分(for 文の内側)を _page.rhtml に移す。 (パーツとして使われるビューは、アンダースコアから始める規約になっている)
$ vi app/views/note/_page.rhtml
<div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
<h2 style="font-size: 110%; margin: 0.3em">
<%= link_to truncate(page.title, 20), :action => 'show', :id => page %>
</h2>
<div style="font-size: 80%; background-color: #fcc">
<%= link_to 'Edit', :action => 'edit', :id => page %>
<%= link_to 'Destroy', { :action => 'destroy', :id => page }, :confirm => 'Are you sure?', :post => true %>
</div>
<%=h page.body %>
</div>
list.rhtml と show.rhtml から、 _page.rhtml を参照する。 従来の for 文の代わりに、 :collection を使って繰り返しを実現している。 (:collection じゃなくて for 文を使っても問題は無い)
$ vi app/views/note/list.rhtml <%= link_to 'create', :action => 'new' %> <%= render(:partial => 'page', :collection => @pages) %>
$ vi app/views/note/show.rhtml <%= link_to 'create', :action => 'new' %> <%= render(:partial => 'page', :collection => @pages) %> <%= link_to 'Back', :action => 'list' %>
これで、一覧表示と個別表示の両方で、同じデザインを共有できた。 ただ、本当は完全に同じだとダメで、一覧表示の方は本文の一部しか表示しないようにしなきゃいけない。 これは明日の課題かな。



