rails chronicle

88
2008 / 2 / 16 日本PostgreSQLユーザ会北海道支部 / Ruby札幌 合同セミナー 株式会社永和システムマネジメント / Rails勉強会@東京メール係 諸橋恭介 Rails <form> Chronicle history of nameattribute and form_* helper methods.

Post on 11-Sep-2014

34 views

Category:

Technology


1 download

DESCRIPTION

form_forすごい。超すごい

TRANSCRIPT

Page 1: Rails  Chronicle

2008 / 2 / 16日本PostgreSQLユーザ会北海道支部 / Ruby札幌 合同セミナー

株式会社永和システムマネジメント / Rails勉強会@東京メール係

諸橋恭介

Rails <form> Chroniclehistory of “name” attribute and form_* helper methods.

Page 2: Rails  Chronicle

諸橋恭介(もろはしきょうすけ)

• (株)永和システムマネジメント所属

• RubyとRailsでご飯を食べてます。

• Rails勉強会@東京のメール係です。

•身重の妻をおいて北海道に来ました。

Page 3: Rails  Chronicle

Acknowledgments

•いろいろ手配してくれた島田さん•本日お越しの日本PostgreSQLユーザ会北海道支部 /

Ruby札幌の皆さん

•ごとうゆうぞうさん•高橋征義さん•Rails勉強会@東京の皆さん

•角谷さん ほか 永和システムマネジメントの皆さん

Page 4: Rails  Chronicle

Acknowledgments

たいへん美味しゅうございました

Page 5: Rails  Chronicle

宣伝

•今春(たぶん)にRailsのレシピ本が出ます。

• Railsに触り始めた人も仕事で使ってる人も。

•日本Rubyの会 会長の高橋さんとの共著です。

Page 6: Rails  Chronicle

Contact me

[email protected] (work)

[email protected] (private)

•http://d.hatena.ne.jp/moro/•はてダの内容は個人の主張であり勤務先等には一切関係ありません :- )

Page 7: Rails  Chronicle

お願い•いろんな反応をお聞きしたいです。ので、お持ちのblogをFeedListに登録して欲しいです。

http://feedlist.net/

Page 8: Rails  Chronicle
Page 9: Rails  Chronicle

Conclusion

# in app/controllers/entries_controller.rb

entry = Entry.new(params[:entry])

•エンティティ/リソースを美しく生成するための長い戦いについて話します

•モデルクラスのセッターを作り込む•formとクエリパラメータを作り込む

Page 10: Rails  Chronicle

What is <form>?

Page 11: Rails  Chronicle

<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メソッドを持つ

Page 12: Rails  Chronicle

<form> is an Interface.•ユーザがブラウザから情報を入力するための

User Interface.

Page 13: Rails  Chronicle

<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>

Page 14: Rails  Chronicle

What is control’s

“name” attribute.• name属性はクエリパラメータのキーとしてアプリケーションに渡される

•アプリケーションがリクエスト内のユーザ情報を識別するための手がかり

•ユーザとアプリケーションをつなぐ鍵こそが

“name”属性

Page 15: Rails  Chronicle

“name” attribute is the Programming Interface

•formはAPIだ!

•特にコントロールのname属性はAPIだ!

•もちろんaction属性とmethod属性も重要。これはもう少しあとの話。

Page 16: Rails  Chronicle

<form> is an Interface.

•ユーザがブラウザから情報を入力するためのUser Interface

•プレゼンテーションレイヤとコントローラレイヤのPrograming Interface

Page 17: Rails  Chronicle

How does Rails handle?

Page 18: Rails  Chronicle

Phase 1

From View to Controller

Page 19: Rails  Chronicle

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”}}

Page 20: Rails  Chronicle

Examples

<form action=”...”> <input name=”title” type=”text” /> <input name=”body” type=”text” /></form>

{:title => “new title”, :body => ”new body”}

Page 21: Rails  Chronicle

Examples

<form action=”...”> <input name=”entry[title]” type=”text” /> <input name=”entry[body]” type=”text” /></form>

{ :entry => {:title => “new title”, :body => ”new body” }}

Page 22: Rails  Chronicle

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"]}}

Page 23: Rails  Chronicle

Phase 2

From Controller to Model (and DB)

Page 24: Rails  Chronicle

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”

Page 25: Rails  Chronicle

And, of course, Rails can

save AR object to DB

•もちろん、RailsはActiveRecordオブジェクトをDB

に保存できる。

entry = Entry.new(hash)entry.title # => “new title”

entry.save

Page 26: Rails  Chronicle

Therefore Rails can

save form-data to DB at ease.

•つまり、Railsではformから受け取ったデータをちょう簡単にDBに保存できる。

entry = Entry.new(params[:entry])

entry.save

Page 27: Rails  Chronicle

In other words,

• params[モデル]という記法が出来るかどうかがコントローラを簡潔に書くカギ

•つまりform内のコントロールのname属性には注目し、工夫する価値がある

entry = Entry.new(params[:entry])

Page 28: Rails  Chronicle

