@loumarven Disciple. Husband. Dad. Dev. Learner.

Inifinite Loop in Rails when Calling update_attribute in an after_save Callback

So I have been learning Ruby on Rails these past months, and I’m steadily learning the basics of the said framework. In this blog, I would like to share about one of the issues I encountered while building an app and the solution I found for the problem.

The Problem

I encountered an infinite loop issue in my Rails app. What I’m basically trying to do is update a column in a table for a model after its nested attributes are saved. I placed my computation in an after_save callback like this (method name arbitrary):

# app/models/nested_model.rb
after_save :update_the_column
# …
def update_the_column
  the_value = compute_value
  parent_model.update_attribute(:the_column, the_value)
end

I would complete the form (with nested attributes) and submit it but it seemed stuck and would not redirect. So I looked at the logs and there I realized that part of my code was executing infinitely. Time to investigate why.

First thing I did after learning about the problem was to practice technical sophistication (DuckDuckGoing, really). The top search result, unsurprisingly, was from our good friends from over Stack Overflow. The question was asked many years ago, so I was unsure if the answers would help or lead me to finding a solution for my problem. Still I kept reading the answers and learned what was causing the issue.

The Root Cause

Calling update_attribute in an after_save callback invokes the save method which then invokes the after_save callback, thus creating an infinite loop. See source code for details.

Looking at update_attribute’s description in the official Rails documentation, we see that:

  • Validation is skipped.
  • Callbacks are invoked.
  • updated_at/updated_on column is updated if that column is available.
  • Updates all the attributes that are dirty in this object.

The Solution

To solve my infinite loop issue, I made the following changes in my code based on this answer in the same thread.

# app/models/nested_model.rb
after_commit :update_the_column
# …
def update_the_column
  the_value = compute_value
  parent_model.update_column(:the_column, the_value)
end

I changed the callback from after_save to after_commit, so my computation is only triggered when the database transaction (commit) is complete. Updating the column in the table was done through update_column. From its description, we see that for update_column/s:

  • Validations are skipped.
  • Callbacks are skipped.
  • updated_at/updated_on are not updated.

Applying the changes above solved my issue. While it may not be “The Rails Way” of solving the said problem, I am just glad to have solved this issue and I have learned a few things as I progress in learning Ruby on Rails. If you have the same problem and are considering the solution above, take into account what is mentioned in the method’s description and tweak as you see fit for your application’s context.

Lessons Learned

  • Read the application logs. They provide clues to what is going on in your code.
  • Look around for similar issues. Someone most likely have already encountered the same thing and have found a solution.
  • Understand what was causing the issue.
  • Understand how the changes solves your issue.
  • Read the guides!

I still find the last one intimidating to do most of the time, but reading the guides have really helped a lot and I’ve noticed that they’re beginning to make sense to me more and more.

P.S.

For the experienced Rails developers here, what is the “Rails way” or recommended approach to solve this issue? I’d love to learn from you.