DEV Community

Cover image for Automating `updated_at` Timestamps with `update_all` in Ruby on Rails
Myungwoo Song
Myungwoo Song

Posted on • Updated on

Automating `updated_at` Timestamps with `update_all` in Ruby on Rails

Introduction

Ruby on Rails greatly simplifies many programming tasks, yet sometimes its behavior can diverge from expectations. A notable example is when using the update_all method. This method, while efficient, does not automatically update the updated_at column in a database record. This oversight can lead to difficulties in tracking changes, particularly when auditing data history. This article presents a practical solution to this issue.

Understanding Ruby's Handling of Modules and Classes

Ruby's approach to modules and classes is fundamental to understanding the solution. In Ruby, a class can inherit features from a parent class and incorporate modules. This is achieved through a super pointer in each class, which references its parent, and a method_table that contains its methods. When a method is invoked, Ruby searches the method_table and, if necessary, proceeds up the inheritance chain. Including a module into a class inserts the module's methods into this lookup process, allowing for flexible and powerful customization.

The Art of Monkey Patching

'Monkey patching' refers to dynamically modifying or extending a class. In our case, we want Ruby to execute custom code before the native update_all method. This reverse lookup—checking our module first and then the original class—is made possible through the prepend keyword. By prepending our module, CustomUpdateAll, to ActiveRecord::Relation, we ensure that our version of update_all is found first. This technique is particularly useful in Rails, where code in config/initializer is executed after the framework has loaded.

Enhanced update_all Code

I first want to acknowledge that this is based on and inspired by Choncou's Code with some slight modifications of my own.

# config/initializer/custom_update_all.rb
module CustomUpdateAll
  def update_all(updates)
    # Check if updates should modify the updated_at column
    if updates.is_a?(Hash) && !updates.delete(:skip_touch) && column_names.include?("updated_at")
      super(updates.merge(updated_at: Time.zone.now)) # Merge our custom timestamp
    else
      super # Call the original update_all method
    end
  end
end

ActiveRecord::Relation.prepend(CustomUpdateAll)
Enter fullscreen mode Exit fullscreen mode

Further Exploration

While this solution addresses hash-based updates, it can be expanded to handle strings and arrays. If you want, experiment and adapt the code to these formats, sharing your modifications and insights.

Conclusion

In summary, this article detailed a method to ensure updated_at is automatically updated when using update_all in Ruby on Rails. This solution enhances data integrity and simplifies record auditing. Your feedback and experiences with this approach are warmly welcomed.

References

Choncou's Code

Top comments (0)