08 February 2012

Using ActiveRecord::Store with Rails 3.2

As you may already know, the Rails 3.2 release has introduced some nice new features. One of them is the ActiveRecord::Store.

This is a kind of simple hash store, which is kept in the single database field as serialized data. Please note that this is why it can be used only in the context of a single record. That means, that querying it outside this context is not possible, or at least very inefficient.

The great thing about Store is that it allows as to declare hash keys accessors. Any accessor acts like any other attribute of a model. And that's the idea!

Let me here show you a quick example of how this can be used in practice.

I assume we have already generated and working Rails 3.2.1 application.
Next assumption will be, that we want to create a Drawing model, with code and scale attributes.
We need code attribute for some database search/query purposes. But we need scale attribute just to make each drawing to be able to remember its user interface settings. The next thing we need is to validate value of the scale to be between 1 and 100, as we assume it is a percentage unit.
And that  is a pretty good example for using ActiveRecord::Store.

Lets start from creating out model first.
$ rails generate model Drawing code:string
Then, modify generated migration file to meet the scale requirements. As ActiveRecord::Store serializes data, the best solution would be creating text format column. Lets name it settings.
class CreateDrawings < ActiveRecord::Migration
  def change
    create_table :drawings do |t|
      t.string :code
      t.text   :settings
      t.timestamps
    end
  end
end
That's it - run the migration against the database.
$ rake db:migrate
Now we need to declare the store within Drawing mode and add some validations, as well.
class Drawing < ActiveRecord::Base
  store :settings, accessors: [:scale]
  validates :code, presence: true, uniqueness: true
  validates :scale, numericality: { only_integer: true,
                                    greater_than: 0,
                                    less_than_or_equal_to: 100 }
end
All right. Now run the rails console to play around.
$ rails console
First, lets see how a new drawing instance is initialized
1.9.3p0 :001 > d = Drawing.new
 => #<Drawing id: nil, code: nil, settings: {}, created_at: nil, updated_at: nil>
As we can see there is an empty settings hash waiting for us.
Lets examine our store accessors now.
1.9.3p0 :002 > d.scale
 => nil
Good - it responds.
Assign something into scale and see the results.
1.9.3p0 :003 > d.scale = 150
 => 150 
1.9.3p0 :004 > d.scale
 => 150 
1.9.3p0 :005 > d
 => #<Drawing id: nil, code: nil, settings: {:scale=>150}, created_at: nil, updated_at: nil>
It's working like a charm.
The validation is next in the queue.
1.9.3p0 :006 > d.valid?
 => false 
1.9.3p0 :007 > d.errors
 => #<ActiveModel::Errors:0x95f96d8 @base=#<Drawing id: nil, code: nil, settings: {:scale=>150}, created_at: nil, updated_at: nil>, @messages={:code=>["can't be blank"], :scale=>["must be less than or equal to 100"]}>
We can see the scale is working like code and any other model attribute.
OK. It is time to make it valid and save.
1.9.3p0 :008 > d.scale = 100
 => 100
 1.9.3p0 :009 > d.code = "abc"
 => "abc"
1.9.3p0 :010 > d.valid?
 => true 
1.9.3p0 :011 > d.save
 => true
1.9.3p0 :012 > Drawing.first
 => #100}, created_at: "2012-02-08 12:30:37", updated_at: "2012-02-08 12:30:37"> 
And that's all folks. Thanks for reading.