Formtastic without ActiveRecord
So I have been doing a fair amount of Rails development recently. I’ve been using Formtastic, which provides simple and consistent semantics for creating web forms. It’s a nice library and quite well designed. It is intended for use with ActiveRecord model objects, and trying to create forms that aren’t populating an ActiveRecord object can be challenging. The library’s author, Justin French, made an intriguing comment on StackOverflow: “It’s really not that hard to create a model instance to wrap up the concerns of your model. Just keep implementing the methods Formtastic is expecting until you get it working!”
I needed to put together a form to gather information for submission to a remote website (SalesForce). I didn’t have a model for the resulting data; that’s being tracked by SalesForce. Since I needed exactly what Justin described, I created one. He’s right, it wasn’t hard. I thought others might find it useful. Note: this will only work with Rails 3, and the example below uses HAML as the templating language.
I’ll start by showing usage. This should be pretty familiar to a Rails coder.
class ContactRequest < FormtasticFauxModel
attr_accessor :first_name, :last_name, :company, :email, :newsletter_opt_in
validates :first_name, :last_name, :presence => true
validates :email, :presence => true, :email => true
self.types = {
:newsletter_opt_in => :boolean,
}
end
The only unusual part is the self.types section, which I believe should be pretty self-explanatory. It gets passed through to Formtastic to inform what type of input to render, and the supported types are defined by Formtastic. This could be achieved by using Formtastic’s “:as => [type]” directive directly, but I prefer to define things once and in one place.
The ContactRequest object can now be used much like a regular ActiveRecord object. The controller manages an @contact variable, eg @contact = ContactRequest.new(params[:contact_request]), and then the view looks like any other formtastic form:
#header
%h1 Contact Us
#body
%h2 Contact Us
%p Please provide the following information:
= semantic_form_for(@contact) do |form|
= form.inputs do
= form_errors(@contact)
= form.input :first_name
= form.input :last_name
= form.input :email
= form.input :newsletter_opt_in, :label => "Send me the newsletter"
= form.input :company
= form.buttons do
= form.commit_button :label => "Send it to me!"
All pretty standard.
So, with no further ado, here’s the FormtasticFauxModel class.
# -------------------------------------------------------------------------------
# Faux model object that plays well with formtastic; this is used for example in
# a contact form which generates a request to salesforce
# -------------------------------------------------------------------------------
class FormtasticFauxModel
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
# Subclass may provide a types hash. Any attributes not listed will
# default to string.
# self.types = {
# :description => :text,
# :state => {:type => :string, :limit => 2}
# :country => :string,
# :newsletter_opt_in => :boolean,
# }
class << self
attr_accessor :types
end
self.types = {}
# So the controller can say "@contact = Contact.new(params[:contact])"
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
# So form_for works correctly -- we only do "new" forms
def persisted? ; false ; end
# To provide the type information
def column_for_attribute(attr)
FauxColumnInfo.new(self.class.types[attr])
end
class FauxColumnInfo
attr_accessor :type, :limit
def initialize(type_info)
type_info ||= :string
case
when type_info.instance_of?(Hash), type_info.instance_of?(OpenStruct)
self.type = type_info[:type].to_sym
self.limit = type_info[:limit]
else
self.type = type_info.to_sym
self.limit = nil
end
end
end
endEnjoy!