ちょっと複雑なファクトリーメソッドのテンプレート
Railsでアソシエーションとバリデーションが複雑に絡み合ったモデルを保存するときには、モデルにファクトリーメソッドを作るとよいと言われています。そういうときの要求はいくつかあります。
- バリデーションに失敗したときは、たとえアソシエーションの奥で失敗したとしても、そのことを教えてほしい。
- 500ではなくて、エラーはちゃんと綺麗に教えてほしい。
- どこかで失敗したら全部キャンセルしてほしい。
こういう中級イディオムを見つけ出すのは時間がかかりますが、とりあえずの物を書いておきます。create_with_managerメソッドがファクトリーメソッドで、バリデーションに応じて真偽値を返します。失敗したときは理由をstore.errorsに保存します。
class Store < ActiveRecord::Base has_many :manager_stores has_many :managers, :through => :manager_stores validates :managers, :presence => true def create_with_manager(manager) Store.transaction do begin manager.save! save(:validate => false) add_manager(manager) save! rescue => e errors.add(:base, e) raise ActiveRecord::Rollback end end end def add_manager(manager) (失敗したらエラーを起こす複雑なメソッド) end end
基本的にはsave!で失敗したらエラーを起こすようにする。でも、これだとバリデーションエラーは一度に一つしか発見できません。入力に複数のエラーがあったときまとめてエラーを表示したいなら、
unless manager.save errors.add(:manager, manager.errors) end ... if errors.length > 0 raise ActiveRecord:Rollback end
とかやると、失敗したときにすべてのエラーをコントローラーに返せる。
前のsave!の成功に次のsave!が依存しているようなときは、あとのエラーの解釈に困るから注意。