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:
- change
ActionView::Base.field_error_proc
to use the customFIELD_ERROR_PROC
object - yields the control (which calls
form_for
) - 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.