history of “name” attribute andform_* helper methods.

Rails <form> Chronicle

Page 29: Rails  Chronicle

~ Rails 1.0Before form_for()

Page 30: Rails  Chronicle

form_tag(url_for_options = {}, options = {}, *parameters_for_url)

• Rails 1.0までは<form>タグを生成するための特別なメソッドは存在しなかった。

• HTMLのform要素のラッパーのみ。

• action属性を第一引数url_for_optionsで

• その他の属性を第二引数optionsで

Page 31: Rails  Chronicle

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

Page 32: Rails  Chronicle

andthere is lovely end_form_tag()

# Outputs “</form>”def end_form_tag “</form>”end

Page 33: Rails  Chronicle

ActionView::Helpers::

FormHelper.

•現代につながる第一歩はFormHelperにあった!!

@entry = Entry.new(:title=>”タイトル”)text_field(:entry, :title)

# => <input type=”text” name=”entry[title]” value=”タイトル” />

• ActiveRecordオブジェクト(=DB構造)とformの結合の端緒がここにある。

Page 34: Rails  Chronicle

ActionView::Helpers::

FormHelper.@entry = Entry.new(:title=>”タイトル”)text_field(:entry, :title)

# => <input type=”text” name=”entry[title]” value=”タイトル” />

{:entry => {:title => “タイトル” }}

and you will get

Page 35: Rails  Chronicle

ActionView::Helpers::

FormHelper.

text_field(object, method, options={})

• ビュースコープの中から 第一引数objectで指定されたインスタンス変数 ”@#{object}” を探し

• 第二引数methodの結果をデフォルトとして

• “#{object}[#{name}]”というnameのinputを生成する

Page 36: Rails  Chronicle

Examples

<form action=”...”> <input name=”entry[title]” type=”text” /> <input name=”entry[body]” type=”text” /></form>

{ :entry => {:title => “new title”, :body => ”new body” }}

remind

Page 37: Rails  Chronicle

Rails 1.1& Rails1.2

form_for() has come.

Page 38: Rails  Chronicle

form_for( object_name, *args, &proc)

• Rails 1.1以降のフォーム生成メソッド

• 第一引数で対象のインスタンス変数名を

• URLなどその他のオプションを第二引数で

• フォームの中身はブロック内に記述

• “Creates a form and a scope around a specific model object, ”(from API Document)

Page 39: Rails  Chronicle

form for the object.

<%# @entryのためのフォーム %><% form_for(:entry, :url=>{:action=>”crate”}) do |f| %> ...<% end %>

• 文字通り「あるオブジェクトのためのフォーム」• text_field()と同様にオブジェクトの名前を指定

• フォームが終わる == ブロックを抜けるとフォームを閉じる

Page 40: Rails  Chronicle

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

Page 41: Rails  Chronicle

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

Page 42: Rails  Chronicle

<% 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

•別途対象のオブジェクトを指定できます。

Page 43: Rails  Chronicle

OK, but

Why form_for()?• RailsはフォームデータをHashに変換する

•“Creates a form and a scope around a specific model object, ”

•特定のモデルをCRUDするHTMLフォームの生成

• モデルとビューの結合レベル↑↑

remind

remind

Page 44: Rails  Chronicle

OK, but

Does it works well?

•併せて読みたい「has_many :through」

• 関連テーブルのCRUDでリソースの関連や状態を表現している

• それRDBMSでやってるよ

Page 45: Rails  Chronicle

But...• form_for登場直後は「分かりづらい」と大不評

(Rails勉強会@東京調べ)

• そんな都合良くインスタンス変数名がキマンネーよ

• end_form_tag()のobsolete化を嘆く声

•第二引数(?)で対象オブジェクトを渡せます

•やっぱりみんな使いたがらなかったわけです。。。難しいし。。。

Page 46: Rails  Chronicle

Rails 2.0

form_for() meets Resource

Page 47: Rails  Chronicle

form_for() and map.resource

ActionController::Routes.draw do |m| map.resources :entriesend

•ARオブジェクトを ”resource”として登録

•“resource”はHTTP経由でCRUDされるリソースである、と宣言

Page 48: Rails  Chronicle

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

Page 49: Rails  Chronicle

<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.

Page 50: Rails  Chronicle

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()を参照

Page 51: Rails  Chronicle

form_for()

remembers the object.

<% form_for(@entry) do |f| %> <label>Title</label> <%= f.text_field :title %><% end %>

remind

•もちろんFormBuilderも使えます。

Page 52: Rails  Chronicle

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>...

Page 53: Rails  Chronicle

•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>

Page 54: Rails  Chronicle

bigger

Convention for CRUD

•HTTPを経由してCRUDするConventionはすでにRFC2616にあった。

•Rails自身もレールに乗ればいいじゃない

Page 55: Rails  Chronicle

on RubyKaigi2006,

DHH saidGET POST PUT DELETE

find create update destroy

SELECT INSERT UPDATE DELETE

http://media.rubyonrails.org/presentations/worldofresources.pdf

