Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

callbacks execution order reversed in after_*

#5650

In rails 3.0.0 the callbacks "after_*" executes
the methods in reverse order.

If in a model you have

before_save :aa, :bb
after_save :aa, :bb

def aa; puts "a"; end
def bb; puts "b"; end

saving the object you get

a
b
b
a

In rails 2.x the result was

a
b
a
b

Reported by Pietro · September 18th, 2010 @ 12:30 AM

State: resolved
Milestone: 3.0.2
Assigned to: José Valim José Valim
Importance: High

Activity

  1. Jeff Kreeftmeijer
    Jeff Kreeftmeijer

    Confirmed on master. The after_ callback order get reversed:

    class User < ActiveRecord::Base
      before_save :aa, :bb, :cc
      after_save :aa, :bb, :cc
    
      def aa; puts "a"; end
      def bb; puts "b"; end
      def cc; puts "c"; end
    end
    
    >> User.create!
    a
    b
    c
    c
    b
    a
    => #<User id: 1, created_at: "2010-09-19 12:57:03", updated_at: "2010-09-19 12:57:03">
    

    September 19th, 2010 @ 01:58 PM

  2. Neeraj Singh
    Neeraj Singh
    • Assigned user set to Neeraj Singh
    • Importance changed from to Low

    Looking into it.

    September 19th, 2010 @ 03:38 PM

  3. Pietro
    Pietro

    In activesupport-3.0.0/lib/active_support/callbacks.rb
    there is only one "reverse" (method "compile" in class "CallbackChain")

    changing

    reverse_each do |callback|

    in

    each do |callback|

    the order in after_* seems to be preserved.

    Have no idea why it was reverse_each in the first
    place.

    September 19th, 2010 @ 06:49 PM

  4. Neeraj Singh
    Neeraj Singh

    @Pietro

    Now that you have the fix could you please attach a patch.

    September 19th, 2010 @ 06:58 PM

  5. Pietro
    Pietro

    @Neeraj

    Here is the patch.

    I'll try to find in git history when the reverse_each
    first appeared.

    September 19th, 2010 @ 07:36 PM

  6. Neeraj Singh
    Neeraj Singh

    @Pietro Can you add a test please? Thanks.

    September 19th, 2010 @ 07:38 PM

  7. Pietro
    Pietro

    I think the code came from

    commit 21e7b8462113fc7c0fd1882dcc2bf7225de67b1a
    Author: Joshua Peek
    Date: Mon Oct 12 22:15:43 2009 -0500

    Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks

    Maybe Joshua can help since I have no experience
    in testing activesuport :-)
    I'll take a look to activesupport/test/callbacks_test.rb
    for inspiration...

    September 19th, 2010 @ 07:59 PM

  8. Neeraj Singh
    Neeraj Singh

    In commit http://github.com/rails/rails/commit/21e7b8462113fc7c0fd1882dcc2bf7... Johsua is refactoring the code. That commit did not introduce reverse_each .

    @Pietro your patch is breaking a number of tests. If you look at ActiveSupport tests it seems the author intended after_* callbacks to be in reverse order.

    I will ping someone from core team to have their input.

    September 20th, 2010 @ 01:40 AM

  9. Neeraj Singh
    Neeraj Singh
    • Assigned user changed from Neeraj Singh to José Valim

    Assigning it to Mr. Valim to get his input.

    September 20th, 2010 @ 01:41 AM

  10. José Valim
    José Valim
    • Milestone cleared.
    • State changed from new to open
    • Importance changed from Low to High

    The problem is this option forcing callbacks to be prepended:

    http://github.com/rails/rails/blob/master/activemodel/lib/active_mo...

    Removing this line should fix it. :)

    September 24th, 2010 @ 11:37 AM

  11. José Valim
    José Valim

    Actually, I was wrong. Because of the reverse_each, we need that prepend line. I will investigate deeper.

    September 25th, 2010 @ 10:09 AM

  12. José Valim
    José Valim

    Btw, changing reverse_each is not the solution, as it will break controller callbacks.

    September 25th, 2010 @ 10:10 AM

  13. José Valim
    José Valim

    Here is the fix:

    http://github.com/rails/rails/blob/master/activesupport/lib/active_...

    It should actually be:

    options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
    

    So when reverse_each is called the order is properly kept.

    Anyone in mood for a patch with tests?

    September 25th, 2010 @ 10:12 AM

  14. Neeraj Singh
    Neeraj Singh

    José Valim: That did not fix it.

    I have a failing test. And it is still failing :-(

    I will look into it more in the evening.

    September 25th, 2010 @ 05:07 PM

  15. José Valim
    José Valim

    Neeraj, can you attach the failing test here? This way I can debug as well!

    September 25th, 2010 @ 05:08 PM

  16. Neeraj Singh
  17. José Valim
    José Valim

    Neeraj, by default, after callbacks in AS::Callbacks are executed like in controller: the ones added first are executed later. We can change this by passing :prepend as option to set_callback, as here:

    http://github.com/rails/rails/blob/master/activemodel/lib/active_mo...

    The test case you added is in AS::Callbacks, which does not set prepend and therefore behaves as controllers. I believe that if you move the test case you added to ActiveModel::Callbacks, it will probably pass!

    September 25th, 2010 @ 05:17 PM

  18. Neeraj Singh
    Neeraj Singh

    I can try that in the evening.

    However that raises the question of after_save behaving differently in controller and model. Is this inconsistency really desired?

    September 25th, 2010 @ 05:19 PM

  19. José Valim
    José Valim

    Unfortunately this inconsistency exists since early versions of Rails. Notice that ActiveRecord in Rails 3 behaves exactly as in ActiveRecord in 2.3, the only exception is when you pass more than one parameter to after_save.

    So this currently works as in Rails 2.3:

    after_save :aa
    after_save :bb
    after_save :cc
    

    The only case which is broken (the one discussed in this ticket) is:

    after_save :aa, :bb, :cc
    

    September 25th, 2010 @ 05:31 PM

  20. Neeraj Singh
    Neeraj Singh
    • Tag set to patched

    Attached is patch with test. All the hard work for this patch was done by Mr. Valim

    September 25th, 2010 @ 11:39 PM

  21. Repository
    Repository
    • State changed from open to resolved

    September 27th, 2010 @ 10:12 PM

  22. Repository
  23. Andrea Campi
    Andrea Campi
    • Tag changed from patched to patch

    bulk tags cleanup

    October 11th, 2010 @ 07:23 AM

  24. Jeremy Kemper
  25. Ryan Bigg
    Ryan Bigg
    • Tag changed from sheepskin boots, patch to patch

    Automatic cleanup of spam.

    October 16th, 2010 @ 02:28 AM