Rose

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.



Validate

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.



Superhero

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:




RSS Feed


CATEGORIES


ARCHIVES


BOOKMARKED


Add to Technorati Favorites