View
34
Download
1
Tags:
Embed Size (px)
DESCRIPTION
form_forすごい。超すごい
Citation preview
2008 / 2 / 16日本PostgreSQLユーザ会北海道支部 / Ruby札幌 合同セミナー
株式会社永和システムマネジメント / Rails勉強会@東京メール係
諸橋恭介
Rails <form> Chroniclehistory of “name” attribute and form_* helper methods.
諸橋恭介(もろはしきょうすけ)
• (株)永和システムマネジメント所属
• RubyとRailsでご飯を食べてます。
• Rails勉強会@東京のメール係です。
•身重の妻をおいて北海道に来ました。
Acknowledgments
•いろいろ手配してくれた島田さん•本日お越しの日本PostgreSQLユーザ会北海道支部 /
Ruby札幌の皆さん
•ごとうゆうぞうさん•高橋征義さん•Rails勉強会@東京の皆さん
•角谷さん ほか 永和システムマネジメントの皆さん
Acknowledgments
たいへん美味しゅうございました
宣伝
•今春(たぶん)にRailsのレシピ本が出ます。
• Railsに触り始めた人も仕事で使ってる人も。
•日本Rubyの会 会長の高橋さんとの共著です。
Contact me
•[email protected] (work)
•[email protected] (private)
•http://d.hatena.ne.jp/moro/•はてダの内容は個人の主張であり勤務先等には一切関係ありません :- )
お願い•いろんな反応をお聞きしたいです。ので、お持ちのblogをFeedListに登録して欲しいです。
http://feedlist.net/
Conclusion
# in app/controllers/entries_controller.rb
entry = Entry.new(params[:entry])
•エンティティ/リソースを美しく生成するための長い戦いについて話します
•モデルクラスのセッターを作り込む•formとクエリパラメータを作り込む
What is <form>?
<form> is an HTML element.
<form id=”new_entry” action=”/entries” method=”POST”> <label for=”entry_title”>Title</label> <input type=”text” id=”entry_title” name=”entry[title]” />
<label for=”entry_body”>Body</label> <textarea id=”entry_body” name=”entry[body]”></textarea></form>
• form要素は情報の入力先を持つ
•入力するためのHTTPメソッドを持つ
<form> is an Interface.•ユーザがブラウザから情報を入力するための
User Interface.
<form> has some Controls.•小要素としてControl要素(input or so)を持つ
• Control要素はname属性を持つ
<form id=”new_entry” action=”/entries” method=”POST”> <label for=”entry_title”>Title</label> <input type=”text” id=”entry_title” name=”entry[title]” />
<label for=”entry_body”>Body</label> <textarea id=”entry_body” name=”entry[body]”> </textarea></form>
What is control’s
“name” attribute.• name属性はクエリパラメータのキーとしてアプリケーションに渡される
•アプリケーションがリクエスト内のユーザ情報を識別するための手がかり
•ユーザとアプリケーションをつなぐ鍵こそが
“name”属性
“name” attribute is the Programming Interface
•formはAPIだ!
•特にコントロールのname属性はAPIだ!
•もちろんaction属性とmethod属性も重要。これはもう少しあとの話。
<form> is an Interface.
•ユーザがブラウザから情報を入力するためのUser Interface
•プレゼンテーションレイヤとコントローラレイヤのPrograming Interface
How does Rails handle?
Phase 1
From View to Controller
Rails handle query parameters
like a Hash
POST /entry entry[title]=new title&entry[body]=new body
•RailsはフォームデータをHashに変換する
params() # =>{ :entry => {:title => “new title”, :body => ”new body”}}
Examples
<form action=”...”> <input name=”title” type=”text” /> <input name=”body” type=”text” /></form>
{:title => “new title”, :body => ”new body”}
Examples
<form action=”...”> <input name=”entry[title]” type=”text” /> <input name=”entry[body]” type=”text” /></form>
{ :entry => {:title => “new title”, :body => ”new body” }}
Examples
<form action="..."> <input type="text" name="entry[title]" /> <input type="text" name="entry[links][]" /> <input type="text" name="entry[links][]" /> <input type="text" name="entry[links][]" /> </form>
{"entry"=>{"title"=>"aaa", "links"=>["xxx", "yyy", "zzz"]}}
Phase 2
From Controller to Model (and DB)
Rails handle Hash as
AR constructor argument.
• RailsはHashをActiveRecordオブジェクトのコンストラクタ引数として指定できる。
hash = { :entry => {:title => “new title”, :body => ”new body”}}
entry = Entry.new(hash[:entry])entry.title # => “new title”
And, of course, Rails can
save AR object to DB
•もちろん、RailsはActiveRecordオブジェクトをDB
に保存できる。
entry = Entry.new(hash)entry.title # => “new title”
entry.save
Therefore Rails can
save form-data to DB at ease.
•つまり、Railsではformから受け取ったデータをちょう簡単にDBに保存できる。
entry = Entry.new(params[:entry])
entry.save
In other words,
• params[モデル]という記法が出来るかどうかがコントローラを簡潔に書くカギ
•つまりform内のコントロールのname属性には注目し、工夫する価値がある
entry = Entry.new(params[:entry])
history of “name” attribute andform_* helper methods.
Rails <form> Chronicle
~ Rails 1.0Before form_for()
form_tag(url_for_options = {}, options = {}, *parameters_for_url)
• Rails 1.0までは<form>タグを生成するための特別なメソッドは存在しなかった。
• HTMLのform要素のラッパーのみ。
• action属性を第一引数url_for_optionsで
• その他の属性を第二引数optionsで
Thin wrapper for HTML <form> tag.
form_tag( {:controller=>”entries”,:action=>”create”}, {:id=>”new_entry”, :method=>”post”} )
<form id=”new_entry” method=”POST” action=>”entries/create”>
generates
andthere is lovely end_form_tag()
# Outputs “</form>”def end_form_tag “</form>”end
ActionView::Helpers::
FormHelper.
•現代につながる第一歩はFormHelperにあった!!
@entry = Entry.new(:title=>”タイトル”)text_field(:entry, :title)
# => <input type=”text” name=”entry[title]” value=”タイトル” />
• ActiveRecordオブジェクト(=DB構造)とformの結合の端緒がここにある。
ActionView::Helpers::
FormHelper.@entry = Entry.new(:title=>”タイトル”)text_field(:entry, :title)
# => <input type=”text” name=”entry[title]” value=”タイトル” />
{:entry => {:title => “タイトル” }}
and you will get
ActionView::Helpers::
FormHelper.
text_field(object, method, options={})
• ビュースコープの中から 第一引数objectで指定されたインスタンス変数 ”@#{object}” を探し
• 第二引数methodの結果をデフォルトとして
• “#{object}[#{name}]”というnameのinputを生成する
Examples
<form action=”...”> <input name=”entry[title]” type=”text” /> <input name=”entry[body]” type=”text” /></form>
{ :entry => {:title => “new title”, :body => ”new body” }}
remind
Rails 1.1& Rails1.2
form_for() has come.
form_for( object_name, *args, &proc)
• Rails 1.1以降のフォーム生成メソッド
• 第一引数で対象のインスタンス変数名を
• URLなどその他のオプションを第二引数で
• フォームの中身はブロック内に記述
• “Creates a form and a scope around a specific model object, ”(from API Document)
form for the object.
<%# @entryのためのフォーム %><% form_for(:entry, :url=>{:action=>”crate”}) do |f| %> ...<% end %>
• 文字通り「あるオブジェクトのためのフォーム」• text_field()と同様にオブジェクトの名前を指定
• フォームが終わる == ブロックを抜けるとフォームを閉じる
form_for()
remembers the object.
<% form_for(:entry, :url=>{:action=>”crate”}) do |f| %> # @entry#titleのためのtext field
<label>Title</label> <%= f.text_field :title %><% end %>
• ブロック引数はフォームが対象としているオブジェクトを知っているActionView::FormBuilderオブジェクト
• text_field(method, options={})
• cf : text_field(object, method, options={}) in Rails 1.0
ActionView::
FormBuilder<% form_for(:entry, :url=>{:action=>”crate”}) do |f| %> <label>Title</label> <%= f.text_field :title %><% end %>
<form action="/entries/create" method="post"> <label>Title</label> <input id="entry_title" name="entry[title]" size="30" type="text" /></form>
generates
<% entry_local = Entry.new(:title=>”local”) %>
<% form_for(:entry, entry_local, :url=>{:action=>”crate”}) do |f| %> <label>Title</label> <%= f.text_field :title %><% end %>
FYI: 2nd argument.
• respond_to?(:title)なオブジェクトなら何でもOK
•別途対象のオブジェクトを指定できます。
OK, but
Why form_for()?• RailsはフォームデータをHashに変換する
•“Creates a form and a scope around a specific model object, ”
•特定のモデルをCRUDするHTMLフォームの生成
• モデルとビューの結合レベル↑↑
remind
remind
OK, but
Does it works well?
•併せて読みたい「has_many :through」
• 関連テーブルのCRUDでリソースの関連や状態を表現している
• それRDBMSでやってるよ
But...• form_for登場直後は「分かりづらい」と大不評
(Rails勉強会@東京調べ)
• そんな都合良くインスタンス変数名がキマンネーよ
• end_form_tag()のobsolete化を嘆く声
•第二引数(?)で対象オブジェクトを渡せます
•やっぱりみんな使いたがらなかったわけです。。。難しいし。。。
Rails 2.0
form_for() meets Resource
form_for() and map.resource
ActionController::Routes.draw do |m| map.resources :entriesend
•ARオブジェクトを ”resource”として登録
•“resource”はHTTP経由でCRUDされるリソースである、と宣言
new form_for()<% @entry = Entry.find(1) %>
<% form_for(@entry) do |f|%> ...<% end %>
<form action="/entries/1" class="edit_entry" id="edit_entry_1" method="post">
<input name="_method" type="hidden" value="put" /> ...</form>
generates
<form action="/entries/1" class="edit_entry" id="edit_entry_1" method="post">
<input name="_method" type="hidden" value="put" /> ...</form>
•ActiveRecordオブジェクトを表示する新しいConvention
• フォームのclass属性やID属性も付与される
form_for()
determine the form identity.
new Convention forDOM ID & CSS class
<form action="/entries/1" class="edit_entry" id="edit_entry_1" method="post">
•CSS classは”【ARクラス名】”
• IDは”【ARクラス名】_【DBのID】”
• それぞれprefix指定可能。編集は”edit”、新規作成は”new”が自動的に付与される
• class属性やID属性のつき方はdom_class()やdom_id()を参照
form_for()
remembers the object.
<% form_for(@entry) do |f| %> <label>Title</label> <%= f.text_field :title %><% end %>
remind
•もちろんFormBuilderも使えます。
ActionView::
FormBuilder#label
<% form_for(:entry, :url=>{:action=>”crate”}) do |f| %> <%= f.label :title, “タイトル” %> <%= f.text_field :title %><% end %>
• label(method, text=nil, options={})はじめました。
...<label for=”entry_title”>タイトル<label>...
•ARオブジェクトの状態から次のアクションを導出している
• 新規レコードの場合ならPOST /entries、既存レコードならPUT /entries/:id
form_for()
understands the object’s status.<form action="/entries/1" class="edit_entry" id="edit_entry_1" method="post">
<input name="_method" type="hidden" value="put" /> ...</form>
bigger
Convention for CRUD
•HTTPを経由してCRUDするConventionはすでにRFC2616にあった。
•Rails自身もレールに乗ればいいじゃない
on RubyKaigi2006,
DHH saidGET POST PUT DELETE
find create update destroy
SELECT INSERT UPDATE DELETE
http://media.rubyonrails.org/presentations/worldofresources.pdf
HTTP methods and actionsGET /entries/:id {:controller=>"entries", :action=>"show"}
PUT /entries/:id {:controller=>"entries", :action=>"update"}
DELETE /entries/:id {:controller=>"entries", :action=>"destroy"}
POST /entries{:controller=>"entries", :action=>"create"}
簡単に説明すれば「リクエスト」には「HTTP Method」が関わっている!
「HTTP Method」は「アクション」をアプリケーションに決定させるからだ!
「アクション」中の「リソースのCRUD」は「ActiveRecordの操作」に関わっている!
「ActiveRecordの操作」イコール「DBのCRUD」!!
つまりDBをCRUDするように、form_forによって「resource orientedなform」を作り出し....
「HTTPを介してリソースをCRUDする
class EntriesController < ApplicationController def show @entry = Entry.find(params[:id]) end
def create @entry = Entry.new(params[:entry]) @entry.save end
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry]) end
def destroy @entry = Entry.find(params[:id]) @entry.destroy endend
class EntriesController < ApplicationController def show @entry = Entry.find(params[:id]) end
def create @entry = Entry.new(params[:entry]) @entry.save end
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry]) end
def destroy @entry = Entry.find(params[:id]) @entry.destroy endend
def show @entry = Entry.find(params[:id]) end
class EntriesController < ApplicationController def show @entry = Entry.find(params[:id]) end
def create @entry = Entry.new(params[:entry]) @entry.save end
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry]) end
def destroy @entry = Entry.find(params[:id]) @entry.destroy endend
def create @entry = Entry.new(params[:entry]) @entry.saveend
class EntriesController < ApplicationController def show @entry = Entry.find(params[:id]) end
def create @entry = Entry.new(params[:entry]) @entry.save end
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry]) end
def destroy @entry = Entry.find(params[:id]) @entry.destroy endend
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry])end
class EntriesController < ApplicationController def show @entry = Entry.find(params[:id]) end
def create @entry = Entry.new(params[:entry]) @entry.save end
def update @entry = Entry.find(params[:id]) @entry.update_attributes(params[:entry]) end
def destroy @entry = Entry.find(params[:id]) @entry.destroy endend
def destroy @entry = Entry.find(params[:id]) @entry.destroyend
FYI: about PUT and DELETE
<form action="/entries/1" class="edit_entry" id="edit_entry_1" method="post">
<input name="_method" type="hidden" value="put" /> ...</form>
•ふつうのブラウザはPUTリクエストができないのでひと工夫
• POSTリクエストかつ
• _methodが指定されているとそのメソッド
I’ve talked about
features of form_for()w/ Rails 2.0
•対象となるオブジェクトからformのDOM ID
とCSSクラスを定義する
•対象となるオブジェクトを知っているFormBuilderを提供する
•対象となるオブジェクトの状態から次のアクションを導出する
remind
One more(sad) thing..
undefined local variable or
method `end_form_tag’
Good bye end_form_tag()
Conclusion
the Goalentry = Entry.new(params[:entry])
entry.save()
Where form_for() and AC::Resource going to?
•Web経由でリソースをCRUDしたい
• つまりアプリケーションが扱うデータをリソースとして設計できるようにしたい
• relationshipもCRUDで表現できる
• has_many :throughが教えてくれた
Conclusion of Conclusions
•Rails 2.0のform_for()すごいよ
•AC::Resourcesと合わせるともっとすごいよ
•Rails関連のblogを「2.0での設計話」で満ち満ちさせたい!!
•感想&ツッコミお待ちしてます
Any Question?
FAQ: あんまり関係ないけどActiveResourceってなに? 関係あるの?
•RESTfulなWebサービスを利用するためのクライアントライブラリ。略してARes
•Rails側には、ActionControllerにARes対応のサービスを実装するための機能がある
• ActionController::Resoucesモジュールあたり
• よく話題に上るルーティングもこの一環
FAQ:インスタンス変数名 + 第二引数での指定はまだできる?
•もちろんRails 1.2と同じレベルでは出来る
•DOM IDをつけたり次のアクションの導出は出来ない
•なので1.2と同様、自分でURLパラメタなんかを渡す
FAQ: どういうオブジェクトをform_for()の引数に出来るの?
•config/routes.rbでresourceとしているもの
•map.resources resource_plural
•new_record?メソッドとidがあるものなもの
•この条件を満たせばARに限らない
• 複数テーブルをaggregateするクラスとか
•↑のArrayとかHash
FAQ: 複雑なフォームはどうするの?
複数のタグを1個のテキストにしたいとか
•AR::Base.new()と組み合せるのがおすすめ
•アクセサさえ定義すればOK
•コンセプトは書籍に書いたので買ってください。
•書籍が分かりづらいようならサポートしますのでメールください :- )
FAQ: 複雑なフォームはどうするの?
複数のタグを1個のテキストにしたいとかclass Entry < ActiveRecord::Base has_many :tags
def tags_string=(str) str.split(“,”).each{ ... } end
def tags_string ts = self.tags ts.map(&:name).join(“, “) endend
entry[tags_string]
ご清聴ありがとうございました
References• Railsレシピブック(仮題)
• RESTful Webサービス
• http://amazon.jp/o/ASIN/4873113539/morodiary05-22
• Discover of World of resource.
• http://media.rubyonrails.org/presentations/worldofresources.pdf
• RFC2616(邦訳版)
• http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616
• HTML 4.01 Specification(ja)
• http://www.asahi-net.or.jp/%7Esd5a-ucd/rec-html401j/cover.html
Epilogue
REST and Rails
•HTTP経由でリソースをCRUDできるようにしとくのは未来への投資
•form_for()はHTMLというもっともプリミティブなUIを提供する良い手段
handlingnested resouce
•単一のリソースのCRUDだけではキツい
• あるブログ(id=2)の記事(id=5)を指定したい
• あるブログ(id=2)に新規記事を追加したい
2 ways for handling nested resource
Ordinary style
<form action="/entries" class="new_entry" id="new_entry" method="post">
<input type=”hidden” name=”blog_id” value=”2” /> <input type=”text” name=”entry[title]” /> ...</form>
•フォームのパラメータでblog_idを指定
2 ways for handling nested resource
AC::Resources style•URLでネストしたリソースであることを表現# config/routes.rb
map.resources :blog, :has_many=>:entries
# in app/views/entries/*.html.erbform_for(@entry,:url=>blog_entries_path(@entry.blog) )
# in HTML<form action=”/blogs/1/entries” method=”POST”>
2 ways for handling nested resource
AC::Resources style•検索も同様に。
# http://exapmle.com/users/1/entries@user = User.find(params[:user_id])@entries = @user.entries.find(:all)
# http://exapmle.com/blogs/1/entries@blog = Blog.find(params[:blog_id])@entries = @blog.entries.find(:all)
# http://exapmle.com/entries@entries = Entry.entries.find(:all)
form_for() and REST
•HTTP is next TCP/IP
• 僕たちがTCPを意識することが減ったように
• ゆとり世代はHTTPの上を意識せずに歩き回る予感
•Man Machine Interfaceこそが未来
• ローカルアプリケーションの復権
• ローカルとWebをつなぐものこそREST
Man Machine Interfaceand REST
•クライアントはHuman Interfaceを提供する
• iPhoneとかNintendoDSとか。
• もちろんMacとかWindowsとか。
•Web上のデータとクライアントを繋ぐプロトコルとしてのREST
•Rails 2.0はその道具としてよくできてます