How to make a custom form builder in Rails

A Form builder is a great way to remove repetative code from your form views or to include some custom code into the existing helpers.
I wanted to change how I displayed error information in my forms and decided to do it in a form builder and this is how.

Overview

There are two common ways of displaying error information, in an error block at the top of the page via error_messages_for :model or inline above or below the field using error_message_on :model, :field

When using error_message_on :model, :field I find that I always have to use the :prepend_text to get the error message to display nicely. So my error handling always looks like this:

<%= form.label :name %>
<%= error_message_on :user, :name, :prepend_text => 'Name ' %>
<%= form.text_field :name %>

and the form is displayed as:

Name
Name can’t be blank

I then thought that the label is already displaying the “prepend_text” so why not inline the error message directly after the label. With a custom form builder (code below), my code just looks like this and the errors will still be displayed:

<%= form.label :name %>
<%= form.text_field :name %>

and the form is displayed as:

Name can’t be blank

The code

This form builder is going to be very simple because I’m only changing the handling of the label helper but the concepts carry through to any of the form helpers you want to extend.

Because I need to get to the errors object on the model, I had to bring in some repeat code from the label_tag helper. The default Rails form builder can be found in [rails/gem path]/actionpack/lib/action_view/helpers/form_helper.rb starting just after line 700.
Unfortunately the FormBuilder seems to be undocumented so you won’t find documentation in the Rails API docs.

I’ve written comments throughout the code to explain what I’ve done.

[lib/error_form_builder.rb]

class ErrorFormBuilder < ActionView::Helpers::FormBuilder
  #Adds error message directly inline to a form label
  #Accepts all the options normall passed to form.label as well as:
  #  :hide_errors - true if you don't want errors displayed on this label
  #  :additional_text - Will add additional text after the error message or after the label if no errors
  def label(method, text = nil, options = {})
    #Check to see if text for this label has been supplied and humanize the field name if not.
    text = text || method.to_s.humanize
    #Get a reference to the model object
    object = @template.instance_variable_get("@#{@object_name}")

    #Make sure we have an object and we're not told to hide errors for this label
    unless object.nil? || options[:hide_errors]
      #Check if there are any errors for this field in the model
      errors = object.errors.on(method.to_sym)
      if errors
        #Generate the label using the text as well as the error message wrapped in a span with error class
        text += " <span class=\"error\">#{errors.is_a?(Array) ? errors.first : errors}</span>"
      end
    end
    #Add any additional text that might be needed on the label
    text += " #{options[:additional_text]}" if options[:additional_text]
    #Finally hand off to super to deal with the display of the label
    super(method, text, options)
  end
end

Using your custom form builder

If you want to use your custom form builder for a single form, include this in your form declaration:

<% form_for :object, :builder => ErrorFormBuilder do |form| %>
<% end %>

If you want to make your custom form builder the default builder for your project, you can add the following line to your config/environment.rb file:

ActionView::Base.default_form_builder = ErrorFormBuilder

If you have any questions on how this works, feel free to ask in the comments.

Comments (4)

Ben HughesJanuary 9th, 2009 at 01:22

Cool technique, I’ve been meaning to play around with some custom form builders to be more DRY. Also, in the first above code snippet it should be “class ErrorFormBuilder < ActionView::Helpers::FormBuilder” ().

Andrew TimberlakeJanuary 9th, 2009 at 05:21

Thanks Ben, got mixed up when fixing &gt; & &lt; - I’ve fixed it in the code.

[...] How to make a custom form builder in Rails [...]

Jared FineFebruary 2nd, 2009 at 23:49

Handy article. Thanks Andrew.