Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails ActiveRecord::RecordInvalid exception not raised in after_create of association

I have 3 very simple models

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

So every time a new ReceiptItem is created, it triggers the after_create callback to create a new Transaction object using save!. But because Transaction requires that the column transacted_at to be present, Transaction.new.save! should raise an ActiveRecord::RecordInvalid every time, I assumed.

So then I created 3 tests:

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

The first two tests passed as expected. The third test, however, failed because the exception was never raised. As a matter of fact, if I add the following lines after the 'r.save!' line:

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

I could see that the Receipt and the ReceiptItem have been created successfully.

Furthermore, if I replaced

assert_raises ActiveRecord::RecordInvalid do

with

assert_difference "Transaction.count", +1 do

I confirmed that the Receipt and the ReceiptItems were created but the Transaction wasn't. That means the creation of the Transaction failed, but was silently ignored, even though I used 'save!' as opposed to just 'save'.

Does anyone know if this is the intended behaviour, or is this actually a bug in Rails?

(Tried this in Rails 4.0.13 and 4.2.0)

Update

I've filed a bug report here: https://github.com/rails/rails/issues/24301

like image 891
mountriv99 Avatar asked Dec 17 '25 21:12

mountriv99


1 Answers

The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.

So although documentation states that raising an exception should at least roll the transaction back (and refuse to save your object), ActiveRecord seems not to discharge its duties.

It turns out that community has already met the similar issue (you should probably check out a github discussion) with rolling back parent transaction during after_save. Some workarounds to this coming to my mind (and mentioned at github thread) include:

  1. Raising something non-active-record-related, like RuntimeError during 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. Wrapping up your saving into an explicit transaction:

    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. Saving a transaction in a different manner then after_save callback. May be you could pre-check a transaction validness in validation section of receipt_item?

As of my point of view, this behaviour is not intended as it is not documented anywhere explicitly. Rails repo owners seem to not sight a significant attention to the corresponding issue, but it's still worth a try to remind them about it.

like image 72
twonegatives Avatar answered Dec 19 '25 11:12

twonegatives



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!