Page 56: Rails  Chronicle

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"}

Page 57: Rails  Chronicle

簡単に説明すれば「リクエスト」には「HTTP Method」が関わっている!

「HTTP Method」は「アクション」をアプリケーションに決定させるからだ!

「アクション」中の「リソースのCRUD」は「ActiveRecordの操作」に関わっている!

「ActiveRecordの操作」イコール「DBのCRUD」!!

つまりDBをCRUDするように、form_forによって「resource orientedなform」を作り出し....

「HTTPを介してリソースをCRUDする

Page 58: Rails  Chronicle
Page 59: Rails  Chronicle

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

Page 60: Rails  Chronicle

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

Page 61: Rails  Chronicle

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

Page 62: Rails  Chronicle

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

Page 63: Rails  Chronicle

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

Page 64: Rails  Chronicle

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が指定されているとそのメソッド

Page 65: Rails  Chronicle

I’ve talked about

features of form_for()w/ Rails 2.0

•対象となるオブジェクトからformのDOM ID

とCSSクラスを定義する

•対象となるオブジェクトを知っているFormBuilderを提供する

•対象となるオブジェクトの状態から次のアクションを導出する

remind

Page 66: Rails  Chronicle

One more(sad) thing..

Page 67: Rails  Chronicle

undefined local variable or

method `end_form_tag’

Good bye end_form_tag()

Page 68: Rails  Chronicle

Conclusion

Page 69: Rails  Chronicle

the Goalentry = Entry.new(params[:entry])

entry.save()

Page 70: Rails  Chronicle

Where form_for() and AC::Resource going to?

•Web経由でリソースをCRUDしたい

• つまりアプリケーションが扱うデータをリソースとして設計できるようにしたい

• relationshipもCRUDで表現できる

• has_many :throughが教えてくれた

Page 71: Rails  Chronicle

Conclusion of Conclusions

•Rails 2.0のform_for()すごいよ

•AC::Resourcesと合わせるともっとすごいよ

•Rails関連のblogを「2.0での設計話」で満ち満ちさせたい!!

•感想&ツッコミお待ちしてます

Page 72: Rails  Chronicle

Any Question?

Page 73: Rails  Chronicle

FAQ: あんまり関係ないけどActiveResourceってなに? 関係あるの?

•RESTfulなWebサービスを利用するためのクライアントライブラリ。略してARes

•Rails側には、ActionControllerにARes対応のサービスを実装するための機能がある

• ActionController::Resoucesモジュールあたり

• よく話題に上るルーティングもこの一環

Page 74: Rails  Chronicle

FAQ:インスタンス変数名 + 第二引数での指定はまだできる?

•もちろんRails 1.2と同じレベルでは出来る

•DOM IDをつけたり次のアクションの導出は出来ない

•なので1.2と同様、自分でURLパラメタなんかを渡す

Page 75: Rails  Chronicle

FAQ: どういうオブジェクトをform_for()の引数に出来るの?

•config/routes.rbでresourceとしているもの

•map.resources resource_plural

•new_record?メソッドとidがあるものなもの

•この条件を満たせばARに限らない

• 複数テーブルをaggregateするクラスとか

•↑のArrayとかHash

Page 76: Rails  Chronicle

FAQ: 複雑なフォームはどうするの?

複数のタグを1個のテキストにしたいとか

•AR::Base.new()と組み合せるのがおすすめ

•アクセサさえ定義すればOK

•コンセプトは書籍に書いたので買ってください。

•書籍が分かりづらいようならサポートしますのでメールください :- )

Page 77: Rails  Chronicle

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]

Page 78: Rails  Chronicle

ご清聴ありがとうございました

Page 79: Rails  Chronicle

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

Page 80: Rails  Chronicle
Page 81: Rails  Chronicle

Epilogue

Page 82: Rails  Chronicle

REST and Rails

•HTTP経由でリソースをCRUDできるようにしとくのは未来への投資

•form_for()はHTMLというもっともプリミティブなUIを提供する良い手段

Page 83: Rails  Chronicle

handlingnested resouce

•単一のリソースのCRUDだけではキツい

• あるブログ(id=2)の記事(id=5)を指定したい

• あるブログ(id=2)に新規記事を追加したい

Page 84: Rails  Chronicle

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を指定

Page 85: Rails  Chronicle

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”>

Page 86: Rails  Chronicle

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)

Page 87: Rails  Chronicle

form_for() and REST

•HTTP is next TCP/IP

• 僕たちがTCPを意識することが減ったように

• ゆとり世代はHTTPの上を意識せずに歩き回る予感

•Man Machine Interfaceこそが未来

• ローカルアプリケーションの復権

• ローカルとWebをつなぐものこそREST

Page 88: Rails  Chronicle

Man Machine Interfaceand REST

•クライアントはHuman Interfaceを提供する

• iPhoneとかNintendoDSとか。

• もちろんMacとかWindowsとか。

•Web上のデータとクライアントを繋ぐプロトコルとしてのREST

•Rails 2.0はその道具としてよくできてます