マルチステップ(ウィザード形式の)フォームで役立つかもしれないTips
activerecord
のインスタンスにnew
的なメソッドが欲しい。
- モデルのインスタンスに
params
で渡ってきた値を使って一時的にattributes
を設定しておきたい &nested model
のbuild
もやって欲しい。 - Viewにインスタンス変数を渡す際、一時的に
attributes
を上書きしたいだけなので、update_attributes!
は使えない。- なぜ一時的かというと、長大なマルチステップフォームだから。最終画面まで保存しちゃだめなの。
Railsでフォームを使ったウィザード形式の画面遷移を実装する際はだいたい以下のように実装してる場合が多い。
class HogehogesController < ApplicationController def new @customer = Customer.new end def update @customer = Customer.new(params[:customer]) if @customer.save ~~~~ end end
要するに画面毎にCustomer
をnew
して、@customer
を生成しているのだけど、
new
を通してフォームから渡ってきたparams、つまりnested_attributes
を渡すと、
nested model
も勝手にbuild
してくれる。
ちなみにフォームから渡ってくるparams
はこんな感じ。
{ "customer"=>{ "customer_favorites_attributes"=>{ "url"=> "fooo" } } }
ここまでは良いのだけど、問題はCustomer
が既に存在する場合。
単純に「入力」→「保存」だけの画面遷移なら、new
してる部分をCustomer.find(:id)
やらに変えれば良い。
ただ、マルチステップなフォームになった場合、途中で保存されると困るケースがある。
さらにいうとnested_attributes
が渡ってくるフォームの場合。
nested model
を個別にbuild
とかしたくない。
そこで期待する挙動は以下の様な形
class HogehogesController < ApplicationController def new @customer = current_or_guest_customer end def update # new の場合と同じ用に nanikaメソッドが 一時的に attributes を上書きしてくれて、 # かつ nested model の build もやってくれる! @customer = current_or_guest_customer.nanika(params[:customer]) if @customer.save ~~~~ end end
前提
class Customer < ActiveRecord::Base has_many :favorites, class_name: "CustomerFavorite" end class CustomerFavorite < ActiveRecord::Base belongs_to :customer end
= simple_form_for @customer, :method => :post do |f| = f.simple_fields_for :favorites do |favorite_form| = favorite_form.input :url
- マルチステップ(入力、入力、確認画面、入力、確認画面、完了画面)フォーム
調査
期待する挙動がnew
のそれに近いものでいいのだから、
インスタンス作るときに使うcreate
かnew
メソッド見ればいいんじゃ?
ということでAPI参照〜。
これかな?
http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-create
def create(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create(attr, &block) } else object = new(attributes, &block) object.save object end end
new
使っとる・・
でもどこのnew
なのこれ・・・
とりあえずnew
を検索検索・・
new
だけ入れると候補多すぎ・・・地道に見ていく・・
このAPIもう少し絞込みの手段が欲しい・・
これっぽい
http://api.rubyonrails.org/classes/ActiveModel/Model.html#method-c-new
def initialize(params={}) params.each do |attr, value| self.public_send("#{attr}=", value) end if params super() end
ほほーpublic_send
とやらを使っているようだ。
ruby自体のObjectクラスに実装されているsend
メソッドと同様に、
レシーバの持つメソッドを、シンボルや文字列で呼び出せるメソッドみたい。
(レシーバって呼び方がいまいちしっくりこないのだけど、レシーバってなんでレシーバって呼ぶんだろう。)
send
メソッドとの違いは、private
なメソッドを呼べるかどうか。
public_send
はprivate
なメソッドを呼び出せない。
脱線してしまったけど、とにかくこのpublic_send
methodでself
のもつpublic
なmethodを呼び出すわけですな。(◯ー大柴かっ)
ということは、selfは元々~~~~_attributes=
ってメソッドを持っていることになる。
今回の前提だと、Customer
にはcustomer_favorites_attributes=
メソッドがあるはず。
methods
メソッドを使って調べてみる。
@customer = Customer.new @customer.methods.grep(/customer_favorites_attributes/) => [ [0] customer_favorites_attributes=(attributes) Customer ]
はいありました!
なるほど、普段何気なく使ってるnested_attributes
を受け入れる仕組みが、
初めからactiverecord
オブジェクトには備わっているのか〜!
それで Customer.new(params[:customer])
みたいなことが簡単にできるんだ。
めちゃくちゃ便利!
実践
実際に試してみるとbuild
もやってくれてる!
@customer.favorites => nil class Customer < ActiveRecord::Base def nanika(params = {}) params.each do |attr, value| self.public_send("#{attr}=", value) end if params end end @customer.nanika(params) @customer.favorites => [ ・・・ ]
普通にもっと楽なやり方ありそうだけど・・。
誰が(どこで)モデルの属性値ごとに_attributes=
メソッドを生やしてるの?
メタプログラミングの領域っぽくて、それっぽい感じの場所は見つけたけど確信が得られず・・
以下それっぽいの。
define_attribute_method
define_proxy_call
- APIで検索しても出てこないけどなにこれ・・
_attributes=
はbuild
もやってくれてるっぽいけど、具体的にどう実装されてるんだろう・・・。
参考
- 作者: 井上誠一郎,奥野幹也,田中慎司,西嶋悠貴,伊藤直也,登尾徳誠,天野祐介,後藤秀宣,ヒノケン,近藤宇智朗,近藤嘉雪,渡邊恵太,堤智代,中島聡,A-Listers,はまちや2,川添貴生,WEB+DB PRESS編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/24
- メディア: 大型本
- この商品を含むブログ (2件) を見る
- 作者: Rubyサポーターズ,すがわらまさのり,寺田玄太郎,三村益隆,近藤宇智朗,橋立友宏,関口亮一
- 出版社/メーカー: 技術評論社
- 発売日: 2013/08/10
- メディア: 大型本
- この商品を含むブログ (10件) を見る