Barebone models to use with ActionPack in Rails 4.0

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 :)

  • Dmytrii Nagirniak

    I think it would be much better if Rails 4 effort for models and ActiveAttr would be joined.

  • http://carlosantoniodasilva.wordpress.com Carlos Antonio

    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.

  • http://profiles.google.com/jbverschoor Joris Verschoor

    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

  • http://carlosantoniodasilva.wordpress.com Carlos Antonio

    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.

  • Chris Griego

     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.

  • http://carlosantoniodasilva.wordpress.com Carlos Antonio

    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