DRY Up Forms with Custom Form Builders
Revisiting Our Form
Continuing with the example from my last article, I have this simple form:
<% form_for :product, :url => products_path do |f| %><%= f.text_field :name %><%= f.text_field :price %><%= f.text_area :features %><% end %>
Note: I changed the original form’s wrapper tags from
to
. The reason is that, if you’re using the built-in Rails error message helpers, by default it will wrap erroneous fields in
tags. Since
tags can’t nest within
tags, this can cause serious layout-breaking problems.
There’s a lot of duplication here in terms of the wrapper code. Each field is wrapped in a
with a class of form_item as well as a descriptor of what kind of field it contains. I use this information for CSS styling of specific kinds of inputs. The labels are also fairly repetitive as well, and these repetitions would become more obnoxious were this a longer form.
Removing Repetition
What if we could generate this same form code while removing the repetition? It’s possible, and Rails gives us a great hook for just this scenario by allowing us to build custom Form Builders. A custom Form Builder is simply a subclass of ActionView::Helpers::FormBuilder that can alter and extend the abilities of the regular Form Builder. In our current form, the f block variable is an instance of FormBuilder, so methods like text_field and submit are all instance methods of the FormBuilder class. Let’s override these methods to not only output the field markup, but to also output the wrapper div:
# in /helpers/application_helper.rb class WrapperFormBuilder < ActionView::Helpers::FormBuilder METHODS_TO_OVERRIDE = %w{text_field text_area password_field file_field date_select datetime_select submit} METHODS_TO_OVERRIDE.each do |method_name| src =<<END_SRC def #{method_name}_with_wrapper(field, options={}) # allow explicit setting of label text with options[:label] field_label = if '#{method_name}' == 'submit' '' # no label for submit inputs elsif options[:label] label(field, options.delete(:label)) else label(field) + ":" # Adds colon as default end # get unwrapped field field_markup = #{method_name}_without_wrapper(field, options) # return wrapped field (@template gives us access to helper methods in this class) @template.content_tag(:div, field_label + field_markup, :class => "form_item #{method_name}") end END_SRC class_eval src, __FILE__, __LINE__ alias_method_chain method_name.to_sym, :wrapper end end
For our form, we can now implement our new class like this:
<% form_for :product, :url => products_path, :builder => WrapperFormBuilder do |f| %>
Another possibility is to create a new method to replace form_for:
# in helpers/application_helper.rb def wrapper_form_for(name, object=nil, options={}, &proc) form_for(name, object, options.merge(:builder => WrapperFormBuilder), &proc) end
Using this new helper method, our form now looks like this:
<% wrapper_form_for :product, :url => products_path do |f| %> <%= f.text_field :name %> <%= f.text_field :price %> <%= f.text_area :features, :label => "Features (1 per line):" %> <%= f.submit "Create Product" %> <% end %>
If you need to create a form field without a wrapper (perhaps to use a non-conforming wrapper), you can still access the original methods like f.text_field_without_wrapper or f.submit_without_wrapper.
I have found custom Form Builders to be powerful tools for speeding up form development, DRYing up code and keeping consistency between forms and developers. This is a fairly basic example, but the sky is the limit for what you can implement using these techniques.
- Person:
- Programming Language:
- Tags:



Recent comments
1 year 23 weeks ago
1 year 23 weeks ago
1 year 25 weeks ago
1 year 27 weeks ago
1 year 42 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 46 weeks ago
1 year 48 weeks ago