2008-08-01 [長年日記]

_ [rails] :dependent => :delete で transaction なテスト?

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
[]

«前の日記(2008-06-30) 最新 次の日記(2008-09-01)»