1
votes

I am facing some issue with ordering callbacks in ActiveRecord. According to Rails guides, when creating an object the order of callbacks is as follows:

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_create
  • around_create
  • after_create
  • after_save
  • after_commit/after_rollback

I have two callbacks in my model. One for after_save (to be run on create and update) and second for after_create. On create I want the after_create callback to be run only after after_save callback. How to get this done in ActiveRecord? Thanks.

1
Well, you can't reorder callbacks. So either put your logic into after_save (or later) or drop using callbacks altogether. In favor of, for example, some kind of Builder pattern.Sergio Tulentsev
I do not see any purpose to do so as change of order of call due to rails-3 to rails-5 application upgrade. So changes as suggested by @SergioTulentsev are valid and preferable.ray
@SergioTulentsev How can I add after_create logic into after_save? after_save will be run on updates also.kanatti
@BKS: off the top of my head, you can set a flag in before_create. Something like self.record_was_just_created = true. Then check this flag in after_save. If flag is true, run the create-specific parts. Me, I would extract this into separate builder/updated objects. Should make the build procedure obvious. And avoidable, when needed.Sergio Tulentsev
@BKS you can check whether record is new or not with new_record? . so in after_save call back check self.new_record?Vishal

1 Answers

1
votes

Do not change the behavior of the rails call_backs - even if you knew how to, it makes it harder to work on the application later - change where you place the code.

You are getting too hung up on the names of the call_backs. When they run is more important.

Here is what we know about your design without making the assumptions about which are the best ways of achieving it ...

  • You have a method that runs after a new record is created
  • You have a method that runs after a old record is updated

Here is where you seem to change your design decisions in order to make programming easier - but in doing so are breaking best practices ...

  • For some reason you believe the method for updating should run every time (after_save) instead of after_update)
  • For some reason you believe the creation method should run after the method for updating (which is an unhealthy chaining of code)

Technically, the parts needed for both creating and updating should be divided out into a third method and called by both after_create & after_update so your code documents itself and is easier to understand.

All of this is relatively important - as most experienced programmers stress you should only utilize call_backs when no other way is reasonable - due to the how hard they are to conceptualization in trouble shooting obscure problems.


Solution 1 - The Rails Way

after_save should be used only for code that needs to run for both updates AND creations. Any code that needs to run for one or the other should be broken up in smaller methods & then called as needed by after_update & after_create to keep it DRY.

Solution 2 & 3 Refactoring old code without changing design

  1. Setup a working test to ensure your code fails and succeeds where you expect
  2. after_save has to run - so we know it has to be there. We wrap it in a method called 'update_method_code' to encapsulate it.
  3. Move the method definition for 'update_method_code' somewhere in model or service object.
  4. Leave the method call for 'update_method_code' in the after_save.

Since we didn't technically change anything the tests should be green.

  1. You code previously in after_create - wrap this in a method called 'new_object_code' so it's encapsulated.
  2. Move the method definition for 'new_object_code' somewhere in model or service object.

This is where you have a few choices ...

  • You can have 'update_method_code' test using one of the four activerecord lifecycle checks to test call the method 'new_object_code'. This isn't good as it creates visibility issues and means if one fails the rest fails. In a very heavy-handed way it ensures your condition of 'new_object_code' only runs after 'update_method_code'

  • You use the already built Rails methods. after_save :update_method_code on: :create to trigger the 'update_method_code'. See example here Rails Guide for transactions. Keep in mind here, this might run even if your other after_save call does not ... so once again, this method is less sound than simply following the Rails Way & using after_update / after_create.


Good luck with your project!