LRBlog

Logical Reality Design: Web Design and Software Development

Rails 2.1 gotcha: don’t name an ActiveRecord field ‘changes’ !

July 10, 2008

In one of my Rails projects, I maintain a user-visible log of updates to the database by recording entries into a table that looks like this (from schema.rb):

create_table "log_entries", :force => true do |t|
    t.integer  "user_id",    :limit => 11
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "item_type"
    t.integer  "source_id",  :limit => 11
    t.string   "table"
    t.string   "action"
    t.text     "changes"
  end

Whenever a record is created, updated, or deleted, I create and save an instance of LogEntry, containing for example {:table => 'task', :action => 'update'} and in the 'changes' column I save a serialized hash showing which attributes of the task object changed before and after save. In addition, I save which logged-in user made these changes, and when.

This is convenient, it gives my client a log that's much more user-accessible and allows them to easily back-trace who did what to the database and when, which is important for their process and certain certifications.

When I upgraded the project to Rails 2.1 a couple of weeks ago, most of the code still worked fine, but the hashes showing the object changes were no longer showing up in views of the log. The culprit turned out to be Rails 2.1's new dirty feature. Why? Because it adds the method 'changes' to ActiveRecord::Base. This is a great new feature that lets you know what has changed to an ActiveRecord object since you loaded it from the database, with all kinds of benefits like doing more selective updates, or not writing to the database at all if no changes have occurred, thus reducing system load if you call save! a lot.

Unfortunately, the new method 'changes' was obscuring my attribute 'changes' in some circumstances, and at the very least confusing the heck out of me, the programmer.

The solution, of course, is not to name your fields anything that corresponds to any Ruby core method or any method of ActiveRecord::Base. I fixed my problem with a migration to rename the column 'details':

def self.up
  rename_column  :log_entries, :changes, :details
end

def self.down
  rename_column  :log_entries, :details, :changes
end  

Add A Comment