Writing a Custom FormBuilder in Rails

I’m currently working on a Ruby on Rails application where I need lots of text fields that have the same properties. I decided to override text_field and have it output all of the extra attribues automatically. This helps keep the view code cleaner, lets me change all of these text fields in one place, and also helps me avoid typing mistakes (at least in this section of the code). Thanks to Ruby on Rails core team member Rick Olson (aka technoweenie) for pointing me to his LabeledFormHelper plugin, which taught me this technique.

Before the custom FormBuilder my initial view code looked something like:

 
<% form_for(:spreadsheet, @spreadsheet, :url => { :action => 'create' }) do |s| %>
      
    <% fields_for :numbers, @spreadsheet.numbers do |f| %>
    
      <%= f.text_field :field1, :onkeypress => 'return isNumberKey(event);', :maxlength => 3 %>
      <%= f.text_field :field2, :onkeypress => 'return isNumberKey(event);', :maxlength => 3 %>

      ... and so on
      
    <% end %>
<% end %>

So I created a custom FormBuilder in my helper to do the extra text_field work for me:

module SpeadsheetsHelper
  
  # tell all of these methods to use my custom FormBuilder
  [:form_for, :fields_for, :form_remote_for, :remote_form_for].each do |meth|
    src = <<-end_src
      def speadsheets_#{meth}(object_name, *args, &proc)
        options = args.last.is_a?(Hash) ? args.pop : {}
        options.update(:builder => SpeadsheetsFormBuilder)
        #{meth}(object_name, *(args << options), &proc)
      end
    end_src
    module_eval src, __FILE__, __LINE__
  end

  # the custom FormBuilder
  class SpeadsheetsFormBuilder < ActionView::Helpers::FormBuilder    
    
    # add onkeypress and set maxlength of field to 3 to all text fields
    def text_field(method, options={})
      super(method, options.merge(:onkeypress => 'return isNumberKey(event);', :maxlength => 3))
    end

  end
end

Now my view code is much simpler and helps avoid goofups:

<% spreadsheets_form_for(:spreadsheet, @spreadsheet, :url => { :action => 'create' }) do |s| %>
      
    <% spreadsheets_fields_for :numbers, @spreadsheet.numbers do |f| %>
    
      <%= f.text_field :field1 %>
      <%= f.text_field :field2 %>

      ... and so on
      
    <% end %>
<% end %>

Notice in the view that instead of using form_for and fields_for you need to use the custom methods spreadsheets_form_for and spreadsheets_fields_for which were created in SpeadsheetsHelper.

5 Responses

  1. Why not generating symbol automatically ? and so having something like this (without all the helper overload mess)

    ‘return isNumberKey(event);’, :maxlength => 3 } %>

  2. problems with blog parser ?

    My last comment with a less “scary” typo

    Why not generating symbol automatically ? and so having something like this

  3. in html maybe ?
    <%= 1.upto(a_number) { |i| f.text_field :”field#{i}” } %>

  4. Coin, you’re right – if that’s all I needed to do there are other ways. I actually have quite a few lines of code that do other various things inside of my FormBuilder and text_field method. This was more of an example how to get started creating a FormBuilder.

  5. Coin, also, my view is a bit more complex than this example. Each field is unique and it isn’t actually a spreadsheet application.