我有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
答案 0 :(得分:1)
整个回调链包含在一个事务中。如果有的话 回调方法返回完全错误或引发异常, 执行链停止并发出ROLLBACK;回调后 只能通过提出例外来实现这一点。
因此虽然documentation声明提出异常应该至少回滚事务(并且拒绝保存你的对象),但ActiveRecord似乎不会履行其职责。
事实证明,在after_save
期间,社区已经遇到了类似的问题(您应该查看github discussion)并回滚父事务。我想到的一些解决方法(并在github线程中提到)包括:
在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
将您的保存包含在显式交易中:
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
以不同的方式保存交易,然后after_save
回调。您可以预先检查transaction
验证部分的receipt_item
有效性吗?
从我的观点来看,这种行为并非意图,因为它没有明确记录。 Rails回购所有者似乎没有看到对相应问题的重视,但它仍然值得尝试提醒他们。