Coding for Fun

ruby on railsを中心としたウェブ技術つれづれ日記

ちょっと複雑なファクトリーメソッドのテンプレート

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!が依存しているようなときは、あとのエラーの解釈に困るから注意。