【问题标题】:Rails ActiveRecord::RecordInvalid exception not raised in after_create of associationRails ActiveRecord::RecordInvalid 异常未在关联的 after_create 中引发
【发布时间】:2016-07-11 16:55:28
【问题描述】:

我有 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 和 ReceiptItems 但未创建 Transaction。这意味着交易的创建失败,但被默默地忽略了,即使我使用了“保存!”而不是仅仅“保存”。

有谁知道这是预期的行为,还是这实际上是 Rails 中的错误?

(在 Rails 4.0.13 和 4.2.0 中尝试过)

更新

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

【问题讨论】:

  • 你能用p r.inspectp r.receipt_items.inspect的输出更新你的问题吗?
  • 当然,它们是:“#" "#<:associations::collectionproxy id: created_at: updated_at:>]>"

标签: ruby-on-rails validation activerecord callback


【解决方案1】:

整个回调链被包装在一个事务中。如果之前有 回调方法完全返回 false 或引发异常, 执行链停止并发出 ROLLBACK;回调后 只能通过引发异常来实现。

因此,尽管documentation 声明引发异常至少应该回滚事务(并拒绝保存您的对象),但 ActiveRecord 似乎没有履行其职责。

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

  1. after_save 期间提出与活动记录无关的内容,例如RuntimeError

     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 回调不同的方式保存事务。您是否可以在receipt_item 的验证部分预先检查transaction 的有效性?

在我看来,这种行为不是有意的,因为它没有在任何地方明确记录。 Rails repo 所有者似乎没有注意到相应的问题,但仍然值得尝试提醒他们。

【讨论】:

  • 感谢您的回答@twonegatives。 #1 有效,但只能作为短期破解。 #2 使我的代码复杂化,因为在某些模型中我有很多回调,而且我在不同的地方这样做,所以事情可能很快就会失控。 #3 破坏了我的代码的模块化,所以这不是我想做的事情。所以看起来这确实是 Rails 中的一个错误,我将向他们提交错误报告。再次感谢您。
猜你喜欢
  • 2016-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多