Prevent an Infinite Loop When Using after_save in Rails

Infinite loops are fun, said no one ever.

If you're getting an error message like SystemStackError (stack level too deep) it means you have an infinite loop somewhere in your code. I'll briefly explain what's going on but you can skip to how to fix your after_save.

One of the most common causes for this is due to the after_save callback.

What's happening is you save a model, the after_save callback is triggered and runs some additional code that also saves the model, and now your callback is run again because it keeps saving the model, and on and on.

It's important to note, it's likely your changes save either because the database will rollback any pending changes:

AWS Made Easy
AWS Made Easy
How to learn AWS quickly and easily.
AWS Made Easy
LEARN MORE
   (0.3ms)  ROLLBACK
Traceback (most recent call last):
       16: from app/models/modal.rb:88:in `add_thing'
       15: from app/models/modal.rb:88:in `add_thing'
       14: from app/models/modal.rb:88:in `add_thing'
       13: from app/models/modal.rb:88:in `add_thing'
       12: from app/models/modal.rb:88:in `add_thing'
       11: from app/models/modal.rb:88:in `add_thing'
       10: from app/models/modal.rb:88:in `add_thing'
        9: from app/models/modal.rb:88:in `add_thing'
        8: from app/models/modal.rb:88:in `add_thing'
        7: from app/models/modal.rb:88:in `add_thing'
        6: from app/models/modal.rb:88:in `add_thing'
        5: from app/models/modal.rb:88:in `add_thing'
        4: from app/models/modal.rb:88:in `add_thing'
        3: from app/models/modal.rb:88:in `add_thing'
        2: from app/models/modal.rb:88:in `add_thing'
        1: from app/models/modal.rb:88:in `add_thing'
SystemStackError (stack level too deep)


How to Fix an after_save Infinite Loop

First, it depends on what specifically your after_save call needs to do.

If it changes a different model, you'll need to investigate that other model and look into its callbacks.

But the easiest way to come across this error is by calling an after_save that continually triggers itself. Specifically, the callback makes another update to the model that triggers another save.

So the easiest way to break this loop is to only make the update if the value changes.

HERE'S THE CODE

update(computed_value: new_value) if computed_value != new_value

Ending a statement with a condition if statement like this is also known as a guard clause.

By using a guard clause on callbacks, you ensure that your update doesn't run if the value won't change. So if our after_save calls this update, it might run once, but the next time it will likely determine that the old and new values are the same so it won't trigger a save on the model and your callback will not loop forever.

Note that if your callback is doing anything dynamic like generating a random number or including timestamps, the old and new values always be different and still trigger updates and saves. Keep these exceptions in mind when designing your guard clause.

Featured
Level up faster
Hey, I'm Nick Dill.

I help people become better software developers with daily tips, tricks, and advice.
Related Articles
More like this
How to Export to CSV with Ruby on Rails
Adding Active Storage to your Rails Project
What is MVC and Why Should I Use It?