Month: September 2011

Rails model without corresponding database tables

Lately, I’ve been fortunate enough to contribute to Dot429. It is, among other things, a great place for the LGBT community and their supporters to connect, discuss business and mentor each other.

One of the mini-projects, a contest, required us to ask for a telephone number, but in three separate fields: (area-code), (first three digits), (last four digits). Once the fields were accepted and validated, they were to be concatenated and stored in the database.

Rails makes this very easy: just add the accessors for non-database fields and validate them as we normally would through the built in validators:

  attr_accessor :phone_area, :phone_part_one, :phone_part_two
  attr_accessible :phone_area, :phone_part_one, :phone_part_two
  validates_presence_of :phone_area, :phone_part_one, :phone_part_two, :message => 'Please provide your phone number.'
  validates_format_of :phone_area, :with => /^[0-9]{3}$/, :message => 'Please enter the phone number in Digits 0 - 9'
  validates_format_of :phone_part_one, :with => /^[0-9]{3}$/, :message => 'Please enter the phone number in Digits 0 - 9'
  validates_format_of :phone_part_two, :with => /^[0-9]{4}$/, :message => 'Please enter the phone number in Digits 0 - 9'

When the save() function is called in the controller, the model should validate just fine, even if the particular fields do not correspond to columns in the database.

Now, what if we want to make use of validators for a model that has no table in the database?
Rails throws its arms in the air if you do the above but for a model that has no corresponding database table. The solution is simple: create a dummy table. This, in my opinion, is the simplest solution to the problem because it makes Rails happy. We can even create one dummy talble and associate it to any model that does not have a corresponding database table. Like so:

class Contact < ActiveRecord::Base
  set_table_name :the_empty
  attr_accessor :phone_area, :phone_part_one, :phone_part_two
  attr_accessible :phone_area, :phone_part_one, :phone_part_two
  validates_presence_of :phone_area, :phone_part_one, :phone_part_two, :message => 'Please provide your phone number.'
  validates_format_of :phone_area, :with => /^[0-9]{3}$/, :message => 'Please enter the phone number in Digits 0 - 9'
  validates_format_of :phone_part_one, :with => /^[0-9]{3}$/, :message => 'Please enter the phone number in Digits 0 - 9'
  validates_format_of :phone_part_two, :with => /^[0-9]{4}$/, :message => 'Please enter the phone number in Digits 0 - 9'
end

The first line in our model’s declaration attaches the model with our dummy table. Now we have all the advantages of a regular rails model, without the need to store it.

Here’s the rest of the code from the test project that I created:

# Routes
ActionController::Routing::Routes.draw do |map|
  map.resources :contacts, :member => { :new => :get, :create => :post }
end

# Migration
class CreateContacts < ActiveRecord::Migration
  def self.up
    create_table :the_empty do |t|
    end
  end

  def self.down
    drop_table :the_empty
  end
end

# Contact Controller
class ContactsController < ApplicationController
  def new
    @contact = Contact.new(params[:contact])
    @message = params[:message]
    respond_to do |format|
      format.html
    end
  end

  def create
    @contact = Contact.new(params[:contact])    
    respond_to do |format|
      if @contact.valid?
        phone = "You entered: (#{@contact.phone_area}) #{@contact.phone_part_one} - #{@contact.phone_part_two}"
        format.html { redirect_to :action => :new, :params => {:message => "#{phone}"} }
      else
        message = "Error: #{flash[:error] = @contact.errors.first[1]}"
        format.html { redirect_to :action => :new, :params => {:message => "#{message}"} }
      end
    end
  end
end

<!-- Contact Form -->
<% form_for :contact, :url => contacts_path do |f| %>
  <% if @message %>
  <div><%=@message%></div>
  <% end %>
  <span id="phone_help_text">Phone Number:</span>
  <%= f.text_field :phone_area, :minlength => 3, :maxlength => 3, :tabindex => 6 %>
  <%= f.text_field :phone_part_one, :minlength => 3, :maxlength => 3, :tabindex => 7 %>
  <%= f.text_field :phone_part_two, :minlength => 4, :maxlength => 4, :tabindex => 8 %>
  <%= f.submit %>
<% end %>