Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`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.

like image 342
leena Avatar asked Nov 26 '15 13:11

leena


1 Answers

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.

like image 102
Viktor Nonov Avatar answered Oct 14 '22 23:10

Viktor Nonov