「RailsによるアジャイルWebアプリケーション開発」22章読了
Action Viewなお話し。
Builderライブラリを使ってHTMLを構築する(ほぼ意味なし)
products_controller.rbに以下のようなアクションを追加する。
def builder_test @products = Product.find(:all) end
で、app/views/products/builder_test.rxmlを作成する
xml.html do xml.head do xml.title("Products") end xml.body do xml.table(:border => "1") do @products.each { |product| xml.tr do xml.td(product.title) xml.td(product.image_url) end } end end end
でもって、
http://localhost:3000/products/builder_test/1
へアクセス。
一応それらしくは表示される。
が、しかし、HTMLのソースを表示してみると、
<!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" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Products: builder_test</title> <link href="/stylesheets/scaffold.css?1215862242" media="screen" rel="stylesheet" type="text/css" /> </head> <body> <p style="color: green"></p> <html> <head> <title>Products</title> </head> <body> <table border="1"> <tr> <td>testtetes</td> <td>test</td> </tr> <tr> <td>てst</td> <td>slkdfjasdf.png</td> </tr> </table> </body> </html> </body> </html>
htmlタグが二回出力されちゃってます。。
これは、テンプレートとレイアウトの両方でHTMLタグを書いてしまっているためですね。
すぐに思いつくのは、テンプレート側でレイアウトに書かれたもの以外を出力するという方法ですが、ここではレイアウトを使わない方法を考えたいと思います。
で、探したら、この章の最後の方にありました。
renderをデフォルトの挙動にまかせるのではなく、明示的にレイアウトなしで呼びます。
def builder_test @products = Product.find(:all) render(:layout => false) end
でもって、
http://localhost:3000/products/builder_test/1
へアクセス。
すると、ブラウザにXMLとして認識されてしまいました。
Content-Typeがapplication/xmlになっているのが原因のようです。
ので、Content-Typeをtext/htmlにしてみます。
def builder_test @products = Product.find(:all) headers["Content-Type"] = "text/html" render(:layout => false) end
これで出来ました。
ただ、こうしてしまうと、
http://localhost:3000/products/builder_test/1.xml
へアクセスしても、htmlが表示されてしまいますね。
ので、scaffoldされたproducts_controller.rbを参考にすると、
def builder_test @products = Product.find(:all) respond_to do |format| format.html { headers["Content-Type"] = "text/html" render(:layout => false) } format.xml # builder_test.rxml end end
のようにするのが正しいように思えます。
こうすると、
/builder_test/1 → レイアウトなしのHTML
/builder_test/1.html → レイアウトなしのHTML(同上)
/builder_test/1.xml → XML
となり、期待通りになっているようです。
でもまあ、素直にerb使えよって感じです。
ポップアップウインドウ内にレスポンスを表示する
index.html.erbに以下の行を追加する。
<tr> <td><%=h product.title %></td> <td><%=h product.description %></td> <td><%=h product.image_url %></td> <td><%= link_to 'Show', product %></td> <!-- added --> <td><%= link_to 'Popup Show', product, :popup => ['Popup Show', 'width=200, height=150'] %></td> <td><%= link_to 'Edit', edit_product_path(product) %></td> <td><%= link_to 'Destroy', product, :confirm => 'Are you sure?', :method => :delete %></td> </tr>
リンクを追加したインデックスぺージと、showアクションをポップアップ表示した結果のイメージ。
画像にリンクを設定する
link_toにimage_tagを組み合わせるだけです。
さっきのindex.html.erbを改造してみる。
<tr> <td><%=h product.title %></td> <td><%=h product.description %></td> <td><%=h product.image_url %></td> <td><%= link_to 'Show', product %></td> <!-- added -->s <td><%= link_to(image_tag("rails.png", :size => "50x30"), product, :popup => ['Popup Show', 'width=200, height=150']) %></td> <td><%= link_to 'Edit', edit_product_path(product) %></td> <td><%= link_to 'Destroy', product, :confirm => 'Are you sure?', :method => :delete %></td> </tr>
で、こうなる。
国とタイムゾーンの選択リストを作る
説明が省略されていたので作ってみる。
まずはコントローラ。
products_controller.rbに以下のアクションを追加。
def country_timezone end
アクションを受け付けて、規定のテンプレートをレンダリングするだけのつまらないアクションです。
次にビュー。
app/views/products/country_timezone.html.erbを作成。
<h1>Choise Country and Timezone</h1> <% form_tag :action => :country_timezone, :id => 1 do |f| %> <%= select_tag("country", country_options_for_select(params[:country])) %> <%= select_tag("timezone", time_zone_options_for_select(params[:timezone])) %> <%= submit_tag %> <% end %> <p> selected country: <%= params[:country] %><br> selected timezone: <%= params[:timezone] %><br>
これで、
http://localhost:3000/products/country_timezone/1
にアクセスすると、こんな感じに表示される。
プルダウンから国とタイムゾーンを選択し、Save Changesを押下するたびに、
プルダウンの中身と、下のテキストの内容が変化します。
ファイルのアップロード
書籍中では、データベースにアップロードしたファイルを格納する例が書かれているので、ここではデータベースに格納しない例を作ってみます。
まずは、アップロードフォームを表示するアクションと、アップロードを行うアクションを定義。
app/controllers/products_controller.rb
def new_upload end def upload fileobj = params["uploaded_file"] puts fileobj.class.to_s puts fileobj.content_type redirect_to :action => :new_upload end
new_uploadは、上のと同じで、アクションを受け付けて、規定のテンプレートをレンダリングするだけのつまらないアクションです。
uploadの方は、パラメータ(uploaded_file; フォームの名前)で受け取った値が、どの型であるかと、Content-Typeをサーバのコンソールに表示しています。(実際にファイルの保存はしません。)
表示が終わったら、アップロードフォームに戻るようリダイレクトします。
IDなしのURLでもアクセスできるよう、routes.rbにルーティングを追加。
map.connect ':controller/new_upload', :action => "new_upload" map.connect ':controller/upload', :action => "upload" map.resources :products
map.resources :productsの上に2行追加。
次に、アップロードフォーム(ビュー)を作成する。
app/views/products/new_upload.html.erb
<h1>File upload sample</h1> <% form_tag ({:action => :upload},{ :multipart => true }) do |f| %> <%= file_field_tag("uploaded_file") %> <%= submit_tag "Upload" %> <% end %>
multipartにするのがミソですね。
ここまで出来たら、
http://localhost:3000/products/new_upload
にアクセスして、アップロードフォームを表示してみる。
適当にファイルを選んで、Uploadボタンをクリックすると、サーバのコンソールに以下のようなものが出力されました。
ActionController::UploadedStringIO text/plain 127.0.0.1 - - [25/Jul/2008:12:03:36 JST] "POST /products/upload HTTP/1.1" 302 107 http://localhost:3000/products/new_upload -> /products/upload /home/taka/test/app/views/products/new_upload.html.erb:3: warning: multiple values for a block parameter (0 for 1) from /usr/lib/ruby/gems/1.8/gems/actionpack-2.1.0/lib/action_view/helpers/capture_helper.rb:141 127.0.0.1 - - [25/Jul/2008:12:03:37 JST] "GET /products/new_upload HTTP/1.1" 304 0 http://localhost:3000/products/new_upload -> /products/new_upload
アップロードされたファイルは、ActionController::UploadedStringIO型として扱えるようになっているようです。
また、試しにアップロードしてみたファイルはテキストファイルだったので、Content-Typeはtext/plainになってますね。
あとは煮るなり焼くなりご自由に。
フラグメントキャッシュ
動的コンテンツと静的コンテンツが混ざったページにおいて、静的コンテンツ部分のみキャッシュしたいという要望に答えるのがフラグメントキャッシュです。
簡単なサンプルを書いてみます。
いつもの順番で、まずはコントローラ。
app/controllers/products_controller.rb
def fragment @dateTime = Time.now end def expirefragment expire_fragment :action => :fragment, :id => params[:id] redirect_to :action => :fragment, :id => params[:id] end
フラグメントキャッシュを行うページを表示するアクションfragmentと、フラグメントキャッシュをクリアして(失効させて)ぺージを再描画するアクションexpirefragmentを用意しました。
次にビュー。
app/views/products/fragment.html.erb
<%= @dateTime %> <% cache do %> <p> 静的コンテンツ </p> <%= link_to "expire fragment cache", :action => :expirefragment, :id => params[:id] %> <% end %>
cacheメソッドに囲まれたブロックが、フラグメントキャッシュされる部分。
それ以外はキャッシュされない部分なので、動的コンテンツなどキャッシュされたくないものを置きます。
ここまで出来たら、
http://localhost:3000/products/fragment/1
にアクセスして、ページを表示してみる。
リロードすると、日時の部分が書き換わります。
で、試しにfragment.html.erbの静的コンテンツ部分を変えてみる。
<%= @dateTime %> <% cache do %> <p> 静的コンテンツだよ。 </p> <%= link_to "expire fragment cache", :action => :expirefragment, :id => params[:id] %> <% end %>
これでリロードしても、キャッシュされているので表示されている内容は変わりません。
その代わり、expire fragment cacheのリンクをクリックすると、キャッシュがクリアされて、最新の内容になります。