在关联的after_create

时间:2016-03-24 06:34:23

标签: ruby-on-rails validation activerecord callback

我有3个非常简单的模型

class Receipt < ActiveRecord::Base
  has_many :receipt_items
end

class ReceiptItem < ActiveRecord::Base
  after_create :create_transaction
  belongs_to :receipt
private
  def create_transaction
    Transaction.new.save!
  end
end

class Transaction < ActiveRecord::Base
  validates :transacted_at, :presence => true
end

因此,每次创建一个新的ReceiptItem时,它都会触发after_create回调以使用save!创建一个新的Transaction对象。但是因为Transaction要求transacted_at列存在,所以Transaction.new.save!我应该每次都提出一个ActiveRecord :: RecordInvalid。

然后我创建了3个测试:

test "creating an invalid transaction" do
    assert_raises ActiveRecord::RecordInvalid do
        Transaction.new.save!
    end
end

test "creating invalid transaction in after_create" do
    assert_raises ActiveRecord::RecordInvalid do
        ReceiptItem.new.save!
    end
end

test "creating invalid transaction in after_create of associated model" do
    assert_raises ActiveRecord::RecordInvalid do
        r = Receipt.new
        i = r.receipt_items.new
        r.save!
    end
end

前两个测试按预期通过。然而,第三次测试失败了,因为从未提出异常。事实上,如果我在'r.save之后'添加以下行!行:

r.reload
p r.inspect
p r.receipt_items.inspect

我可以看到Receipt和ReceiptItem已成功创建。

此外,如果我更换了

assert_raises ActiveRecord::RecordInvalid do

assert_difference "Transaction.count", +1 do

我确认已创建Receipt和ReceiptItem但交易不是。这意味着交易的创建失败了,但是被忽略了,即使我使用了“保存!”而不仅仅是'保存'。

有人知道这是否是预期的行为,或者这实际上是Rails中的错误?

(在Rails 4.0.13和4.2.0中试过这个)

更新

我在这里提交了一份错误报告: https://github.com/rails/rails/issues/24301

1 个答案:

答案 0 :(得分:1)

  

整个回调链包含在一个事务中。如果有的话   回调方法返回完全错误或引发异常,   执行链停止并发出ROLLBACK;回调后   只能通过提出例外来实现这一点。

因此虽然documentation声明提出异常应该至少回滚事务(并且拒绝保存你的对象),但ActiveRecord似乎不会履行其职责。

事实证明,在after_save期间,社区已经遇到了类似的问题(您应该查看github discussion)并回滚父事务。我想到的一些解决方法(并在github线程中提到)包括:

  1. RuntimeError期间提升与非活动记录相关的内容,例如after_save

     class ReceiptItem < ActiveRecord::Base
       def create_transaction         
         raise RuntimeError unless Transaction.new.save
         # NOTE: this error gonna propagate till you rescue it somewhere manually
       end
     end
    
  2. 将您的保存包含在显式交易中:

    class ReceiptItem < ActiveRecord::Base
      belongs_to :receipt
      # NOTE: we removed after_save callback here
      private
    
      def create_transaction
        Transaction.new.save!
      end
    end
    
    
    r = Receipt.new
    i = r.receipt_items.new
    Receipt.transaction do
     r.save!
     r.receipt_items.each &:create_transaction
     # NOTE: whole transaction gonna be rolled back
    end
    
  3. 以不同的方式保存交易,然后after_save回调。您可以预先检查transaction验证部分的receipt_item有效性吗?

  4. 从我的观点来看,这种行为并非意图,因为它没有明确记录。 Rails回购所有者似乎没有看到对相应问题的重视,但它仍然值得尝试提醒他们。