Posts tagged "rails 4"

Update 08/13/2012

Since the new deprecation policy the composed_of removal was reverted. We are still discussing the future of this feature.

The reason

A few days ago we started a discussion about the removal of the composed_of method from Active Record. I started this discussion because when I was tagging every Rails issue according to its framework, I found some of them related to composed_of, that are not only hard to fix but would add more complexity for a feature that, in my opinion, is not adding any visible value to the framework.

In this presentation, Aaron Patterson talks about three types of features: Cosmetic, Refactoring, and Course Correction. Aaron defines cosmetic features as a feature that adds dubious value and unknown debt (I highly recommend that you watch the whole presentation to see more about these types of features). This is exactly what I think about composed_of. At this time this feature is adding more debt than value to the Rails code base and applications, so the Rails team have decided to remove this method.

The plan

The removal plan is simple, deprecate in 3.2 and remove in 4.0. This means that you need to stop using this feature and implement it in another way.

The Rails team have chosen this path because this feature can be implemented using plain ruby methods for getters and setters. You will see how in the next section.

Implementation

In the simplest case, when you have only one attribute and needs to instantiate an object with the value of this attribute, you can use the serialize feature with a custom serializer:

class MoneySerializer
  def dump(money)
    money.value
  end
 
  def load(value)
    Money.new(value)
  end
end
 
class Account < ActiveRecord::Base
  serialize :balance, MoneySerializer.new
end

To use it with multiple attributes you can do the following:

class Person < ActiveRecord::Base
  def address
    @address ||= Address.new(address_street, address_city)
  end
 
  def address=(address)
    self[:address_street] = address.street
    self[:address_city]   = address.city
 
    @address = address
  end
end

Benefits for Rails developers

I already talked about what this removal can provide to Rails maintainers, but what benefits does it bring to Rails developers?

I think that the best advantages are:

  • It is easier to test the composite objects;
  • It is easier to understand the lazy methods;
  • It is easier to customize it without resorting to options like :converter, :constructor and :allow_nil.

Wrapping up

I strongly recommend that you read the whole discussion in the pull request. You will find more examples and additional information there.

Also, I want to thank @steveklabnik for working on this feature and the awesome work that he has been doing on the Rails Issues Team.

Finally, I want to invite you to help the Rails team to fix, test, and track issues. About half of the issues are related to the Active Record framework and we need to work on them. As a regular Rails contributor and Rails developer, I think there is still a lot we can do to improve the Rails code base, so join us.

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