Yesterday I read through formtastic‘s SemanticFormBuilder#semantic_form_for
. Today I’m reading through SemanticFormBuilder#inputs
which formtastic uses to create a fieldset and automatically generate all of the form fields needed.
The Code
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 26 27 28 29 30 31 32 |
module Formtastic #:nodoc: class SemanticFormBuilder < ActionView::Helpers::FormBuilder def inputs(*args, &block) title = field_set_title_from_args(*args) html_options = args.extract_options! html_options[:class] ||= "inputs" html_options[:name] = title if html_options[:for] # Nested form inputs_for_nested_attributes(*(args << html_options), &block) elsif block_given? field_set_and_list_wrapping(*(args << html_options), &block) else if @object && args.empty? args = self.association_columns(:belongs_to) args += self.content_columns args -= RESERVED_COLUMNS args.compact! end legend = args.shift if args.first.is_a?(::String) contents = args.collect { |method| input(method.to_sym) } args.unshift(legend) if legend.present? field_set_and_list_wrapping(*((args << html_options) << contents)) end end alias :input_field_set :inputs end end |
Review
The first part of #inputs
just sets up options like the title and css classes. The if
statement is where formtastic does the form generation:
- When the
:for
option is passed,#inputs
will build fields for nested attributes using#inputs_for_nested_attributes
. - When a block is passed (like with explicit
input
calls) then#field_set_and_list_wrapping
is called which generates the block’s HTML and wraps it in a fieldset. - Otherwise,
#inputs
will automatically generate form fields for the object based on the model.
Automatic form fields generation
I wanted to take a closer look at how formtastic automatically generates the form fields because this is what attracted me to formtastic in the first place.
1 2 3 4 5 6 7 8 9 10 11 |
if @object && args.empty? args = self.association_columns(:belongs_to) args += self.content_columns args -= RESERVED_COLUMNS args.compact! end legend = args.shift if args.first.is_a?(::String) contents = args.collect { |method| input(method.to_sym) } args.unshift(legend) if legend.present? field_set_and_list_wrapping(*((args << html_options) << contents)) |
When there is an @object
but no args
, formtastic builds a list of the @object
‘s columns and of the associated belongs_to models. Then reserved columns like “created_at”, “updated_at”, and “lock_version” are removed from the list so fields are not created for them (they shouldn’t be editable).
1 |
legend = args.shift if args.first.is_a?(::String) |
Next the legend is generated from the first arg. This confused me at first because the code above overrode the args with an array of the column names and I thought legend would pick up the first column name to use that as the legend. I was wrong though, the column names come across as Symbols so the guard on that line wouldn’t run. For example: args are [:title, :description, :name]
has no strings.
Next, #inputs
runs #input
on each column to create the field. This, combined with the column finder above, is how formtastic can automatically find all of the columns from the model that need fields built. So it looks like the #input
method will be the next code I read to figure out how the fields are actually created.