Rails Best Practices 2: Move logic to model
02:43 PMDevelopment, Metodologies, RubyClaudio
Today we continue our analysis of the Rails Best Practices.
In the previous post we saw Named Scope, Model Association and
Following the same direction, in today’s post we’ll examine the use of Callback Model and Virtual Attribute.
1. Virtual Attribute
Suppose we have a customers list table defined as follows
1 2 3 4 5 6 | create_table "clients", :force => true do |t| t.string "first_name" t.string "last_name" t.string "street" t.string "city" end |
Suppose we have a customers list table defined as follows but we want to define an input mask where street and city are grouped into one field called “address”.
The form with the “address” field will be defined as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <h1>Create Client</h1> <% form_for @client do |f| %> <ol class="formList"> <li> <%= f.label :first_name, 'First Name' %> <%= f.text_field :first_name %> </li> <li> <%= f.label :last_name, 'Last Name' %> <%= f.text_field :last_name %> </li> <li> <%= f.label :address, 'Address' %> <%= text_field_tag :address %> </li> </ol> <% end %> |
Now in the create method we’ll have something like:
1 2 3 4 5 6 7 8 9 10 11 12 | class ClientsController < ApplicationController def create @client = Client.new(params[:client]) @client.street = params[:address].split(' ', 2).first @client.city = params[:address].split(' ', 2).last @client.save end ... end |
We can now improve this method by defining the address field as a virtual attribute of the Client model.
1 2 3 4 5 6 7 8 9 10 11 12 | class Client < ActiveRecord::Base def address [street, city].join(' ') end def address=(addr) split = addr.split(' ', 2) self.street = split.first self.city = split.last end end |
In the form we can now define the address field as f.field_tag instead of as text_field_tag :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <h1>Create Client</h1> <% form_for @client do |f| %> <ol class="formList"> <li> <%= f.label :first_name, 'First Name' %> <%= f.text_field :first_name %> </li> <li> <%= f.label :last_name, 'Last Name' %> <%= f.text_field :last_name %> </li> <li> <%= f.label :address, 'Address' %> <%= f.text_field :address %> </li> </ol> <% end %> |
Finally, the controller will become simpler:
1 2 3 4 5 6 7 8 9 10 | class ClientsController < ApplicationController def create @client = Client.new(params[:client]) @client.save end ... end |
2. Model Callback
Now we see a method, called Model Callback that allows us, as with the virtual attribute just seen, to simplify the form and to move the controller’s logic inside the model.
Suppose we have to implement a feature that automatically associates a set of tags to a post.
This feature, called calculate_tags return a list of tags based on the most frequent words contained in the post.
We don’t see the code of this function and instead see how and when to invoke the automatic generation of tags.
A first implementation can be as follows. Given the following form:
1 2 3 4 | <% form_for @post do |f| %> <%= f.text_field :content %> <%= check_box_tag 'calculate_tags' %> <% end %> |
the corresponding create method will be:
1 2 3 4 5 6 7 8 9 10 | class PostController < ApplicationController def create @post = Post.new(params[:post]) if params[:calculate_tags] == '1' @post.tags = TagGenerator.generate(@post.content) else @post.tags = "" end @post.save end |
Editing the Post model, let’s see how to improve the code we’ve just wrote.
First we introduce in our model an attribute, “calculate_tags”, and a filter “generate_tags”, of type before_save
1 2 3 4 5 6 7 8 9 10 | class Post < ActiveRecord::Base attr_accessor :calculate_tags before_save :generate_tags private def generate_tags return unless calculate_tags == '1' self.tags = TagGenerator.generate(self.content) end end |
Returning to the form we can now redefine the field calculate_tags as f.check_box
1 2 3 4 | <% form_for @post do |f| %> <%= f.text_field :content %> <%= f.check_box 'calculate_tags' %> <% end %> |
Finally, our create method in the controller will return to its simplest form.
1 2 3 4 5 6 | class PostController < ApplicationController def create @post = Post.new(params[:post]) @post.save end end |
Tags: Best Practices, model callback, ruby on rails, virtual attributes

















[...] This post was mentioned on Twitter by Ruby Reflector. Ruby Reflector said: Top Ruby Article: Rails Best Practices 2: Move logic to model: http://bit.ly/amLWC2 [...]
Great post. I need to do better at this! One quick question, instead of: self.street = split.first; self.city = split.last, could you just write: street = split.first; city = split.last? This is one area of Ruby that confuses me.
James, if you’re saving a value to your database. Then you must explicitly call the self method e.g self.city = split.last.
As a rule of thumb, self must always be explicit for setting attributes, but not for retrieving them.
Nice Read. Keep them coming.