Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.
@gmcintire Passenger may be setting GEM_HOME differently -- can you print out ENV["GEM_HOME"] in passenger?
ActiveModel: Make Any Ruby Object Feel Like ActiveRecord
January 10th, 2010
Rails 2.3 has a ton of really nice functionality locked up in monolithic components. I’ve posted quite a bit about how we’ve opened up a lot of that functionality in ActionPack, making it easier to reuse the router, dispatcher, and individual parts of ActionController. ActiveModel is another way we’ve exposed useful functionality to you in Rails 3.
Before I Begin, The ActiveModel API
Before I begin, there are two major elements to ActiveModel. The first is the ActiveModel API, the interface that models must adhere to in order to gain compatibility with ActionPack’s helpers. I’ll be talking more about that soon, but for now, the important thing about the ActiveModel API is that your models can become ActiveModel compliant without using a single line of Rails code.
In order to help you ensure that your models are compliant, ActiveModel comes with a module called ActiveModel::Lint that you can include into your test cases to test compliance with the API:
class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests class CompliantModel extend ActiveModel::Naming def to_model self end def valid?() true end def new_record?() true end def destroyed?() true end def errors obj = Object.new def obj.[](key) [] end def obj.full_messages() [] end obj end end def setup @model = CompliantModel.new end end
The ActiveModel::Lint::Tests provide a series of tests that are run against the @model, testing for compliance.
ActiveModel Modules
The second interesting part of ActiveModel is a series of modules provided by ActiveModel that you can use to implement common model functionality on your own Ruby objects. These modules were extracted from ActiveRecord, and are now included in ActiveRecord.
Because we’re dogfooding these modules, you can be assured that APIs you bring in to your models will remain consistent with ActiveRecord, and that they’ll continue to be maintained in future releases of Rails.
The ActiveModel comes with internationalization baked in, providing an avenue for much better community sharing around translating error messages and the like.
The Validations System
This was perhaps the most frustrating coupling in ActiveRecord, because it meant that people writing libraries for, say, CouchDB had to choose between painstakingly copying the API over, allowing inconsistencies to creep in, or just inventing a whole new API.
Validations have a few different elements.
First, declaring the validations themselves. You’ve seen the usage before in ActiveRecord:
class Person < ActiveRecord::Base validates_presence_of :first_name, :last_name end
To do the same thing for a plain old Ruby object, simply do the following:
class Person include ActiveModel::Validations validates_presence_of :first_name, :last_name attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name, @last_name = first_name, last_name end end
The validations system calls read_attribute_for_validation to get the attribute, but by default, it aliases that method to send, which supports the standard Ruby attribute system of attr_accessor.
To use a more custom attribute lookup, you can do:
class Person include ActiveModel::Validations validates_presence_of :first_name, :last_name def initialize(attributes = {}) @attributes = attributes end def read_attribute_for_validation(key) @attributes[key] end end
Let’s look at what a validator actually is. First of all, the
def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end
You can see that validates_presence_of is using the more primitive validates_with, passing it the validator class, merging in {:attributes => attribute_names} into the options passed to the validator. Next, the validator itself:
class PresenceValidator < EachValidator def validate(record) record.errors.add_on_blank(attributes, options[:message]) end end
The EachValidator that it inherits from validates each attribute with the validate method. In this case, it adds the error message to the record, only if the attribute is blank.
The add_on_blank method does add(attribute, :blank, :default => custom_message) if value.blank? (among other things), which is adding the localized :blank message to the object. If you take a look at the built-in locale/en.yml looks like:
en:
errors:
# The default format use in full error messages.
format: "{{attribute}} {{message}}"
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
confirmation: "doesn't match confirmation"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
too_long: "is too long (maximum is {{count}} characters)"
too_short: "is too short (minimum is {{count}} characters)"
wrong_length: "is the wrong length (should be {{count}} characters)"
not_a_number: "is not a number"
greater_than: "must be greater than {{count}}"
greater_than_or_equal_to: "must be greater than or equal to {{count}}"
equal_to: "must be equal to {{count}}"
less_than: "must be less than {{count}}"
less_than_or_equal_to: "must be less than or equal to {{count}}"
odd: "must be odd"
even: "must be even"As a result, the error message will read first_name can't be blank.
The Error object is also a part of ActiveModel.
Serialization
ActiveRecord also comes with default serialization for JSON and XML, allowing you to do things like: @person.to_json(:except => :comment).
The main important part of the serialization support is adding general support for specifying the attributes to include across all serializers. That means that you can do @person.to_xml(:except => :comment) as well.
To add serialization support to your own model, you will need to include the serialization module and implement attributes. Check it out:
class Person include ActiveModel::Serialization attr_accessor :attributes def initialize(attributes) @attributes = attributes end end p = Person.new(:first_name => "Yukihiro", :last_name => "Matsumoto") p.to_json #=> %|{"first_name": "Yukihiro", "last_name": "Matsumoto"}| p.to_json(:only => :first_name) #=> %|{"first_name": "Yukihiro"}|
You can also pass in a :methods option to specify methods to call for certain attributes that are determined dynamically.
Here's the Person model with validations and serialization:
class Person include ActiveModel::Validations include ActiveModel::Serialization validates_presence_of :first_name, :last_name attr_accessor :attributes def initialize(attributes = {}) @attributes = attributes end def read_attribute_for_validation(key) @attributes[key] end end
Others
Those are just two of the modules available in ActiveModel. Some others include:
AttributeMethods: Makes it easy to add attributes that are set liketable_name :fooCallbacks: ActiveRecord-style lifecycle callbacks.Dirty: Support for dirty trackingNaming: Default implementations ofmodel.model_name, which are used by ActionPack (for instance, when you dorender :partial => modelObserving: ActiveRecord-style observersStateMachine: A simple state-machine implementation for modelsTranslation: The core translation support
This mostly reflects the first step of ActiveRecord extractions done by Josh Peek for his Google Summer of Code project last summer. Over time, I expect to see more extractions from ActiveRecord and more abstractions built up around ActiveModel.
I also expect to see a community building up around things like adding new validators, translations, serializers and more, especially now that they can be reused not only in ActiveRecord, but in MongoMapper, Cassandra Object, and other ORMs that leverage ActiveModel's built-in modules.

Shimon Amit, Posted January 11, 2010, 1:59 am
Very exciting! Interesting to a StateMachine in there. Will that be included in the default ActiveRecord Rails 3.0 configurations?
Kieran P, Posted January 11, 2010, 2:11 am
Excellent post. Nice to be able to use these outside of Rails (like Sinatra).
Sammy, Posted January 11, 2010, 2:13 am
AM#new_record? sounds odd for me. There is no records for example in a CouchRest or Mongo. Why not rename it to AM#new? or something more appropriate?
kain, Posted January 11, 2010, 3:07 am
kinda cool, thanks for sharing
Gerard, Posted January 11, 2010, 3:07 am
hrmm.
“…These modules were extracted from ActiveRecord, and are now included in ActiveRecord…”
Was that a lot of work?
John Topley, Posted January 11, 2010, 3:37 am
Presumably ActiveResource will be also implement the ActiveModel API?
DAddYE, Posted January 11, 2010, 4:05 am
Yehuda,
your are a fan of datamapper, why activemodel can’t act as dm?
Can be beautiful see in AR:
property :id, Serial
…
Thanks for the post!
Michelangelo Altamore, Posted January 11, 2010, 6:25 am
I think that ActiveModel ensures a minimal interface to which an ORM should adhere. IMHO it is reasonable to not force that the single point of thruth resides within the model (even if it may be the most logical place) as long as it is somewhere. In the AR case it is obtained by reflection on the database layer. So I like the actual design decision even if I prefer the datamapper way for declaring model properties.
Francis Hwang, Posted January 11, 2010, 7:59 am
Thanks for the info, Yehuda. It looks like it will be easier to introspect a model’s validations from the outside, then? This is something I keep wanting and then writing some nasty workaround for.
Francis Hwang, Posted January 11, 2010, 7:59 am
Thanks for the info, Yehuda. It looks like it will be easier to introspect a model’s validations from the outside, then? This is something I keep wanting and then writing some nasty workaround for.
Raphaƫl Valyi, Posted January 11, 2010, 7:59 am
Hello, very cool you did that finally.
However, I’ve got a question: how is ActiveModel being integrated with ActiveResource? It seems essential to me that one can build an ActiveResource that is ActiveModel compliant. Will ActiveResource be compliant by default, will it be somewhat rewritten to account for it when it’s enabled? Thank you for any clarification.
The reason I’m asking this is that I’m the creator of the OOOR ActiveResource wrapper over OpenERP business objects http://github.com/rvalyi/ooor (they also deal with relations and inheritance). We wanted ActiveModel for long to port directly into the Rails proxies the validations (and hopefully more) from OpenERP.
George Palmer, Posted January 11, 2010, 10:53 am
Great post Yehuda. How will validates_uniqueness_of work in terms it needs to go back to database to check that’s it unique? This was something that was problematic writing CouchFoo.
Elliot Winkler, Posted January 11, 2010, 11:07 am
Woohoo! That stuff looks awesome. I especially like ActiveModel::Lint — it’s like code teaching you how to code!
Ryan Angilly, Posted January 11, 2010, 12:31 pm
This is some hot stuff, Yehuda. Awesome job.
Ben Johnson, Posted January 12, 2010, 10:55 am
Thanks for the post Yehuuda. Quick question. I really want to use this in authlogic. What would you recommend the best way to do this while also supporting previous versions of rails? My only thought is to include the module with the library and load it if needed. That’s what I’ve saw other libraries do when named_scopes was a new feature. Thanks again!
Arj, Posted January 14, 2010, 1:46 am
This looks perfect for something I might have to do for an upcoming project.
Dynamic Models based on data retrieved from an API. Would this be a reasonable way of doing it? creating a model based on the data and then eval’ing it to create an ActiveModel class?
Steve, Posted April 14, 2010, 11:47 am
You might want to update your post to reflect that StateMachine has been taken out… :(
ken, Posted April 21, 2010, 4:38 pm
I’m a rails rookie. I’m surprised there isn’t validation support for the built-in date/time types. (I’ll try this plugin
http://github.com/adzap/validates_timeliness/
)
It would be nice if rails came with more string types (email, url, etc) & numeric types (currency, distance, etc).
The lack of this ripples so the live-validations plugin doesn’t do this either.