Description
It seems that the commit callbacks (defined in https://github.com/rails/rails/blob/master/activerecord/lib/active_record/transactions.rb#L230) is defined differently from the rest of the callbacks (defined in https://github.com/rails/rails/blob/master/activemodel/lib/active_model/callbacks.rb#L103).
I experienced this difference in the order after_commit
is executed. It seems that after_*
callbacks are executed in reverse order, and after_commit
doesn't handle this:
class M < ActiveRecord::Base
before_create :a
before_create :b
after_create :c
after_create :d
after_commit :e
after_commit :f
def a; puts 'a'; end
def b; puts 'b'; end
def c; puts 'c'; end
def d; puts 'd'; end
def e; puts 'e'; end
def f; puts 'f'; end
end
M._create_callbacks.select { |c| c.kind == :before }.map(&:filter)
=> [:a, :b]
M._create_callbacks.select { |c| c.kind == :after }.map(&:filter)
=> [:d, :c] # Weird behavior in after_* callbacks
M._commit_callbacks.select { |c| c.kind == :after }.map(&:filter)
=> [:e, :f]
M.create
=> a
=> b
=> c
=> d
=> f # The result of after_commit not handling the weird behavior
=> e
after_*
callbacks add a default (actually, they override) prepend: true
to the options to resolve this (see here https://github.com/rails/rails/blob/master/activemodel/lib/active_model/callbacks.rb#L139), while after_commit
doesn't, thus the inconsistency.
The culprit seems to be the reverse execution in after_*
callbacks and not really after_commit
, I would say the code in after_*
is just a patch to solve this. This patch actually prevents using the :prepend
option in the after_*
callbacks.
I would also change the definition of the transactions callbacks to use the same logic of the rest of the callbacks to prevent inconsistencies in the future.