Last Updated: February 25, 2016
·
12.36K
· mcmillion

Custom Form Builders in Rails

Rails is packed with tools to cleanup your codebase, and customizing these tools can sometimes take that a step further.

In a recent project, our forms had begun to get cluttered. Special styling and markup for displaying validation and icons were being repeated for each form control.

/ login.html.slim

= form_for(:user, { url: '/login' }) do |f|
  h1 Sign Into Your Account

  .field#field-username
    ul.errorlist
    .field-wrapper
      .indicator
      = f.text_field :username, placeholder: 'Username'

  .field#field-password
    ul.errorlist
    .field-wrapper
      .indicator
      = f.password_field :password, placeholder: 'Password'

  .submit
    = f.submit 'Sign In', class: 'button'

What we needed was a way to encapsulate these chunks of markup into reusable pieces. Luckily, through the power of inheritance, we can do just that.

Creating a New Form Builder

The first step is to sublcass the ActionView::Helpers::FormBuilder class so we can begin overriding pieces of it for our own use. Since this builder is going to take over for our vertically-oriented forms, we'll call it VerticalFormBuilder.

# app/assets/helpers/vertical_form_builder.rb

class VerticalFormBuilder < ActionView::Helpers::FormBuilder

  ...

end

The next step is to override the methods that create the various form elements. Inside those methods, we can supply our own markup (using content_tag) and then simply call super. If we pass in the correct arguments in our overridden methods, we can keep using all of the existing functionality supplied by Rails. Since we'll also be using a similar wrapper for some of the form fields, we'll break that out into its own function.

def field_wrapper(attribute, args, &block)
  @template.content_tag(:div, { class: 'field', id: "field-#{attribute}" }) do
    @template.content_tag(:ul, '', class: 'errorlist') +
    @template.content_tag(:div, class: 'field-wrapper') do
      @template.content_tag(:div, '', class: 'indicator') +
      block.call
    end
  end
end

def text_field(attribute, args)
  field_wrapper(attribute, args) do
    super(attribute, args)
  end
end

def password_field(attribute, args)
  field_wrapper(attribute, args) do
    super(attribute, args)
  end
end

Adding a Helper Method

Now that we have the class to handle our form building, we need to add a helper to make calling the form easier in our markup. This lives in a module that Rails will pick up automatically (as a helper). Our custom form builder is used by adding a new method that calls form_for with an argument supplying the modified builder class.

module VerticalFormHelper

  def vertical_form_for(name, *args, &block)
    options = args.extract_options!
    args << options.merge(builder: VerticalFormBuilder)
    form_for(name, *args, &block)
  end

end

Cleaner Markup

With these changes, our form markup now looks something like this:

= vertical_form_for(:user, { url: '/login' }) do |f|
  h1 Sign Into Your Account

  = f.text_field :username, placeholder: 'Username'
  = f.password_field :password, placeholder: 'Password'

  .submit
    = f.submit 'Sign In', class: 'button'

A huge improvement, especially if we ever have to make major changes to the way forms look throughout the app.

Next Steps...

This can be extended much further. We use similar methods to wrap validation settings and markup, and when paired with a controller, model validation, and some standardized Javascript, our forms all have real-time validation simply by using our custom form builder.