Daily Code Reading #26 – Formtastic semantic_form_for

I’m starting to read through a new code base this week. In my latest plugin I’ve integrated formtastic with Redmine, so I decided to start reading through some of formtastic’s methods to better understand how it works.

The Code

To start using formtastic, you use the semantic_form_for method so this will be a prime spot to start with.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module Formtastic #:nodoc:
  module SemanticFormHelper
    [:form_for, :fields_for, :remote_form_for].each do |meth|
      module_eval <<-END_SRC, __FILE__, __LINE__ + 1
        def semantic_#{meth}(record_or_name_or_array, *args, &proc)
          options = args.extract_options!
          options[:builder] ||= @@builder
          options[:html] ||= {}
 
          class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
          class_names << "formtastic"
          class_names < "post"
            when Array then ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class)  # [@post, @comment] # => "comment"
            else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class)                  # @post => "post"
          end
          options[:html][:class] = class_names.join(" ")
 
          with_custom_field_error_proc do
            #{meth}(record_or_name_or_array, *(args << options), &proc)
          end
        end
      END_SRC
    end
  end
end

Review

formtastic is using Ruby’s module_eval to define semantic_form_for so the first thing I need to do is to evaluate the method into the actual Ruby code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def semantic_form_for(record_or_name_or_array, *args, &proc)
  options = args.extract_options!
  options[:builder] ||= @@builder
  options[:html] ||= {}
 
  class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
  class_names << "formtastic"
  class_names < "post"
    when Array then ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class)  # [@post, @comment] # => "comment"
    else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class)                  # @post => "post"
  end
  options[:html][:class] = class_names.join(" ")
 
  with_custom_field_error_proc do
    form_for(record_or_name_or_array, *(args << options), &proc)
  end
end

Now this code is easier to read.

Options setup

1
2
3
  options = args.extract_options!
  options[:builder] ||= @@builder
  options[:html] ||= {}

The first part of semantic_form_for extracts the options and sets up a few defaults. @@builder is initialized to Formtastic::SemanticFormBuilder but it can be overridden to use another builder class.

HTML class names

1
2
3
4
5
6
7
  class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
  class_names << "formtastic"
  class_names < "post"
    when Array then ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class)  # [@post, @comment] # => "comment"
    else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class)                  # @post => "post"
  end
  options[:html][:class] = class_names.join(" ")

Next semantic_form_for builds up a set of html classes for the form:

  • method options from the caller
  • a hardcoded “formtastic”
  • the name of the model of the form, based on a symbol or an instance of the model

Rails form_for with custom errors formatting

1
2
3
  with_custom_field_error_proc do
    form_for(record_or_name_or_array, *(args << options), &proc)
  end

Finally semantic_form_for calls Rails’ form_for but it replaces it replaces how the error messages are shown by wrapping the method in #with_custom_error_proc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Override the default ActiveRecordHelper behaviour of wrapping the input.
# This gets taken care of semantically by adding an error class to the LI tag
# containing the input.
#
FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
  html_tag
end
 
def with_custom_field_error_proc(&block)
  @@default_field_error_proc = ::ActionView::Base.field_error_proc
  ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
  result = yield
  ::ActionView::Base.field_error_proc = @@default_field_error_proc
  result
end

What it’s doing is to:

  1. change ActionView::Base.field_error_proc to use the custom FIELD_ERROR_PROC object
  2. yields the control (which calls form_for)
  3. then replaces the ActionView::Base.field_error_proc

This is done so semantic_form_for can control how the errors are displayed without over-ridding the non-formtastic forms.

Now that I understand how formtastic‘s form builder is setup, I can start reading through the field generation methods.