Quacking like an ActiveRecord in Rails 2.3.4

Mike Burns

You know everything about pretending to be an ActiveRecord::Base object, and especially the bit about ActiveRecord::Errors. That part’s really cool.

What we are doing here is making an object that quacks like an ActiveRecord::Base object but has no database table backing it. As examples: a search object, a credit card object, or a remote user object. To do this, you need id, new_record?, and the attributes must work in a specific fashion. You can also have errors produce an ActiveRecord::Errors instance so user errors will show in forms and whatnot.

However you may not have noticed that Rails 2.3.4 broke API compatibility for ActiveRecord::Errors. If your ActiveRecord::Base-like class provides an #errors instance method, it must now provide these class methods:

  • self_and_descendants_from_active_record, which produces an array of classes.
  • human_name, which takes an optional hash and produces a string.
  • human_attribute_name, which takes a string and optional hash and produces a string.

The last two of these methods are useful in your day-to-day Rails knowledge (the first is an internal, undocumented method used by Rails to produce the class itself and its parents, up to and excluding ActiveRecord::Base).

human_name is used to produce a humanized string representing the class/table name. If your class is named AuthenticationRecord, .human_name will produce "Authenticationrecord". Any options you pass to this are normally sent to I18n.translate.

human_attribute_name is used to map an attribute to a human-readable string. For example, the attribute created_at is mapped to "Created at". Any options passed to this are normally sent to I18n.translate.

Here are some tests that need to pass now:

class SearchTest < ActiveSupport::TestCase
  should "conform to the ActiveRecord::Errors interface" do
    assert_respond_to Search, :self_and_descendants_from_active_record
    assert_respond_to Search, :human_name
    assert_respond_to Search, :human_attribute_name

    assert_equal [Search], Search.self_and_descendants_from_active_record
    assert_equal "Search", Search.human_name
    assert_equal "Published at", Search.human_attribute_name('published_at')
  end
end