class Foo < ActiveRecord::Base
has_one :bar, :dependent => :delete
def before_destroy
if self.class.count == 1
raise
end
end
end
class Bar < ActiveRecord::Base
belongs_to :foo
end
どこにでもある関連。最後の Foo は消さないでね、という状況。
さて、
>> [Foo, Bar].each(&:delete_all)
>> 2.times{Foo.create.bar = Bar.create}
>> [Foo, Bar].map(&:count)
=> [2, 2]
>> Foo.find(:all, :include => :bar).each(&:destroy)
RuntimeError:
(snip)
>> [Foo, Bar].map(&:count)
=> [1, 1]
最後の Foo は、Bar とともに生き残っている。でもこれ、test が書けなくない?
$ cat test/unit/foo_test.rb
require File.dirname(__FILE__) + '/../test_helper'
class FooTest < Test::Unit::TestCase
def test_truth
[Foo, Bar].each(&:delete_all)
2.times{Foo.create.bar = Bar.create}
assert_equal 2, Foo.count
assert_equal 2, Bar.count
assert_raise(RuntimeError){
Foo.find(:all, :include => :bar).each(&:destroy)
}
assert_equal 1, Foo.count
assert_equal 1, Bar.count, "last Bar should be alive, as well"
end
end
$ ruby test/unit/foo_test.rb
Loaded suite test/unit/foo_test
Started
F
Finished in 0.28703 seconds.
1) Failure:
test_truth(FooTest) [test/unit/foo_test.rb:13]:
last Bar should be alive, as well.
<1> expected but was
<0>.
1 tests, 5 assertions, 1 failures, 0 errors
本来なら、transaction が効いて Bar も保護されるはずなんだけど、 test 環境では、初っ端から transaction に入って最後に抜けてロールバック、 という感じなので、テスト中には Bar が戻ってこないですよね。:delete じゃなくて :destroy でも同じで、:nullify だと <1>-<0> が <2>-<1> になるだけ。
[追記]: そうか。:dependent って結局はクラスメソッドの方の before_destroy なので、 それより先にクラスメソッドの方の before_destroy でセットしとけばいいのか。 これで上に書いたテストが通りました。 でも、ここに書くなら、raise じゃなくて、エラーをセットして false を返す方がいいのかも。
class Foo < ActiveRecord::Base
before_destory do |record|
if record.class.count == 1
record.errors.add(:base, "save the last Foo")
false
end
end
has_one :bar, :dependent => :delete
end