class SimpleForm::FormBuilder
def self.discovery_cache
def self.discovery_cache @discovery_cache ||= {} end
def association(association, options = {}, &block)
Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
From the options above, only :collection can also be supplied.
end
c.input :type
c.input :name
f.association :company do |c|
simple_fields_for:
When a block is given, association simple behaves as a proxy to
== Block
# Same as using :order option, but overriding collection
f.association :company, collection: Company.all(order: 'name')
end
f.association :company # Company.all
simple_form_for @user do |f|
== Examples
can also be given:
supported in input are also supported by association. Some extra options
collection automatically. It's just a wrapper to input, so all options
Helper for dealing with association selects/radios, generating the
def association(association, options = {}, &block) options = options.dup return simple_fields_for(*[association, options.delete(:collection), options].compact, &block) if block_given? raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object reflection = find_association_reflection(association) raise "Association #{association.inspect} not found" unless reflection options[:as] ||= :select options[:collection] ||= fetch_association_collection(reflection, options) attribute = build_association_attribute(reflection, association, options) input(attribute, options.merge(reflection: reflection)) end
def attempt_mapping(mapping, at)
def attempt_mapping(mapping, at) return if SimpleForm.inputs_discovery == false && at == Object begin at.const_get(mapping) rescue NameError => e raise unless e.message.include?(mapping) end end
def attempt_mapping_with_custom_namespace(input_name)
def attempt_mapping_with_custom_namespace(input_name) SimpleForm.custom_inputs_namespaces.each do |namespace| if (mapping = attempt_mapping(input_name, namespace.constantize)) return mapping end end nil end
def build_association_attribute(reflection, association, options)
def build_association_attribute(reflection, association, options) case reflection.macro when :belongs_to (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id" when :has_one raise ArgumentError, ":has_one associations are not supported by f.association" else if options[:as] == :select || options[:as] == :grouped_select html_options = options[:input_html] ||= {} html_options[:multiple] = true unless html_options.key?(:multiple) end # Force the association to be preloaded for performance. if options[:preload] != false && object.respond_to?(association) target = object.send(association) target.to_a if target.respond_to?(:to_a) end :"#{reflection.name.to_s.singularize}_ids" end end
def build_input_field_components(components)
def build_input_field_components(components) components.map do |component| if component == :input SimpleForm::Wrappers::Leaf.new(component, build_input_field_options) else SimpleForm::Wrappers::Leaf.new(component) end end end
def build_input_field_options
def build_input_field_options input_field_options = {} valid_class = SimpleForm.input_field_valid_class error_class = SimpleForm.input_field_error_class if error_class.present? input_field_options[:error_class] = error_class end if valid_class.present? input_field_options[:valid_class] = valid_class end input_field_options end
def button(type, *args, &block)
def button(type, *args, &block) options = args.extract_options!.dup options[:class] = [SimpleForm.button_class, options[:class]].compact args << options if respond_to?(:"#{type}_button") send(:"#{type}_button", *args, &block) else send(type, *args, &block) end end
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
* item_wrapper_class => the CSS class to use for item_wrapper_tag
* item_wrapper_tag => the tag to wrap each item in the collection.
is ignored if the :collection_wrapper_tag option is blank.
* collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
* collection_wrapper_tag => the tag to wrap the entire collection.
item or an array of items.
* disabled => the value or values that should be disabled. Accepts a single
a single item or an array of items. It overrides existing associations.
* checked => the value or values that should be checked initially. Accepts
Collection check box accepts some extra options:
== Options
end
end
b.label { b.check_box + b.text }
) do |b|
:options, [[true, 'Yes'] ,[false, 'No']], :first, :last
f.collection_check_boxes(
form_for @user do |f|
label. To wrap the check box with the label, for instance:
It is also possible to give a block that should generate the check box +
end
f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
form_for @user do |f|
== Examples
that will be evaluated for each item in the collection.
You can give a symbol or a proc to both value_method and text_method,
convert items in the collection for use as text/value in check boxes.
associated with a clickable label. Use value_method and text_method to
Creates a collection of check boxes for each item in the collection,
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block) SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block) end
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
* item_wrapper_class => the CSS class to use for item_wrapper_tag
* item_wrapper_tag => the tag to wrap each item in the collection.
* collection_wrapper_class => the CSS class to use for collection_wrapper_tag
* collection_wrapper_tag => the tag to wrap the entire collection.
item or an array of items.
* disabled => the value or values that should be disabled. Accepts a single
* checked => the value that should be checked initially.
Collection radio accepts some extra options:
== Options
end
end
b.label { b.radio_button + b.text }
) do |b|
:options, [[true, 'Yes'] ,[false, 'No']], :first, :last
f.collection_radio_buttons(
form_for @user do |f|
label. To wrap the radio with the label, for instance:
It is also possible to give a block that should generate the radio +
end
f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
form_for @user do |f|
== Examples
the collection.
value_method and text_method, that will be evaluated for each item in
to convert these text/value. You can give a symbol or a proc to both
text/value option in the collection, using value_method and text_method
helper will create a radio input associated with a label for each
Create a collection of radio inputs for the attribute. Basically this
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block) SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block) end
def default_input_type(attribute_name, column, options)
default always fallback to the user :as option, or to a :select when a
Attempt to guess the better input type given the defined options. By
def default_input_type(attribute_name, column, options) return options[:as].to_sym if options[:as] custom_type = find_custom_type(attribute_name.to_s) and return custom_type return :select if options[:collection] input_type = column.try(:type) case input_type when :timestamp :datetime when :string, :citext, nil case attribute_name.to_s when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url else file_method?(attribute_name) ? :file : (input_type || :string) end else input_type end end
def discovery_cache
If cache_discovery is enabled, use the class level cache that persists
def discovery_cache if SimpleForm.cache_discovery self.class.discovery_cache else @discovery_cache ||= {} end end
def error(attribute_name, options = {})
f.error :name, id: "cool_error"
f.error :name
== Examples
contains errors. All the given options are sent as :error_html.
Creates an error tag based on the given attribute, only when the attribute
def error(attribute_name, options = {}) options = options.dup options[:error_html] = options.except(:error_tag, :error_prefix, :error_method) column = find_attribute_column(attribute_name) input_type = default_input_type(attribute_name, column, options) wrapper.find(:error). render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options)) end
def error_notification(options = {})
f.error_notification id: 'user_error_message', class: 'form_error'
f.error_notification message: 'Something went wrong'
f.error_notification
== Examples
passed straight as html options to the html tag.
otherwise it will look for a message using I18n. All other options given are
has some error. You can give a specific message with the :message option,
Creates an error notification message that only appears when the form object
def error_notification(options = {}) SimpleForm::ErrorNotification.new(self, options).render end
def fetch_association_collection(reflection, options)
def fetch_association_collection(reflection, options) options.fetch(:collection) do relation = reflection.klass.all if reflection.respond_to?(:scope) && reflection.scope if reflection.scope.parameters.any? relation = reflection.klass.instance_exec(object, &reflection.scope) else relation = reflection.klass.instance_exec(&reflection.scope) end else order = reflection.options[:order] conditions = reflection.options[:conditions] conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call) relation = relation.where(conditions) if relation.respond_to?(:where) && conditions.present? relation = relation.order(order) if relation.respond_to?(:order) end relation end end
def file_method?(attribute_name)
- `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility)
- `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0`
- `remote_#{attribute_name}_url` - Refile >= `0.3.0` and CarrierWave >= `0.2.2`
- `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
The order here was chosen based on the popularity of Gems:
Note: This does not support multiple file upload inputs, as this is very application-specific.
of their methods using `#respond_to?`.
This method tries to guess if an attribute belongs to some of these Gems by checking the presence
Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in.
Internal: Try to discover whether an attribute corresponds to a file or not.
def file_method?(attribute_name) @object.respond_to?("#{attribute_name}_attachment") || @object.respond_to?("#{attribute_name}_attachments") || @object.respond_to?("remote_#{attribute_name}_url") || @object.respond_to?("#{attribute_name}_attacher") || @object.respond_to?("#{attribute_name}_file_name") end
def find_association_reflection(association)
def find_association_reflection(association) if @object.class.respond_to?(:reflect_on_association) @object.class.reflect_on_association(association) end end
def find_attribute_column(attribute_name)
def find_attribute_column(attribute_name) if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name) @object.type_for_attribute(attribute_name.to_s) elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name) @object.column_for_attribute(attribute_name) end end
def find_custom_type(attribute_name)
def find_custom_type(attribute_name) SimpleForm.input_mappings.find { |match, type| attribute_name =~ match }.try(:last) if SimpleForm.input_mappings end
def find_input(attribute_name, options = {}, &block)
def find_input(attribute_name, options = {}, &block) column = find_attribute_column(attribute_name) input_type = default_input_type(attribute_name, column, options) if block_given? SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block) else find_mapping(input_type).new(self, attribute_name, column, input_type, options) end end
def find_mapping(input_type)
2) If not, fallbacks to #{input_type}Input
b) Or use the found mapping
a) Try to find an alternative with the same name in the Object scope
1) It tries to find a registered mapping, if succeeds:
Attempts to find a mapping. It follows the following rules:
def find_mapping(input_type) discovery_cache[input_type] ||= if mapping = self.class.mappings[input_type] mapping_override(mapping) || mapping else camelized = "#{input_type.to_s.camelize}Input" attempt_mapping_with_custom_namespace(camelized) || attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) || raise("No input found for #{input_type}") end end
def find_wrapper(input_type, options)
def find_wrapper(input_type, options) if name = options[:wrapper] || find_wrapper_mapping(input_type) name.respond_to?(:render) ? name : SimpleForm.wrapper(name) else wrapper end end
def find_wrapper_mapping(input_type)
1) It tries to find a wrapper for the current form
Attempts to find a wrapper mapping. It follows the following rules:
def find_wrapper_mapping(input_type) if options[:wrapper_mappings] && options[:wrapper_mappings][input_type] options[:wrapper_mappings][input_type] else SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type] end end
def full_error(attribute_name, options = {})
f.full_error :token #=> Token is invalid
== Examples
when errors for a hidden field need to be shown.
Return the error but also considering its name. This is used
def full_error(attribute_name, options = {}) options = options.dup options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name) object.class.human_attribute_name(attribute_name.to_s) else attribute_name.to_s.humanize end error(attribute_name, options) end
def hint(attribute_name, options = {})
f.hint "Don't forget to accept this"
f.hint :name, id: "cool_hint"
f.hint :name # Do I18n lookup
== Examples
as :hint_html.
an attribute for I18n lookup or a string. All the given options are sent
Creates a hint tag for the given attribute. Accepts a symbol indicating
def hint(attribute_name, options = {}) options = options.dup options[:hint_html] = options.except(:hint_tag, :hint) if attribute_name.is_a?(String) options[:hint] = attribute_name attribute_name, column, input_type = nil, nil, nil else column = find_attribute_column(attribute_name) input_type = default_input_type(attribute_name, column, options) end wrapper.find(:hint). render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options)) end
def initialize(*) #:nodoc:
def initialize(*) #:nodoc: super @object = convert_to_model(@object) @defaults = options[:defaults] @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper) end
def input(attribute_name, options = {}, &block)
given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
Some inputs, as :time_zone and :country accepts a :priority option. If none is
== Priority
value_method: the method to apply on the array collection to get the value
label_method: the method to apply on the array collection to get the label
collection: use to determine the collection to generate the radio or select
inputs), you have three extra options:
When playing with collections (:radio_buttons, :check_boxes and :select
== Collection
f.input :created_at, include_blank: true
prompt and/or include blank. Such options are given in plainly:
Some inputs, as datetime, time and select allow you to give extra options, like
== Options
input html, supply :input_html instead and so on.
the label html you just need to give a hash to :label_html. To configure the
Besides the html for any component can be changed. So, if you want to change
hint: false. The same works for :error, :label and :wrapper.
So, for instance, if you need to disable :hint for a given input, you can pass
The fact SimpleForm is built in components allow the interface to be unified.
by default.
required: defines whether this attribute is required or not. True
can use it to generate a text field for a date column.
as: allows you to define the input type you want, for instance you
You have some options for the input to enable/disable some functions:
heuristic to determine which is the best option.
Each database type will render a default input, based on some mappings and
can't be blank
My hint
name="user[name]" type="text" value="Carlos" />
* Super User Name!
def input(attribute_name, options = {}, &block) options = @defaults.deep_dup.deep_merge(options) if @defaults input = find_input(attribute_name, options, &block) wrapper = find_wrapper(input.input_type, options) wrapper.render input end
def input_field(attribute_name, options = {})
- when the input is invalid:
- when the input is valid:
the class configured according to the validation:
When the validation happens, the input will be rendered with
end
f.input_field :name
simple_form_for @user do |f|
end
config.input_field_error_class = 'is-invalid'
config.input_field_valid_class = 'is-valid'
SimpleForm.setup do |config|
# config/initializers/simple_form.rb
It also support validation classes once it is configured.
name="user[name]" type="text" value="Carlos" />
This is the output html (only the input portion, not the form):
end
f.input_field :name
simple_form_for @user do |f|
== Examples
are sent as :input_html.
Creates a input tag for the given attribute. All the given options
def input_field(attribute_name, options = {}) components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS) options = options.dup options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components) options = @defaults.deep_dup.deep_merge(options) if @defaults input = find_input(attribute_name, options) wrapper = find_wrapper(input.input_type, options) components = build_input_field_components(components.push(:input)) SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input end
def label(attribute_name, *args)
f.label :name, id: "cool_label"
f.label :name, required: false
f.label :name, label: "Name" # Same as above, but adds required tag
f.label :name, "Name" # Same behavior as Rails, do not add required tag
f.label :name # Do I18n lookup
== Examples
as :label_html.
through the :label option or using i18n. All the given options are sent
Creates a default label tag for the given attribute. You can give a label
def label(attribute_name, *args) return super if args.first.is_a?(String) || block_given? options = args.extract_options!.dup options[:label_html] = options.except(:label, :label_text, :required, :as) column = find_attribute_column(attribute_name) input_type = default_input_type(attribute_name, column, options) SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label end
def lookup_action #:nodoc:
The action to be used in lookup.
def lookup_action #:nodoc: @lookup_action ||= begin action = template.controller && template.controller.action_name return unless action action = action.to_s ACTIONS[action] || action end end
def lookup_model_names #:nodoc:
["route", "blocks", "blocks_learning_object", "foo"]
route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
Example:
explicit child indexes.
Extract the model names from the object_name mess, ignoring numeric and
def lookup_model_names #:nodoc: @lookup_model_names ||= begin child_index = options[:child_index] names = object_name.to_s.scan(/(?!\d)\w+/).flatten names.delete(child_index) if child_index names.each { |name| name.gsub!('_attributes', '') } names.freeze end end
def mapping_override(klass)
def mapping_override(klass) name = klass.name if name =~ /^SimpleForm::Inputs/ input_name = name.split("::").last attempt_mapping_with_custom_namespace(input_name) || attempt_mapping(input_name, Object) end end