入れ子モデルフォーム
元記事はこちら
翻訳
Bad Smell
class Product < ActiveRecord::Base has_one :detail end class Detail < ActiveRecord::Base belongs_to :product end <% form_for :product do |f| %> <%= f.text_field :title %> <% fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %> <% end %> class ProductsController < ApplicationController def create @product = Product.new(params[:product]) @detail = Detail.new(params[:detail]) Product.transaction do @product.save! @detail.product = @product @detail.save end end end
ProductモデルとDetailモデルには1対1の関係があり、productと一緒にmanufacturerも保存したいとします。この場合、productオブジェクトとdetailオブジェクトを生成し、トランザクションによって関連付けます。でも、なんでこんな込み入った処理をコントローラーでやっているのでしょう。Productモデルがdetailの生成を担当すべきです。
Refactor
class Product < ActiveRecord::Base has_one :detail accepts_nested_attributes_for :detail end <% form_for :product do |f| %> <%= f.text_field :title %> <% f.fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %> <% end %> class ProductsController < ApplicationController def create @product = Product.new(params[:product]) @product.save end end
Productモデルに、accepts_nested_attributes_forを追加したので、ProductモデルはDetailモデルの生成を制御できるようになりました。これは良いことです!
accepts_nested_attributes_forのおかげで、1対多の関係における生成も簡単にできるようになります。
class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasks end class Task < ActiveRecord::Base belongs_to :project end <% form_for @project do |f| %> <%= f.text_field :name %> <% f.fields_for :tasks do |tasks_form| %> <%= tasks_form.text_field :name %> <% end %> <% end %>
railsに素晴らしいメソッドが用意されていて、実にありがたいことです!
感想
Railsガイドの関連付けに関する章を最近読みながら書いてますが、いまいち理解できていない。 関連付けの仕方はわかってきたものの、生成のさせ方と検索時に取れるレコードの形(Assosiation/Array)の判別がいまいち腑に落ちていないというか、モヤっとしている。 今日もちょうどBadSmellなコードを書いてしまったので、テスト書いてリファクタリングせねば。。