leena
leena

Reputation: 302

`after_commit` callback on `update` doesn't trigger

I have defined after_commit callback on update. It doesn't trigger in rspec.

Here is my callback :

after_commit :notify_trip, :if => Proc.new { |trip| trip.can_send_schedule_notification? }, on: :update

Here is my rspec code :

@work_order.update_attributes(:status_id => statuses(:scheduled).id, :tour_id => @tour.id)
@work_order.run_callbacks(:commit)
assert_equal 1, ActionMailer::Base.deliveries.size
expect(ActionMailer::Base.deliveries[0].to).to include(@user.email)
expect(ActionMailer::Base.deliveries[0].subject).to include("Your trip has been scheduled!")

Here callback is not calling and ActionMailer::Base.deliveries.size returning 0

Is there any suggestion on this.

Upvotes: 7

Views: 5763

Answers (3)

mate89
mate89

Reputation: 166

For me, I found the answer in this comment: https://stackoverflow.com/a/75404278/5808008

They key part is instance_variable_set(:@_trigger_update_callback, true)

Upvotes: 0

Viktor Nonov
Viktor Nonov

Reputation: 1512

TL;DR

@work_order.update_attributes(:status_id => statuses(:scheduled).id, :tour_id => @tour.id)
@work_order.instance_variable_set(:@_start_transaction_state, {})
@work_order.run_callbacks(:commit)

Explanation

I had a same similar situation with Rails 4.0.1:

@work_order = WorkOrder.create(status_id: 1, order_id: 2)
@work_order.update_attributes(status_id: 4, order_id: 5)
@work_order.run_callbacks(:commit)

when the model implementation look something like this:

class WorkOrder < ActiveRecord::Base
  after_commit :notify_trip, :if => Proc.new { |trip| trip.can_send_schedule_notification? }, on: :update
  after_commit :do_something, on: :create
end

and every time when I call @work_order.run_callbacks(:commit) then it would run the after_commit on create method - do_something. That happens because after the @work_order is created by (@work_order = WorkOrder.create(status_id: 1, order_id: 2)) a instance variable called @_start_transaction_state is initialized, but never cleared. @_start_transaction_state is used in ActiveRecord::Transaction module to track a transaction process as far as I understood from reading the code.

So before calling run_callbacks if we clear up @_start_transaction_state, then we will be able to run the after_commit on: update callbacks

@work_order.instance_variable_set(:@_start_transaction_state, {})
@work_order.run_callbacks(:commit) 

There's one more option which is a little bit cleaner:

@work_order.send(:clear_transaction_record_state)
@work_order.run_callbacks(:commit) 

I know that the solution is hacky and I'm not sure if that might introduce some side effects, especially with nested transactions, but was the only variant that worked for me in Rails 4.0.1.

Upvotes: 6

sadaf2605
sadaf2605

Reputation: 7540

According to documentation, you need to use the test_after_commit gem to have after_commit hooks get fired in tests. this will be no longer needed on rails 5.0+.

Another alternative could be putting following code at the end of the body of the it block:

subject.run_callbacks(:commit)

Upvotes: 4

Related Questions