This example shows how to create a contact form in Rails. Among other aspects, you can see how to perform serverside validations and handle the results. For this example the form values (form.export_values) are passed directly to ActionMailer. A special validation has been set on the section level, to validate the combination of address, postal code and city; if either is given, the others need to be given as well.

User feedback is given by using the flash mechanism and after succesful validation the form is altered accordingly by removing certain elements and freezing the form to an ineditable state. If you look closely at the ActiveForm::Element::Base.define_element_wrapper portion below you will notice how optional/empty fields are removed from the frozen view; which is what the user will see once submitted.

go back to activeform.rubyforge.org

Contact Form Screenshot
# app/forms/definitions/contact.rb

require 'forms/views/default'

ActiveForm::Definition::create :contact do |f|
  
  f.section :details do |s|
    s.text_element :name, :class => 'required' do |e|
      e.validates_as_required :msg => 'enter your name'
      e.validates_within_length_range :range => (3..64), :msg => 'your name should be at least 3 characters long'
    end
    s.text_element :email, :class => 'required' do |e| 
      e.validates_as_required :msg => 'enter a valid email address'
      e.validates_as_email :msg => 'enter a valid email address'
    end
    s.text_element :phone
    s.text_element :address
    s.text_element :postal_code
    s.text_element :city
    s.submit_element :submit, :label => 'Submit', :class => 'fancy_submit' 
    
    s.define_validation do |elem|  
      group = [ elem[:address], elem[:postal_code], elem[:city] ]
      partitioned = group.partition { |e| e.blank? }
      if !partitioned.first.empty? && !partitioned.last.empty?
        partitioned.first.each do |e| 
          case e.name
            when :address then e.errors.add('enter your address', 'required')
            when :postal_code then e.errors.add('enter your postal code', 'required')
            when :city then e.errors.add('enter your city', 'required')
          end
        end
      end
    end       
  end
  
  f.section :message do |s|
    s.textarea_element :message, :class => 'required', :rows => 20, :cols => 40 do |e|
      e.define_casting_filter { |value| value.strip }
      e.validates_as_required :msg => 'enter your message'
      e.validates_within_length_range :range => (10..1600), :msg => 'your message should be at least 10 characters long'
    end
  end
  
  f.after_validation do |form|
    if form.valid?
      form.remove_elements_of_type :submit, :button
      form.freeze!
    end
  end
  
end
# app/controllers/main_controller.rb

class MainController < ApplicationController
  
  def contact
    @section_name = 'contact'
    @form = build_active_form :contact, :contact_form
    if @form.submitted?
      if @form.validated?
        begin
          values = @form.export_values
          ContactMailer.deliver_forward values
          ContactMailer.deliver_auto_reply values
          flash.now[:message] = { :title => 'Thank you', :msg => 'A copy of your email has been sent for your reference.' }
        rescue
          flash.now[:message] = { :title => 'Message not sent', :msg => 'An error prevented your message from being sent.' }
        end
      else
        flash.now[:message] = { :title => 'Invalid form', :msg => 'The following errors prevented the form from being sent:' }
        flash.now[:message][:errors] = @form.initial_errors.collect(&:message)
      end      
    end
  end
  
end
<!-- extracted from app/views/main/contact.rhtml -->

<body>
    <div id="container">
        <div id="header"></div>
        <div id="subheader">
            <% if flash[:message] -%>
            <div id="message" class="<%= flash[:message][:errors] ? 'error' : 'info' %>">
                <% if flash[:message][:title] -%><h4><%= flash[:message][:title] %></h4><% end -%>
                <% if flash[:message][:msg] -%><p><%= flash[:message][:msg] %></p><% end -%>
                <% if flash[:message][:errors] %>
                <ul><% for error in flash[:message][:errors] -%><li><%= error %></li><% end -%></ul>                
                <% end -%>
            </div>  
            <% end -%>
        </div>
        <div id="main">
            <div class="section">
                <%= @form.header %>
                <div class="column">
                    <%= @form[:details] -%>
                </div>
                <div class="column">
                    <%= @form[:message] -%>
                </div>
                <%= @form.footer %>
            </div>
            <div id="sidebar">
                <%= render :partial => 'shared/address' %>
            </div>
        </div>
        <div id="footer"></div> 
    </div>
</body>
# environment.rb

ActiveForm::Definition.load_paths << File.join(RAILS_ROOT, 'app', 'forms', 'definitions')
# app/forms/views/default.rb (included in app/forms/definitions/contact.rb)

ActiveForm::Element::Section.define_element_wrapper do |builder, elem, render|
  builder.fieldset(:class => 'section') {
    builder.div(:id => "section-#{elem.identifier}", &render)
  }
end

ActiveForm::Element::Base.define_element_wrapper do |builder, elem, render|
  return if elem.frozen? && elem.export_value.blank?
  builder.div(:id => "elem_#{elem.identifier}", :class => elem.css, :style => elem.style) {
    elem.render_label(builder)
    elem.frozen? ? builder.div(:class => 'text') { render.call(builder) } : render.call(builder)
  }
end

# ActiveForm Copyright (c) 2007 atelierfabien - loobmedia
# released under MIT license