We're currently hiring Developers and Designers in Boston and New York.
Find out more and apply on our jobs page.

Sep 10 2009

Quacking like an ActiveRecord in Rails 2.3.4

Posted by lolconomy

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.

A giant inflatable duck

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
blog comments powered by Disqus