Rails 4.0 – current master branch at the time of this writing – has recently got a small – yet very useful – addition: ActiveModel::Model
. The implementation is really simple, as you can see below:
module ActiveModel
module Model
def self.included(base)
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
end
end
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
def persisted?
false
end
end
end
Quite straightforward, huh? But what does it do, and what are we supposed to do with it?
ActiveModel::Model: Basic Model implementation
According to the docs, ActiveModel::Model
includes all the required interface for an object to interact with ActionPack
, using different ActiveModel
modules. It includes model name instrospection, conversions, translations and validations. In addition to that, it allows you to initialize the object with a hash of attributes, pretty much like ActiveRecord
does.
Wait, what? In short: you can easily extend ActiveModel::Model
in a normal Ruby class and use instances of that class with helpers like form_for
, dom_id / dom_class
, and any other ActionView
helper, as you do with ActiveRecord
objects. It also gives you known method helpers such as human_attribute_name
.
A minimal implementation could be:
class Person
include ActiveModel::Model
attr_accessor :name, :age
validates_presence_of :name
end
person = Person.new(:name => 'bob', :age => '18')
person.name # => 'bob'
person.age # => 18
person.valid? # => true
This is really handy, considering that before this addition, we’d have to add all that code to have a model up and running to use with ActionView's form_for
, for instance. Ok, it is not that much code to add, but now we don’t even need to remember which modules are required for such integration. And I have to add that I’ve been creating similar classes in different applications lately. Take a moment to think about a contact form, that does not need to be tied to a database: it’s a common scenario to implement using ActiveModel::Model
.
Extending Basic Model even more
Note that, by default, ActiveModel::Model
implements persisted?
to return false
, which is the most common case. For instance, when used with form_for
, this means that the generated url would post
to the create
action. You may want to override it in your class to simulate a different scenario:
class Person
include ActiveModel::Model
attr_accessor :id, :name
def persisted?
self.id == 1
end
end
person = Person.new(:id => 1, :name => 'bob')
person.persisted? # => true
Besides that, if for some reason you need to run code on initialize
, make sure you call super if you want the attributes hash initialization to happen.
class Person
include ActiveModel::Model
attr_accessor :id, :name, :omg
def initialize(attributes)
super
@omg ||= true
end
end
person = Person.new(:id => 1, :name => 'bob')
person.omg # => true
And remember that, at the end, this is all Ruby: you can include any other module of your own and other ActiveModel
modules easily in your class. For instance, lets add callbacks
to our model to mimic ActiveRecord's save
functionality:
class Person
include ActiveModel::Model
extend ActiveModel::Callbacks
define_model_callbacks :save
attr_accessor :id, :name
# Just check validity, and if so, trigger callbacks.
def save
if valid?
run_callbacks(:save) { true }
else
false
end
end
end
This gives you before_save
, after_save
and around_save
callbacks. Quick and easy, huh?
Wrapping up
ActiveModel::Model
is a really small, handy addition to Rails 4.0, which helps us to get classes that act more like ActiveRecord
and easily integrate with ActionPack
.
For more detailed information on other features available, please refer to the specific modules included in ActiveModel::Model
. Each module includes plenty of docs explaining its functionality. Apart from these included modules, ActiveModel
itself has a bunch of useful stuff to add to your Ruby classes that are really worth checking out.
This is the kind of thing that makes me a happier Rails developer every day. What about you, what makes you a happier Rails developer? Please take a moment to tell us in the comments section below 🙂
I think it would be much better if Rails 4 effort for models and ActiveAttr would be joined.
I believe ActiveModel::Model is a great start, it makes it a lot easier to get a barebone model with the most common required interface up and running quickly. Other than that, Rails already provides everything else required to create AR-like classes, you just need to cherry-pick what you need, when you need. ActiveAttr is great in the sense of collecting all available modules in ActiveModel as a convenience, but I’m not sure it’d be a great fit for Rails to have an all-in-one module like it.
Thanks for your feedback.
Can you use this inline? i.e.: use this to define a “Form”, instead of a model?
imo rails confuses “Form” with “Model”… Ofcourse it should be easy to create a form from a model, but this is where the annoyance started
Hey Joris, sorry but what do you mean about “defining a form”? Would you mind trying to exemplify your idea?
The change we are showing in this post helps you create a model with some plug and play functionality, the api you need to use with Rails helpers – ie form_for and url helpers.. but that doesn’t mean it’s only tied to a form.
Thanks.
Carlos, you may want to take a closer look at ActiveAttr. While it does offer the all-in-one Model module, it’s made up of slices of functionality that I think captures the spirit of the ActiveModel library.
Hello Chris 🙂
Yeah, I do know ActiveAttr – it’s a great gem btw, congrats – and that it’s created to be modular. That’s ActiveModel’s goal as well, to provide pieces that you can use to build up your classes/models. I just think that ActiveModel gives you the API to be flexible enough to create the functionality you need, and usually not some functionality out of the box. ActiveAttr takes it a step further and implement its modules on top of ActiveModel, giving you a final implementation for common cases, which is great =).
Thanks for your feedback.
—
At.
Carlos Antonio