Today I’m looking into formtastic‘s SemanticFormBuilder#default_input_type
. It’s used to guess what type of data is stored in a field so it can show the correct form element.
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 33 34 35 36 37 38 39 |
module Formtastic #:nodoc: class SemanticFormBuilder < ActionView::Helpers::FormBuilder def default_input_type(method, options = {}) #:nodoc: if column = self.column_for(method) # Special cases where the column type doesn't map to an input method. case column.type when :string return :password if method.to_s =~ /password/ return :country if method.to_s =~ /country$/ return :time_zone if method.to_s =~ /time_zone/ when :integer return :select if method.to_s =~ /_id$/ return :numeric when :float, :decimal return :numeric when :timestamp return :datetime end # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database. return :select if column.type == :string && options.key?(:collection) # Try 3: Assume the input name will be the same as the column type (e.g. string_input). return column.type else if @object return :select if self.reflection_for(method) file = @object.send(method) if @object.respond_to?(method) return :file if file && @@file_methods.any? { |m| file.respond_to?(m) } end return :select if options.key?(:collection) return :password if method.to_s =~ /password/ return :string end end end end |
Review
#default_input_type
has two main branches for it’s checks:
- Is the field a database column?
- Or is the field a virtual column (e.g.
attr_accessor
)
This logic for this is handled by ActiveRecord’s column_for
method and it will either return an ActiveRecord column object or nil.
1 2 3 4 5 |
>> Issue.send(:column_for, :subject) # >> Issue.send(:column_for, :not_a_column) nil |
Database columns
1 2 3 4 5 6 7 8 9 10 11 12 13 |
case column.type when :string return :password if method.to_s =~ /password/ return :country if method.to_s =~ /country$/ return :time_zone if method.to_s =~ /time_zone/ when :integer return :select if method.to_s =~ /_id$/ return :numeric when :float, :decimal return :numeric when :timestamp return :datetime end |
When the method
matches a database column, #default_input_type
will first use the column type and method name to try to match on a few special cases. For example a :string with ‘password’ in the method name would become a password field. Integer fields for foreign keys would become selects.
1 2 |
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database. return :select if column.type == :string && options.key?(:collection) |
Next #default_input_type
checks for enumeration keys that are typically saved to the database as strings.
1 |
return column.type |
If none of the previous checks matched, the method will just return the column type.
Virtual Columns
1 |
return :select if self.reflection_for(method) |
formtastic defines #reflection_for
, which uses ActiveRecord’s reflect_on_association
to see if a method is an association. If so, it returns a select field so you can populate the association directly from the form.
1 2 3 4 5 6 |
@@file_methods = [ :file?, :public_filename, :filename ] # ... file = @object.send(method) if @object.respond_to?(method) return :file if file && @@file_methods.any? { |m| file.respond_to?(m) } |
Here formtastic is doing some checks to see if the method is one that is used to add a file upload. It calls the method to create a new instance of the object and then checks if it responds to any of the @@file_methods
(file?
, public_filename
, filename
by default).
1 2 |
return :select if options.key?(:collection) return :password if method.to_s =~ /password/ |
If the field isn’t for an association or a file upload, formtastic then checks if it should display a select field for a collection or a password field.
1 |
return :string |
If none of the checks have matched, then default_input_type
will default to a string field.
Other than the reflections and file upload fields, default_input_type
is just doing some pretty straight forward matching. It starts with the most specific matches and slowly broadens out until it returns either the database column type or a string.