In my last article I wrote about using data modeling to clean up form-related code and to take advantage of powerful helpers like form_for and error_messages_for. This solves the significant problem of isolating business logic into a model class, but another problem remains — how can we make our form data pretty without trashing our model's code with view logic?
Beautifying the Data
I have a simple Product model with rows for name, price and features. Setting and displaying the price field is tricky because I need to remove currency formatting before storing it in the database as a decimal, and I want to reformat it when displaying the current price in an 'Edit' form. The features field is also tricky. I want the user to enter each product feature ("Slices and Dices", "Purees Anything", etc.) on a separate line of the textarea and for that to be split into a serialized array that I will store in the database.
My first instinct is to create virtual attributes in my model to handle the logic of deformatting/reformatting this data. Here's how it looks:
class Product < ActiveRecord::Base serialize :features, Array validates_presence_of :name validates_numericality_of :price # need this for formatting def helpers ActionController::Base.helpers end def price_field=(p) # expecting p to be something like "$4,000.00", set price to 4000.00 p.gsub!(/[^0-9.]/, '') self.price = p.to_f end def price_field helpers.number_to_currency(self.price) end def features_field=(str) # expecting string with features separated by newlines self.features = str.split("\n").collect {|f| f.strip } end def features_field self.features.join("\n") end end
<%# the product form %> <% form_for :product, url => products_path do |f| %> <p class="form_item text_field"> <label for="product[name]">Name:</label> <%= f.text_field :name %> </p> <p class="form_item text_field"> <label for="product[price_field]">Price:</label> <%= f.text_field :price_field %> </p> <p class="form_item text_field"> <label for="product[features_field]">Features (1 per line):</label> <%= f.text_area :features_field %> </p> <p class="form_item submit_button"> <%= f.submit "Create Product" %> </p> <% end %>
The good news is that we have a very simple, straightforward looking view with no logic stuffed in it. Our controller is also completely vanilla, so I didn't bother to even show it.
Our model, however, is getting cluttered. What once was a haven for business logic is now filled with both business and view logic. I'm also a little concerned about having to use the #price_field and #features_field methods, since it adds complexity to what should be a simple object API. Will this cause confusion with my fellow programmers?
Beautifying the Code
What if we extracted the view logic into its own object? By using a presenter object as an adapter between our Product model and our form we can isolate the view logic from the business logic. Here's how it looks:
class Product < ActiveRecord::Base serialize :features, Array validates_presence_of :name validates_numericality_of :price end
class ProductPresenter attr_reader :product def initialize(product) @product = product end # need this for formatting def helpers ActionController::Base.helpers end # for mass assignment def attributes=(hash) hash.each_pair do |key, val| self.send("#{key}=".to_sym, val) end end def price=(p) # expecting p to be something like "$4,000.00", set price to 4000.00 p.gsub!(/[^0-9.]/, '') @product.price = p.to_f end def price helpers.number_to_currency(@product.price) end def features=(str) # expecting string with features separated by newlines @product.features = str.split("\n").collect {|f| f.strip } end def features @product.features.join("\n") end # proxy all other methods to @product def method_missing(method_name, *args, &block) @product.send(method_name, *args, &block) end end
class ProductsController < ApplicationController def new @product = ProductPresenter.new(Product.new) end def edit product = Product.find(params[:id]) @product = ProductPresenter.new(product) end def create @product = ProductPresenter.new(Product.new) @product.attributes = params[:product] if @product.save # success else render :action => 'new' end end def update p = Product.find(params[:id]) @product = ProductPresenter.new(p) @product.attributes = params[:product] if @product.save # success else render :action => 'new' end end end
<%# the product form %> <% form_for :product, url => products_path do |f| %> <p class="form_item text_field"> <label for="product[name]">Name:</label> <%= f.text_field :name %> </p> <p class="form_item text_field"> <label for="product[price]">Price:</label> <%= f.text_field :price %> </p> <p class="form_item text_field"> <label for="product[features]">Features (1 per line):</label> <%= f.text_area :features %> </p> <p class="form_item submit_button"> <%= f.submit "Create Product" %> </p> <% end %>
Our business and view logic have been effectively separated, our form code is clearer and the controller is only slightly more complicated. Because the ProductPresenter is acting as a proxy object to its Product object, we can simply treat it like a product thanks to 'duck typing'. While this example is a little contrived, using presenter classes can make or break your code base as you create complex model objects with equally complex visual representations.
Forms of Every Shape and Color
As the Web continues to develop as an interactive, read/write medium, web forms are more useful and necessary than ever. Historically speaking, forms tend to be difficult for developers — validation, error handling, field population, redirection, data modeling and storage, these all play a part in even the simplest forms.
In the Rails world, our lives were made significantly easier thanks to the form_for helper. With this helper method, the Rails developer gets a lot for free thanks to its interaction with the ActiveRecord object that is being manipulated. Between ActiveRecord validation and the intelligent field population of form_for, creating forms becomes nearly trivial.
But what about the other forms, those that are not directly manipulating an ActiveRecord object? Forms like 'Log In' and 'Search' forms? If the developer is not using the form to CRUD an ActiveRecord model instance, he suddenly finds himself back to using low level helpers like text_field_tag and enforcing validation and other business logic within the controller.
But There's a Better Way!
All data that we receive from forms should be modeled, but we don't always have to use an ActiveRecord model class to do it. For data that needs to be modeled but not stored in the database, we can simply use a plain ol' Ruby class. Let's say that we want to create a login form - it will have fields for email and password, and a checkbox for "Remember Me". We can model this data like so:
# app/models/login.rb class Login attr_reader :email, :password, :remember_me def initialize(params = {}) @email = params[:email] @password = params[:password] @remember_me = params[:remember_me] end def authenticate # tie into User model for authentication user = User.authenticate(self.email, self.password) return user end end
# app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new @login = Login.new end def create @login = Login.new(params[:login]) if current_user = @login.authenticate redirect_to successful_login_path else render :action => 'new' # unsuccessful login end end end
<%# app/views/sessions/new %> <% form_for :login, :url => sessions_path do |f| %> <p class="form_item"> <label for="login[email]">Email</label> <%= f.text_field :email %> </p> <p class="form_item"> <label for="login[password]">Password</label> <%= f.password_field :password %> </p> <p class="form_item"> <label for="login[remember_me]">Remember Me</label> <%= f.check_box :remember_me %> </p> <p class="form_item submit_button"> <%= f.submit "Login" %> </p> <% end %>
A Dash of Validation
The good news is that we have modeled the login data and now have a place other than the controller for login-specific logic. We are still missing validation, however. If our Login class doesn't inherit from ActiveRecord, does that mean we need to roll our own validation methods?
Thankfully Jay Fields already did the work for us by creating the Validatable Ruby gem (sudo gem install validatable). The Validatable module can be mixed into any Ruby class and provides the same validation interface that we are accustomed to using with ActiveRecord. Now we can require both the email and password to be filled out before we even try attempt a login. Here's our updated code:
# app/models/login.rb class Login include Validatable validates_presence_of :email, :password attr_reader :email, :password, :remember_me def initialize(params = {}) @email = params[:email] @password = params[:password] @remember_me = params[:remember_me] end def authenticate # tie into User model for authentication unless user = User.authenticate(self.email, self.password) self.errors.add(:base, "No user was found with that email and password.") end return user end end
# app/controllers/sessions_controller.rb class SessionsController < ApplicationController def new @login = Login.new end def create @login = Login.new(params[:login]) if @login.valid? && current_user = @login.authenticate redirect_to successful_login_path else render :action => 'new' # unsuccessful login end end end
<%# app/views/sessions/new %> <%= error_messages_for :login %> <% form_for :login, :url => sessions_path do |f| %> <p class="form_item"> <label for="login[email]">Email</label> <%= f.text_field :email %> </p> <p class="form_item"> <label for="login[password]">Password</label> <%= f.password_field :password %> </p> <p class="form_item"> <label for="login[remember_me]">Remember Me</label> <%= f.check_box :remember_me %> </p> <p class="form_item submit_button"> <%= f.submit "Login" %> </p> <% end %>
Our Login class now can use the familiar validates_* class methods, the valid? instance method and all the facilities that come with having the errors object (like using error_messages_for).
It also becomes very easy to set default values for the login form. For example, if we wanted to 'check' the Remember Me box by default, we could simply change our SessionsController#new action to this:
def new @login = Login.new(:remember_me => true) end
Modeling our form data in this way is a win-win scenario — we're using good coding practices by keeping business logic out of our controller, and we're also inheriting all the facilities that Rails provides by leveraging form_for, error_messages_for and validations.
What Are Named Scopes?
Of all the great new features of Rails 2.1 (and the forthcoming Rails 2.2), named_scope is my favorite. named_scope provides a mechanism for wrapping up a set of find conditions. Let's check out an example to see how this all works.
Imagine we have a Person model with the following schema:
create_table :people do |t| t.string :eye_color t.integer :age t.boolean :admin end
Now, in many of our finds we are probably going to be interested in that :admin attribute, especially when we're looking up users that are able to log into the admin section of our site. Normally, we might write a find something like this:
@admins = Person.find(:all, :conditions => {:admin => true})
That would grab all the people in our database who are admins. However, that find syntax is a little verbose, especially for a controller (which is probably where we might find code like this). named_scope to the rescue! We'll add the following to our Person model:
named_scope :admins, :conditions => {:admin => true}
This allows us to change our controller call to something a bit closer semantically to what we're actually doing, leaving the meat of the logic in our model where it belongs:
@admins = Person.admins
We may also be interested in our non-admins as well, so we might write another named scope like the following:
named_scope :non_admins, :conditions => {:admin => false}
But this is starting to seem a little redundant. Our admin and non_admin named scopes are almost the same, just flipping the boolean value in our conditions. There must be a better way... and there is! Our old friend lambda to the rescue:
named_scope :admins, lambda {|boolean| {:conditions => {:admin => boolean}} }
Now we can pass the boolean value to our named_scope like so:
@admins = Person.admins(true) @non_admins = Person.admins(false)
Nice and DRY... Like a Good Chardonnay
Now, I know what you're thinking. You're thinking you'd like a nice glass of chardonnay right now. But this article's not about chardonnay. This article's about named_scope... so stay with me. No, you're thinking, "Gee, couldn't I just write some class-level finder methods and achieve the same thing?" Of course you could. Before Rails 2.1 and named_scope that's exactly what you might have done. Something like this:
def self.admins(boolean) find(:all, :conditions => {:admin => boolean}) end
And that would work the same as our "admins" named scope does, with a few big exceptions. The thing that makes named_scope so powerful is the ability to chain them together to make more complex finds.
Let's say our admin area for our site is going to have an ad for that chardonnay I mentioned earlier. Naturally, we're going to want to make sure our admins are also of legal drinking age. If we weren't using named scopes, we would write another class-level finder method, something like the following:
def self.drinkin_admins find(:all, :conditions => ['age >= ? and admin = ?', 21, true]) end
Hmm. Is your code Spidey-sense tingling like mine is? We've got essentially the same admin conditions from our admins class-level finder mixed in there. Not so DRY. Let's see how named scopes would make this situation better:
class Person < ActiveRecord::Base named_scope :admins, lambda {|boolean| {:conditions => {:admin => boolean}} } named_scope :drinkers, :conditions => ["age >= ?", 21] end
Our resulting find might look something like this:
@drinkin_admins = Person.admins(true).drinkers
Just like that, we've got all the admins who are also over 21 years old. But more than that, we haven't mixed our conditions together. And because our drinkers named scope is separate, we can use it in other scenarios as well.
Furthermore, what if you need to get a count of all the admins who are over 21? Before named_scope, you'd write a second class-level finder method using the count method instead of find. But because named scopes soak up all those conditions and evaluate them at the time that you need them, you can simply add a .count to the end of your scope-chain.
@drinkin_admins_count = Person.admins(true).drinkers.count
Is That a Cape and Tights I See?
Of all the awesomeness that we've seen, named_scope is just getting started. Let's say one of our scopes is "almost" exactly what we want, but we really only need the first 10 results, or maybe we want to order the results in some special way. No worries, named_scope pulls out it's super hero outfit and saves the day:
@first_ten_drinkers = Person.drinkers.scoped(:limit => 10, :order => 'eye_color')
And just like that we can scope our named_scope even more, on the fly, no problems. The scoped named scope is special in that it can get merged into the scope chain and add additional conditions right there inline.
Now, this is all well and good, but we can make this whole thing even easier. What if we added another scope called limit, like the following:
named_scope :limit, lambda {|num| {:limit => num}}
Now we can limit any find we do off the Person model simply by including the limit scope in the chain, like so:
@first_ten_drinkers = Person.drinkers.limit(10).scoped(:order => 'eye_color')
Nice! Now that's feeling a whole lot better. I bet you can think of a few others that would be helpful too, like an order scope that lets you order by created_at dates. And we probably want these scopes on all of our ActiveRecord models, right? In fact, Ryan Daigle has already done a lot of that work for us with his utility_scopes gem (http://github.com/yfactorial/utility_scopes/).
script/plugin install git://github.com/yfactorial/utility_scopes.git
Now you'll have all kinds of great named scopes to make finding records easy as pie, including:
Model.with(:association) #=> eager loads the association, so you don't have to. Model.except(1, 2, 3) #=> returns all of the models except the ones with ids 1, 2 or 3 Model.limited #=> returns the first 10 (default, no argument) Model.limited(5) #=> returns the first 5 Model.order_by(:eye_color) #=> results ordered by the eye_color attribute.
And much, much more. Check out the README for utility_scopes on github.
What's that you say? Pagination? With will_paginate? Well, wouldn't you know it, will_paginate uses named_scope to implement it's pagination methods. So, just tack a "paginate" on the end of your scope-chain, like so:
@drinkin_admins = Person.admins(true).drinkers.paginate(:page => params[:page])
Easy peasy.
For more information on named_scope, check out these fine articles across the internet:

