This is a custom Contact form with Validations without ActiveRecord. This works nicely without the use of the database or ActiveRecord model. It's quite easy with a gem called Validatable.

To Install:

sudo gem install validatable

Add to your environment.rb file:

require "validatable"

Controller

class ContactsController < ApplicationController
  layout  'site'
  
  def new
    @title = "Contact"
    @contact = Contact.new  
  end
  
  def create
    @title = "Contact" 
    @contact = Contact.new params[:contact]
    if @contact.valid?
      @contact.deliver
      flash[:notice] = "Contact Message Sent"
      redirect_to root_url
    else
      render :action => 'new'
    end
  end
  
end

Helper

module ContactHelper
  def error_messages_for_attribute(object, attribute)
    if object.errors.on(attribute)
      html = '<small class="errors"><ul>'
      object.errors.on(attribute).each do |message|
        html += "<li>" + message + "</li>"
      end
      html += "</ul></small>"
    end
  end
end

Model

class Contact
  # self is the class
  include Validatable
 
  attr_accessor :name, :email, :phone, :message
 
  validates_presence_of :name, :message => "Name is required."
  validates_format_of :email, :with => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
    :message => "Email is not valid."
  validates_format_of :phone, :with => /^[0-9]{3,3}-[0-9]{3,3}-[0-9]{4,4}$/,
    :message => "Phone number is not valid (xxx-xxx-xxxx)."
  validates_presence_of :message, :message => "Name is required."
  
  class << self
    alias_method :new_without_params, :new
    def new params ={}
      returning new_without_params do |contact|
        params.each_pair do |key,value|
          contact.send "#{key}=", value
        end
      end
    end
  end
 
  def phone=(phone)
    # self is the object
    @phone = phone.gsub(/[^0-9]/, "").gsub(/^([0-9]{0,3})([0-9]{0,3})([0-9]{0,})$/) do |match|
      tmp = ""
      tmp += $1 if $1.size > 0
      tmp += "-" + $2 if $2.size > 0
      tmp += "-" + $3 if $3.size > 0
      tmp
    end
  end  
 
  def deliver
    if valid?
      ContactMailer.deliver_contact(self)
    else
      false
    end
  end
end

View

<div class="form_row">
  <% form_tag '/contact/create' do %>
    <fieldset>
      <legend><%= @title %></legend>
        <p><label for="contact_name">Name</label>
        <%= text_field :contact, :name %>
        <%= error_message_on(@contact, :name) %></p>

        <p><label for="contact_email">Email</label>
        <%= text_field :contact, :email %>
        <%= error_message_on(@contact, :email) %></p>

        <p><label for="contact_phone">Phone</label>
        <%= text_field :contact, :phone %>
        <%= error_message_on(@contact, :phone) %></p>

        <p><label for="contact_message">Message</label>
        <%= text_field :contact, :message %>
        <%= error_message_on(@contact, :message) %></p>

        <p><%= submit_tag 'Submit', :class => "submit" %></p>
      </fieldset>
  <% end %>
</div>

Rspec Controller

require File.dirname(__FILE__) + '/../spec_helper'

describe ContactsController do
  def mock_contact(params={})
    params = {:deliver => true, :save => true, :valid? => true}.merge(params)
    @mock_contact ||= mock('contact', params)
  end
  
  describe "on GET to new" do
    it "should expose new contact as @contact" do
      Contact.should_receive(:new).and_return(@contact)
      get :new
      assigns(:contact).should == @contact
    end
  end

  describe "on POST to create" do
    it "should populate new contact with form data" do
      Contact.should_receive(:new).with({'these' => 'options'}).and_return(mock_contact)
      post :create, :contact => {'these' => 'options'}
    end

    describe "with valid contact" do
      it "should tell the contact to deliver" do
        Contact.should_receive(:new).and_return(mock_contact(:save => true))
        mock_contact.should_receive(:deliver)
        post :create
      end
      
      it "should notify the user of successful submission" do
        Contact.should_receive(:new).and_return(mock_contact(:save => true))
        post :create
        flash[:notice].should == "Contact Message Sent"
      end
      
      it "should redirect the user to the home page" do
        Contact.should_receive(:new).and_return(mock_contact(:save => true))
        post :create
        response.should redirect_to('/')
      end
    end
    
    describe "with invalid contact" do
      it "should render the new contact form again" do
        Contact.stub!(:new).and_return(mock_contact(:valid? => false))
        post :create
        response.should render_template('contacts/new')
      end
    end
  end
end

Rspec Model

require File.dirname(__FILE__) + '/../spec_helper'

describe Contact do
  it "should assign name" do
    contact = Contact.new(:name => "Lance")
    contact.name.should == 'Lance'
  end

  it "should assign email" do
    contact = Contact.new(:email => "person@site.com")
    contact.email.should == 'person@site.com'
  end

  it "should assign phone" do
    contact = Contact.new(:phone => "555.555.1212")
    contact.phone.should == '555-555-1212'
  end

  it "should assign message" do
    contact = Contact.new(:message => "hey, how come you never call me?")
    contact.message.should == "hey, how come you never call me?"
  end

  it "should not assign non_existent_attribute" do
    lambda {
      Contact.new(:non_existent_attribute => "whatever")
    }.should raise_error(NoMethodError)
  end
  
  it "should have error for missing name" do
    contact = Contact.new
    contact.valid?
    contact.errors.on(:name).should == 'Name is required.'
  end
  
  it "should have error for missing email" do
    contact = Contact.new
    contact.valid?
    contact.errors.on(:email).should == 'Email is not valid.'
  end
  
  it "should have error for missing phome" do
    contact = Contact.new
    contact.valid?
    contact.errors.on(:phone).should =~ /Phone number is not valid/
  end
  
  it "should have error for invalid phome" do
    contact = Contact.new(:phone => 'invalid phone number')
    contact.valid?
    contact.errors.on(:phone).should =~ /Phone number is not valid/
  end
end

Rspec View

require File.dirname(__FILE__) + "/../../spec_helper"

describe "/contacts/new" do
  it "should render the form" do
    assigns[:contact] = Contact.new
    
    render "/contacts/new.html.erb"
    
    response.should have_tag('form[action=?]','/contact/create') do
      with_tag('input[name=?]','contact[name]')
      with_tag('input[name=?]','contact[email]')
      with_tag('input[name=?]','contact[phone]')
      with_tag('input[name=?]','contact[message]')
    end
  end
  
  def expect_error_message_on(attribute)
    template.stub!(:error_message_on)
    assigns[:contact] = Contact.new
    template.should_receive(:error_message_on).with(assigns[:contact], attribute).and_return("message received")
  end
  
  it "should ask for error messages for name" do
    expect_error_message_on(:name)
    
    render "/contacts/new.html.erb"
    
    response.should include_text("message received")
  end
  
  it "should ask for error messages for email" do
    expect_error_message_on(:email)
    
    render "/contacts/new.html.erb"
    
    response.should include_text("message received")
  end
  
  it "should ask for error messages for phone" do
    expect_error_message_on(:phone)
    
    render "/contacts/new.html.erb"
    
    response.should include_text("message received")
  end
  
  it "should ask for error messages for message" do
    expect_error_message_on(:message)
    
    render "/contacts/new.html.erb"
    
    response.should include_text("message received")
  end

end

Story Runner

Steps

steps_for(:visitor_contacts) do

  Given "I am an anonymous visitor" do
    # no-op
  end
  
  When "I go to the contact form" do
    visits "/contact"
    # puts response.body
  end
  
  When "I submit the form with valid data" do
    fills_in 'Name', :with => 'Lance'
    fills_in 'Email', :with => 'lance@whatever.com'
    fills_in 'Phone', :with => '555-555-1212'
    fills_in 'Message', :with => 'Hello, what is this website about?'
    clicks_button "Submit"
  end
  
  When "I enter '$data' for $field" do |data, field|
    fills_in field, :with => data
  end
  
  When "I submit the form" do
    clicks_button "Submit"
  end
  
  Then /I should see '(.*)'/ do |text|
    response.should include_text(text)
  end

end

Stories

Story: visitor contacts your site

  As a visitor to your site
  I want to contact your site
  So that I can tell them how awesome the site is
  
  Scenario: visitor sends message
    Given I am an anonymous visitor
    When I go to the contact form
    And I submit the form with valid data
    Then I should see 'Contact Message Sent'
    
  Scenario: invalid phone format
    Given I am an anonymous visitor
    When I go to the contact form
    And I enter '123.4567' for Phone
    And I submit the form
    Then I should see 'Phone number is not valid'

  Scenario: missing name
    Given I am an anonymous visitor
    When I go to the contact form
    And I enter '' for Name
    And I submit the form
    Then I should see 'Name is required.'

Sorry, comments are closed for this article.