formtastic 3.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitattributes +1 -0
- data/.github/workflows/test.yml +61 -0
- data/.gitignore +3 -2
- data/CHANGELOG.md +61 -0
- data/Gemfile.lock +140 -0
- data/MIT-LICENSE +1 -1
- data/{README.textile → README.md} +191 -168
- data/RELEASE_PROCESS +3 -1
- data/Rakefile +24 -8
- data/app/assets/stylesheets/formtastic.css +1 -1
- data/bin/appraisal +8 -0
- data/formtastic.gemspec +13 -17
- data/gemfiles/rails_6.0/Gemfile +5 -0
- data/gemfiles/rails_6.1/Gemfile +5 -0
- data/gemfiles/rails_7.0/Gemfile +5 -0
- data/gemfiles/rails_7.1/Gemfile +5 -0
- data/gemfiles/rails_edge/Gemfile +13 -0
- data/lib/formtastic/action_class_finder.rb +19 -0
- data/lib/formtastic/actions/base.rb +1 -0
- data/lib/formtastic/actions/button_action.rb +56 -53
- data/lib/formtastic/actions/buttonish.rb +1 -0
- data/lib/formtastic/actions/input_action.rb +60 -57
- data/lib/formtastic/actions/link_action.rb +69 -67
- data/lib/formtastic/actions.rb +7 -3
- data/lib/formtastic/deprecation.rb +6 -0
- data/lib/formtastic/engine.rb +4 -1
- data/lib/formtastic/form_builder.rb +32 -25
- data/lib/formtastic/helpers/action_helper.rb +22 -31
- data/lib/formtastic/helpers/actions_helper.rb +1 -0
- data/lib/formtastic/helpers/enum.rb +14 -0
- data/lib/formtastic/helpers/errors_helper.rb +3 -2
- data/lib/formtastic/helpers/fieldset_wrapper.rb +16 -11
- data/lib/formtastic/helpers/file_column_detection.rb +1 -0
- data/lib/formtastic/helpers/form_helper.rb +4 -3
- data/lib/formtastic/helpers/input_helper.rb +59 -80
- data/lib/formtastic/helpers/inputs_helper.rb +33 -27
- data/lib/formtastic/helpers/reflection.rb +5 -4
- data/lib/formtastic/helpers.rb +2 -2
- data/lib/formtastic/html_attributes.rb +13 -1
- data/lib/formtastic/i18n.rb +2 -1
- data/lib/formtastic/input_class_finder.rb +19 -0
- data/lib/formtastic/inputs/base/associations.rb +1 -0
- data/lib/formtastic/inputs/base/choices.rb +4 -3
- data/lib/formtastic/inputs/base/collections.rb +47 -11
- data/lib/formtastic/inputs/base/database.rb +8 -5
- data/lib/formtastic/inputs/base/datetime_pickerish.rb +1 -0
- data/lib/formtastic/inputs/base/errors.rb +7 -6
- data/lib/formtastic/inputs/base/fileish.rb +1 -0
- data/lib/formtastic/inputs/base/hints.rb +2 -1
- data/lib/formtastic/inputs/base/html.rb +12 -10
- data/lib/formtastic/inputs/base/labelling.rb +3 -2
- data/lib/formtastic/inputs/base/naming.rb +5 -4
- data/lib/formtastic/inputs/base/numeric.rb +1 -0
- data/lib/formtastic/inputs/base/options.rb +3 -3
- data/lib/formtastic/inputs/base/placeholder.rb +1 -0
- data/lib/formtastic/inputs/base/stringish.rb +1 -0
- data/lib/formtastic/inputs/base/timeish.rb +9 -4
- data/lib/formtastic/inputs/base/validations.rb +39 -12
- data/lib/formtastic/inputs/base/wrapping.rb +2 -3
- data/lib/formtastic/inputs/base.rb +17 -12
- data/lib/formtastic/inputs/boolean_input.rb +2 -1
- data/lib/formtastic/inputs/check_boxes_input.rb +16 -24
- data/lib/formtastic/inputs/color_input.rb +1 -1
- data/lib/formtastic/inputs/country_input.rb +4 -1
- data/lib/formtastic/inputs/datalist_input.rb +42 -0
- data/lib/formtastic/inputs/date_picker_input.rb +1 -0
- data/lib/formtastic/inputs/date_select_input.rb +1 -0
- data/lib/formtastic/inputs/datetime_picker_input.rb +1 -0
- data/lib/formtastic/inputs/datetime_select_input.rb +1 -0
- data/lib/formtastic/inputs/email_input.rb +1 -0
- data/lib/formtastic/inputs/file_input.rb +3 -2
- data/lib/formtastic/inputs/hidden_input.rb +3 -2
- data/lib/formtastic/inputs/number_input.rb +1 -0
- data/lib/formtastic/inputs/password_input.rb +1 -0
- data/lib/formtastic/inputs/phone_input.rb +1 -0
- data/lib/formtastic/inputs/radio_input.rb +26 -21
- data/lib/formtastic/inputs/range_input.rb +1 -0
- data/lib/formtastic/inputs/search_input.rb +1 -0
- data/lib/formtastic/inputs/select_input.rb +32 -10
- data/lib/formtastic/inputs/string_input.rb +1 -0
- data/lib/formtastic/inputs/text_input.rb +1 -0
- data/lib/formtastic/inputs/time_picker_input.rb +1 -0
- data/lib/formtastic/inputs/time_select_input.rb +1 -0
- data/lib/formtastic/inputs/time_zone_input.rb +17 -6
- data/lib/formtastic/inputs/url_input.rb +1 -0
- data/lib/formtastic/inputs.rb +33 -28
- data/lib/formtastic/localized_string.rb +2 -1
- data/lib/formtastic/localizer.rb +23 -24
- data/lib/formtastic/namespaced_class_finder.rb +98 -0
- data/lib/formtastic/version.rb +2 -1
- data/lib/formtastic.rb +19 -14
- data/lib/generators/formtastic/form/form_generator.rb +8 -2
- data/lib/generators/formtastic/input/input_generator.rb +47 -0
- data/lib/generators/formtastic/install/install_generator.rb +2 -0
- data/lib/generators/templates/formtastic.rb +29 -7
- data/lib/generators/templates/input.rb +19 -0
- data/sample/basic_inputs.html +1 -1
- data/script/integration-template.rb +73 -0
- data/script/integration.sh +19 -0
- data/spec/action_class_finder_spec.rb +13 -0
- data/spec/actions/button_action_spec.rb +21 -20
- data/spec/actions/generic_action_spec.rb +134 -133
- data/spec/actions/input_action_spec.rb +20 -19
- data/spec/actions/link_action_spec.rb +30 -29
- data/spec/builder/custom_builder_spec.rb +39 -22
- data/spec/builder/error_proc_spec.rb +6 -5
- data/spec/builder/semantic_fields_for_spec.rb +46 -45
- data/spec/fast_spec_helper.rb +13 -0
- data/spec/generators/formtastic/form/form_generator_spec.rb +33 -32
- data/spec/generators/formtastic/input/input_generator_spec.rb +125 -0
- data/spec/generators/formtastic/install/install_generator_spec.rb +10 -9
- data/spec/helpers/action_helper_spec.rb +70 -97
- data/spec/helpers/actions_helper_spec.rb +43 -42
- data/spec/helpers/form_helper_spec.rb +56 -39
- data/spec/helpers/input_helper_spec.rb +314 -255
- data/spec/helpers/inputs_helper_spec.rb +217 -202
- data/spec/helpers/reflection_helper_spec.rb +7 -6
- data/spec/helpers/semantic_errors_helper_spec.rb +26 -25
- data/spec/i18n_spec.rb +30 -29
- data/spec/input_class_finder_spec.rb +11 -0
- data/spec/inputs/base/collections_spec.rb +78 -0
- data/spec/inputs/base/validations_spec.rb +481 -0
- data/spec/inputs/boolean_input_spec.rb +73 -72
- data/spec/inputs/check_boxes_input_spec.rb +174 -123
- data/spec/inputs/color_input_spec.rb +53 -64
- data/spec/inputs/country_input_spec.rb +23 -22
- data/spec/inputs/custom_input_spec.rb +3 -6
- data/spec/inputs/datalist_input_spec.rb +62 -0
- data/spec/inputs/date_picker_input_spec.rb +114 -113
- data/spec/inputs/date_select_input_spec.rb +76 -61
- data/spec/inputs/datetime_picker_input_spec.rb +123 -122
- data/spec/inputs/datetime_select_input_spec.rb +85 -68
- data/spec/inputs/email_input_spec.rb +17 -16
- data/spec/inputs/file_input_spec.rb +18 -17
- data/spec/inputs/hidden_input_spec.rb +32 -31
- data/spec/inputs/include_blank_spec.rb +10 -9
- data/spec/inputs/label_spec.rb +36 -31
- data/spec/inputs/number_input_spec.rb +212 -211
- data/spec/inputs/password_input_spec.rb +17 -16
- data/spec/inputs/phone_input_spec.rb +17 -16
- data/spec/inputs/placeholder_spec.rb +18 -17
- data/spec/inputs/radio_input_spec.rb +92 -65
- data/spec/inputs/range_input_spec.rb +136 -135
- data/spec/inputs/readonly_spec.rb +51 -0
- data/spec/inputs/search_input_spec.rb +16 -15
- data/spec/inputs/select_input_spec.rb +209 -102
- data/spec/inputs/string_input_spec.rb +51 -50
- data/spec/inputs/text_input_spec.rb +34 -33
- data/spec/inputs/time_picker_input_spec.rb +115 -114
- data/spec/inputs/time_select_input_spec.rb +84 -70
- data/spec/inputs/time_zone_input_spec.rb +58 -31
- data/spec/inputs/url_input_spec.rb +17 -16
- data/spec/inputs/with_options_spec.rb +9 -8
- data/spec/localizer_spec.rb +18 -17
- data/spec/namespaced_class_finder_spec.rb +91 -0
- data/spec/schema.rb +22 -0
- data/spec/spec_helper.rb +180 -249
- data/spec/support/custom_macros.rb +128 -98
- data/spec/support/deprecation.rb +2 -1
- data/spec/support/shared_examples.rb +13 -0
- data/spec/support/specialized_class_finder_shared_example.rb +28 -0
- data/spec/support/test_environment.rb +25 -10
- metadata +95 -136
- data/.travis.yml +0 -28
- data/Appraisals +0 -25
- data/CHANGELOG +0 -27
- data/gemfiles/rails_3.2.gemfile +0 -7
- data/gemfiles/rails_4.0.4.gemfile +0 -7
- data/gemfiles/rails_4.1.gemfile +0 -7
- data/gemfiles/rails_4.gemfile +0 -7
- data/gemfiles/rails_edge.gemfile +0 -10
- data/lib/formtastic/util.rb +0 -53
- data/spec/support/deferred_garbage_collection.rb +0 -21
- data/spec/util_spec.rb +0 -52
@@ -1,9 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Formtastic
|
2
3
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
4
|
|
4
|
-
|
5
|
+
# Defines a new configurable option
|
6
|
+
# @param [Symbol] name the configuration name
|
7
|
+
# @param [Object] default the configuration default value
|
8
|
+
# @private
|
9
|
+
#
|
10
|
+
# @!macro [new] configure
|
11
|
+
# @!scope class
|
12
|
+
# @!attribute [rw] $1
|
13
|
+
# @api public
|
14
|
+
def self.configure(name, default = nil)
|
5
15
|
class_attribute(name)
|
6
|
-
self.send(:"#{name}=",
|
16
|
+
self.send(:"#{name}=", default)
|
7
17
|
end
|
8
18
|
|
9
19
|
configure :custom_namespace
|
@@ -12,7 +22,7 @@ module Formtastic
|
|
12
22
|
configure :default_text_area_width
|
13
23
|
configure :all_fields_required_by_default, true
|
14
24
|
configure :include_blank_for_select_by_default, true
|
15
|
-
configure :required_string, proc {
|
25
|
+
configure :required_string, proc { %{<abbr title="#{Formtastic::I18n.t(:required)}">*</abbr>}.html_safe }
|
16
26
|
configure :optional_string, ''
|
17
27
|
configure :inline_errors, :sentence
|
18
28
|
configure :label_str_method, :humanize
|
@@ -31,6 +41,15 @@ module Formtastic
|
|
31
41
|
configure :default_hint_class, 'inline-hints'
|
32
42
|
configure :use_required_attribute, false
|
33
43
|
configure :perform_browser_validations, false
|
44
|
+
# Check {Formtastic::InputClassFinder} to see how are inputs resolved.
|
45
|
+
configure :input_namespaces, [::Object, ::Formtastic::Inputs]
|
46
|
+
configure :input_class_finder, Formtastic::InputClassFinder
|
47
|
+
# Check {Formtastic::ActionClassFinder} to see how are inputs resolved.
|
48
|
+
configure :action_namespaces, [::Object, ::Formtastic::Actions]
|
49
|
+
configure :action_class_finder, Formtastic::ActionClassFinder
|
50
|
+
|
51
|
+
configure :skipped_columns, [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
|
52
|
+
configure :priority_time_zones, []
|
34
53
|
|
35
54
|
attr_reader :template
|
36
55
|
|
@@ -43,11 +62,11 @@ module Formtastic
|
|
43
62
|
include Formtastic::Helpers::ActionHelper
|
44
63
|
include Formtastic::Helpers::ActionsHelper
|
45
64
|
include Formtastic::Helpers::ErrorsHelper
|
46
|
-
|
47
|
-
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
65
|
+
|
66
|
+
# This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
|
48
67
|
# provided to ensure that the `:builder` from `semantic_form_for` was passed down into
|
49
|
-
# the nested `fields_for`. Rails
|
50
|
-
# provided purely for backwards compatibility and DSL consistency.
|
68
|
+
# the nested `fields_for`. Our supported versions of Rails no longer require us to do this,
|
69
|
+
# so this method is provided purely for backwards compatibility and DSL consistency.
|
51
70
|
#
|
52
71
|
# When constructing a `fields_for` form fragment *outside* of `semantic_form_for`, please use
|
53
72
|
# `Formtastic::Helpers::FormHelper#semantic_fields_for`.
|
@@ -73,29 +92,17 @@ module Formtastic
|
|
73
92
|
#
|
74
93
|
# @todo is there a way to test the params structure of the Rails helper we wrap to ensure forward compatibility?
|
75
94
|
def semantic_fields_for(record_or_name_or_array, *args, &block)
|
76
|
-
|
77
|
-
options = args.extract_options!
|
78
|
-
options[:parent_builder] ||= self
|
79
|
-
|
80
|
-
# Wrap the Rails helper
|
81
|
-
fields_for(record_or_name_or_array, *(args << options), &block)
|
95
|
+
fields_for(record_or_name_or_array, *args, &block)
|
82
96
|
end
|
83
|
-
|
84
|
-
def initialize(object_name, object, template, options
|
85
|
-
|
86
|
-
|
87
|
-
# rails 4.1 removes the parameter completely
|
88
|
-
if Util.rails3? || Util.rails4_0?
|
89
|
-
super
|
90
|
-
else # Must be rails4_1 or greater
|
91
|
-
super object_name, object, template, options
|
92
|
-
end
|
93
|
-
|
97
|
+
|
98
|
+
def initialize(object_name, object, template, options)
|
99
|
+
super
|
100
|
+
|
94
101
|
if respond_to?('multipart=') && options.is_a?(Hash) && options[:html]
|
95
102
|
self.multipart = options[:html][:multipart]
|
96
103
|
end
|
97
104
|
end
|
98
|
-
|
105
|
+
|
99
106
|
end
|
100
107
|
|
101
108
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
module Formtastic
|
3
4
|
module Helpers
|
4
5
|
module ActionHelper
|
5
|
-
|
6
6
|
# Renders an action for the form (such as a subit/reset button, or a cancel link).
|
7
7
|
#
|
8
8
|
# Each action is wrapped in an `<li class="action">` tag with other classes added based on the
|
@@ -23,9 +23,9 @@ module Formtastic
|
|
23
23
|
# reset: "Reset form"
|
24
24
|
# submit: "Submit"
|
25
25
|
#
|
26
|
-
# For forms with an object present, the `update` key will be used if calling `persisted?` on
|
27
|
-
# the object returns true (saving changes to a record), otherwise the `create`
|
28
|
-
# used. The `submit` key is used as a fallback when there is no object or we cannot determine
|
26
|
+
# For forms with an object present, the `update` key will be used if calling `persisted?` on
|
27
|
+
# the object returns true (saving changes to a record), otherwise the `create` key will be
|
28
|
+
# used. The `submit` key is used as a fallback when there is no object or we cannot determine
|
29
29
|
# if `create` or `update` is appropriate.
|
30
30
|
#
|
31
31
|
# @example Basic usage
|
@@ -80,46 +80,37 @@ module Formtastic
|
|
80
80
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
81
81
|
options[:as] ||= default_action_type(method, options)
|
82
82
|
|
83
|
-
klass =
|
83
|
+
klass = namespaced_action_class(options[:as])
|
84
84
|
|
85
85
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
86
86
|
end
|
87
87
|
|
88
88
|
protected
|
89
89
|
|
90
|
-
def default_action_type(method, options = {})
|
90
|
+
def default_action_type(method, options = {}) # @private
|
91
91
|
case method
|
92
92
|
when :submit then :input
|
93
|
-
when :reset
|
93
|
+
when :reset then :input
|
94
94
|
when :cancel then :link
|
95
|
+
else method
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
# :as => :button # => ButtonAction
|
114
|
-
def custom_action_class_name(as)
|
115
|
-
"#{as.to_s.camelize}Action"
|
116
|
-
end
|
117
|
-
|
118
|
-
# :as => :button # => Formtastic::Actions::ButtonAction
|
119
|
-
def standard_action_class_name(as)
|
120
|
-
"Formtastic::Actions::#{as.to_s.camelize}Action"
|
99
|
+
# Takes the `:as` option and attempts to return the corresponding action
|
100
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
101
|
+
# top level `AwesomeAction` class (to allow the application to subclass
|
102
|
+
# and modify to suit), falling back to `Formtastic::Actions::AwesomeAction`.
|
103
|
+
#
|
104
|
+
# Custom action namespaces to look into can be configured via the
|
105
|
+
# {Formtastic::FormBuilder.action_namespaces} configuration setting.
|
106
|
+
# @see Helpers::InputHelper#namespaced_input_class
|
107
|
+
# @see Formtastic::ActionClassFinder
|
108
|
+
def namespaced_action_class(as)
|
109
|
+
@action_class_finder ||= action_class_finder.new(self)
|
110
|
+
@action_class_finder.find(as)
|
111
|
+
rescue Formtastic::ActionClassFinder::NotFoundError => e
|
112
|
+
raise Formtastic::UnknownActionError, "Unable to find action #{e.message}"
|
121
113
|
end
|
122
|
-
|
123
114
|
end
|
124
115
|
end
|
125
116
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Formtastic
|
3
|
+
module Helpers
|
4
|
+
# @private
|
5
|
+
module Enum
|
6
|
+
# Returns the enum (if defined) for the given method
|
7
|
+
def enum_for(method) # @private
|
8
|
+
if @object.respond_to?(:defined_enums)
|
9
|
+
@object.defined_enums[method.to_s]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Formtastic
|
2
3
|
module Helpers
|
3
4
|
module ErrorsHelper
|
@@ -52,7 +53,7 @@ module Formtastic
|
|
52
53
|
return nil if full_errors.blank?
|
53
54
|
html_options[:class] ||= "errors"
|
54
55
|
template.content_tag(:ul, html_options) do
|
55
|
-
|
56
|
+
full_errors.map { |error| template.content_tag(:li, error) }.join.html_safe
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -78,4 +79,4 @@ module Formtastic
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
end
|
81
|
-
end
|
82
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Formtastic
|
2
3
|
module Helpers
|
3
4
|
# @private
|
@@ -21,8 +22,8 @@ module Formtastic
|
|
21
22
|
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
|
22
23
|
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
|
23
24
|
# f.inputs :title, :body, :author # First argument is a column => (no legend)
|
24
|
-
def field_set_and_list_wrapping(*args, &block)
|
25
|
-
contents = args.
|
25
|
+
def field_set_and_list_wrapping(*args, &block) # @private
|
26
|
+
contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
|
26
27
|
html_options = args.extract_options!
|
27
28
|
|
28
29
|
if block_given?
|
@@ -33,12 +34,15 @@ module Formtastic
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
37
|
+
# Work-around for empty contents block
|
38
|
+
contents ||= ""
|
39
|
+
|
36
40
|
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
37
41
|
contents = contents.join if contents.respond_to?(:join)
|
38
42
|
|
39
43
|
legend = field_set_legend(html_options)
|
40
44
|
fieldset = template.content_tag(:fieldset,
|
41
|
-
|
45
|
+
legend.html_safe << template.content_tag(:ol, contents.html_safe),
|
42
46
|
html_options.except(:builder, :parent, :name)
|
43
47
|
)
|
44
48
|
|
@@ -47,30 +51,31 @@ module Formtastic
|
|
47
51
|
|
48
52
|
def field_set_legend(html_options)
|
49
53
|
legend = (html_options[:name] || '').to_s
|
50
|
-
|
51
|
-
legend
|
54
|
+
# only applying if String includes '%i' avoids argument error when $DEBUG is true
|
55
|
+
legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i')
|
56
|
+
legend = template.content_tag(:legend, template.content_tag(:span, legend.html_safe)) unless legend.blank?
|
52
57
|
legend
|
53
58
|
end
|
54
59
|
|
55
60
|
# Gets the nested_child_index value from the parent builder. It returns a hash with each
|
56
61
|
# association that the parent builds.
|
57
|
-
def parent_child_index(parent)
|
62
|
+
def parent_child_index(parent) # @private
|
58
63
|
# Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
|
59
64
|
duck = parent[:builder].instance_variable_get('@nested_child_index')
|
60
|
-
|
65
|
+
|
61
66
|
# Could be symbol for the association, or a model (or an array of either, I think? TODO)
|
62
67
|
child = parent[:for]
|
63
68
|
# Pull a sybol or model out of Array (TODO: check if there's an Array)
|
64
69
|
child = child.first if child.respond_to?(:first)
|
65
70
|
# If it's an object, get a symbol from the class name
|
66
71
|
child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
|
67
|
-
|
72
|
+
|
68
73
|
key = "#{parent[:builder].object_name}[#{child}_attributes]"
|
69
74
|
|
70
|
-
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
75
|
+
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
|
71
76
|
# in the legend, so if we have a number, return it with a +1 until we can verify this scenario.
|
72
|
-
return duck + 1 if duck.is_a?(
|
73
|
-
|
77
|
+
return duck + 1 if duck.is_a?(Integer)
|
78
|
+
|
74
79
|
# First try to extract key from duck Hash, then try child
|
75
80
|
(duck[key] || duck[child]).to_i + 1
|
76
81
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Formtastic
|
2
3
|
module Helpers
|
3
4
|
|
@@ -155,7 +156,7 @@ module Formtastic
|
|
155
156
|
options[:builder] ||= @@builder
|
156
157
|
options[:html] ||= {}
|
157
158
|
options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
|
158
|
-
|
159
|
+
options[:custom_namespace] = options.delete(:namespace)
|
159
160
|
|
160
161
|
singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)
|
161
162
|
|
@@ -163,7 +164,7 @@ module Formtastic
|
|
163
164
|
class_names << @@default_form_class
|
164
165
|
model_class_name = case record_or_name_or_array
|
165
166
|
when String, Symbol then record_or_name_or_array.to_s # :post => "post"
|
166
|
-
when Array then options[:as] || singularizer.call(record_or_name_or_array.
|
167
|
+
when Array then options[:as] || singularizer.call(record_or_name_or_array[-1].class) # [@post, @comment] # => "comment"
|
167
168
|
else options[:as] || singularizer.call(record_or_name_or_array.class) # @post => "post"
|
168
169
|
end
|
169
170
|
class_names << @@default_form_model_class_proc.call(model_class_name)
|
@@ -182,7 +183,7 @@ module Formtastic
|
|
182
183
|
def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
|
183
184
|
options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
|
184
185
|
options[:builder] ||= @@builder
|
185
|
-
|
186
|
+
options[:custom_namespace] = options.delete(:namespace)
|
186
187
|
|
187
188
|
with_custom_field_error_proc do
|
188
189
|
self.fields_for(record_name, record_object, options, &block)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
module Formtastic
|
3
4
|
module Helpers
|
4
5
|
|
@@ -37,6 +38,7 @@ module Formtastic
|
|
37
38
|
# @see Formtastic::Helpers::FormHelper#semantic_form_for
|
38
39
|
module InputHelper
|
39
40
|
include Formtastic::Helpers::Reflection
|
41
|
+
include Formtastic::Helpers::Enum
|
40
42
|
include Formtastic::Helpers::FileColumnDetection
|
41
43
|
|
42
44
|
# Returns a chunk of HTML markup for a given `method` on the form object, wrapped in
|
@@ -86,26 +88,26 @@ module Formtastic
|
|
86
88
|
#
|
87
89
|
# Available input styles:
|
88
90
|
#
|
89
|
-
# * `:boolean`
|
90
|
-
# * `:check_boxes`
|
91
|
-
# * `:color`
|
92
|
-
# * `:country`
|
93
|
-
# * `:datetime_select`
|
94
|
-
# * `:date_select`
|
95
|
-
# * `:email`
|
96
|
-
# * `:file`
|
97
|
-
# * `:hidden`
|
98
|
-
# * `:number`
|
99
|
-
# * `:password`
|
100
|
-
# * `:phone`
|
101
|
-
# * `:radio`
|
102
|
-
# * `:search`
|
103
|
-
# * `:select`
|
104
|
-
# * `:string`
|
105
|
-
# * `:text`
|
106
|
-
# * `:time_zone`
|
107
|
-
# * `:time_select`
|
108
|
-
# * `:url`
|
91
|
+
# * `:boolean` (see {Inputs::BooleanInput})
|
92
|
+
# * `:check_boxes` (see {Inputs::CheckBoxesInput})
|
93
|
+
# * `:color` (see {Inputs::ColorInput})
|
94
|
+
# * `:country` (see {Inputs::CountryInput})
|
95
|
+
# * `:datetime_select` (see {Inputs::DatetimeSelectInput})
|
96
|
+
# * `:date_select` (see {Inputs::DateSelectInput})
|
97
|
+
# * `:email` (see {Inputs::EmailInput})
|
98
|
+
# * `:file` (see {Inputs::FileInput})
|
99
|
+
# * `:hidden` (see {Inputs::HiddenInput})
|
100
|
+
# * `:number` (see {Inputs::NumberInput})
|
101
|
+
# * `:password` (see {Inputs::PasswordInput})
|
102
|
+
# * `:phone` (see {Inputs::PhoneInput})
|
103
|
+
# * `:radio` (see {Inputs::RadioInput})
|
104
|
+
# * `:search` (see {Inputs::SearchInput})
|
105
|
+
# * `:select` (see {Inputs::SelectInput})
|
106
|
+
# * `:string` (see {Inputs::StringInput})
|
107
|
+
# * `:text` (see {Inputs::TextInput})
|
108
|
+
# * `:time_zone` (see {Inputs::TimeZoneInput})
|
109
|
+
# * `:time_select` (see {Inputs::TimeSelectInput})
|
110
|
+
# * `:url` (see {Inputs::UrlInput})
|
109
111
|
#
|
110
112
|
# Calling `:as => :string` (for example) will call `#to_html` on a new instance of
|
111
113
|
# `Formtastic::Inputs::StringInput`. Before this, Formtastic will try to instantiate a top-level
|
@@ -131,6 +133,7 @@ module Formtastic
|
|
131
133
|
#
|
132
134
|
# @option options :input_html [Hash]
|
133
135
|
# Override or add to the HTML attributes to be passed down to the `<input>` tag
|
136
|
+
# (If you use attr_readonly method in your model, formtastic will automatically set those attributes's input readonly)
|
134
137
|
#
|
135
138
|
# @option options :wrapper_html [Hash]
|
136
139
|
# Override or add to the HTML attributes to be passed down to the wrapping `<li>` tag
|
@@ -138,12 +141,6 @@ module Formtastic
|
|
138
141
|
# @option options :collection [Array<ActiveModel, String, Symbol>, Hash{String => String, Boolean}, OrderedHash{String => String, Boolean}]
|
139
142
|
# Override collection of objects in the association (`:select`, `:radio` & `:check_boxes` inputs only)
|
140
143
|
#
|
141
|
-
# @option options :member_label [Symbol, Proc, Method]
|
142
|
-
# Override the method called on each object in the `:collection` for use as the `<label>` content (`:check_boxes` & `:radio` inputs) or `<option>` content (`:select` inputs)
|
143
|
-
#
|
144
|
-
# @option options :member_value [Symbol, Proc, Method]
|
145
|
-
# Override the method called on each object in the `:collection` for use as the `value` attribute in the `<input>` (`:check_boxes` & `:radio` inputs) or `<option>` (`:select` inputs)
|
146
|
-
#
|
147
144
|
# @option options :multiple [Boolean]
|
148
145
|
# Specify if the `:select` input should allow multiple selections or not (defaults to `belongs_to` associations, and `true` for `has_many` and `has_and_belongs_to_many` associations)
|
149
146
|
#
|
@@ -230,14 +227,15 @@ module Formtastic
|
|
230
227
|
# first_name: "Joe"
|
231
228
|
# last_name: "Smith"
|
232
229
|
#
|
230
|
+
# @see #namespaced_input_class
|
233
231
|
# @todo Many many more examples. Some of the detail probably needs to be pushed out to the relevant methods too.
|
234
232
|
# @todo More i18n examples.
|
235
233
|
def input(method, options = {})
|
236
|
-
method = method.to_sym
|
234
|
+
method = method.to_sym
|
237
235
|
options = options.dup # Allow options to be shared without being tainted by Formtastic
|
238
236
|
options[:as] ||= default_input_type(method, options)
|
239
237
|
|
240
|
-
klass =
|
238
|
+
klass = namespaced_input_class(options[:as])
|
241
239
|
|
242
240
|
klass.new(self, template, @object, @object_name, method, options).to_html
|
243
241
|
end
|
@@ -254,14 +252,15 @@ module Formtastic
|
|
254
252
|
#
|
255
253
|
# If there is no column for the method (eg "virtual columns" with an attr_accessor), the
|
256
254
|
# default is a :string, a similar behaviour to Rails' scaffolding.
|
257
|
-
def default_input_type(method, options = {})
|
255
|
+
def default_input_type(method, options = {}) # @private
|
258
256
|
if @object
|
259
257
|
return :select if reflection_for(method)
|
260
258
|
|
261
259
|
return :file if is_file?(method, options)
|
262
260
|
end
|
263
261
|
|
264
|
-
|
262
|
+
column = column_for(method)
|
263
|
+
if column && column.type
|
265
264
|
# Special cases where the column type doesn't map to an input method.
|
266
265
|
case column.type
|
267
266
|
when :string
|
@@ -275,6 +274,7 @@ module Formtastic
|
|
275
274
|
return :color if method.to_s =~ /color/
|
276
275
|
when :integer
|
277
276
|
return :select if reflection_for(method)
|
277
|
+
return :select if enum_for(method)
|
278
278
|
return :number
|
279
279
|
when :float, :decimal
|
280
280
|
return :number
|
@@ -284,6 +284,10 @@ module Formtastic
|
|
284
284
|
return :time_select
|
285
285
|
when :date
|
286
286
|
return :date_select
|
287
|
+
when :hstore, :json, :jsonb
|
288
|
+
return :text
|
289
|
+
when :citext, :inet
|
290
|
+
return :string
|
287
291
|
end
|
288
292
|
|
289
293
|
# Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
|
@@ -298,16 +302,27 @@ module Formtastic
|
|
298
302
|
end
|
299
303
|
|
300
304
|
# Get a column object for a specified attribute method - if possible.
|
301
|
-
|
302
|
-
|
305
|
+
# @return [ActiveModel::Type::Value, #type] in case of rails 5 attributes api
|
306
|
+
# @return [ActiveRecord::ConnectionAdapters::Column] in case of rails 4
|
307
|
+
def column_for(method) # @private
|
308
|
+
case
|
309
|
+
when @object.class.respond_to?(:type_for_attribute)
|
310
|
+
@object.class.type_for_attribute(method.to_s)
|
311
|
+
when @object.class.respond_to?(:column_for_attribute)
|
312
|
+
@object.class.column_for_attribute(method)
|
313
|
+
when @object.respond_to?(:column_for_attribute)
|
314
|
+
@object.column_for_attribute(method)
|
315
|
+
else nil
|
316
|
+
end
|
303
317
|
end
|
304
318
|
|
305
|
-
# Takes the `:as` option and attempts to return the corresponding input
|
306
|
-
# `:as => :
|
307
|
-
#
|
319
|
+
# Takes the `:as` option and attempts to return the corresponding input
|
320
|
+
# class. In the case of `:as => :awesome` it will first attempt to find a
|
321
|
+
# top level `AwesomeInput` class (to allow the application to subclass
|
322
|
+
# and modify to suit), falling back to `Formtastic::Inputs::AwesomeInput`.
|
308
323
|
#
|
309
|
-
#
|
310
|
-
#
|
324
|
+
# Custom input namespaces to look into can be configured via the
|
325
|
+
# {Formtastic::FormBuilder.input_namespaces} configuration setting.
|
311
326
|
#
|
312
327
|
# @param [Symbol] as A symbol representing the type of input to render
|
313
328
|
# @raise [Formtastic::UnknownInputError] An appropriate input class could not be found
|
@@ -319,50 +334,14 @@ module Formtastic
|
|
319
334
|
#
|
320
335
|
# @example When a top-level class is found
|
321
336
|
# input_class(:string) #=> StringInput
|
322
|
-
# input_class(:awesome) #=> AwesomeInput
|
323
|
-
|
324
|
-
|
325
|
-
@
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
# prevent exceptions in production environment for better performance
|
333
|
-
def input_class_with_const_defined(as)
|
334
|
-
input_class_name = custom_input_class_name(as)
|
335
|
-
|
336
|
-
if ::Object.const_defined?(input_class_name)
|
337
|
-
input_class_name.constantize
|
338
|
-
elsif Formtastic::Inputs.const_defined?(input_class_name)
|
339
|
-
standard_input_class_name(as).constantize
|
340
|
-
else
|
341
|
-
raise Formtastic::UnknownInputError, "Unable to find input class #{input_class_name}"
|
342
|
-
end
|
337
|
+
# input_class(:awesome) #=> AwesomeInput
|
338
|
+
# @see NamespacedClassFinder#find
|
339
|
+
def namespaced_input_class(as)
|
340
|
+
@input_class_finder ||= input_class_finder.new(self)
|
341
|
+
@input_class_finder.find(as)
|
342
|
+
rescue Formtastic::InputClassFinder::NotFoundError
|
343
|
+
raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}"
|
343
344
|
end
|
344
|
-
|
345
|
-
# use auto-loading in development environment
|
346
|
-
def input_class_by_trying(as)
|
347
|
-
begin
|
348
|
-
custom_input_class_name(as).constantize
|
349
|
-
rescue NameError
|
350
|
-
standard_input_class_name(as).constantize
|
351
|
-
end
|
352
|
-
rescue NameError
|
353
|
-
raise Formtastic::UnknownInputError, "Unable to find input class for #{as}"
|
354
|
-
end
|
355
|
-
|
356
|
-
# :as => :string # => StringInput
|
357
|
-
def custom_input_class_name(as)
|
358
|
-
"#{as.to_s.camelize}Input"
|
359
|
-
end
|
360
|
-
|
361
|
-
# :as => :string # => Formtastic::Inputs::StringInput
|
362
|
-
def standard_input_class_name(as)
|
363
|
-
"Formtastic::Inputs::#{as.to_s.camelize}Input"
|
364
|
-
end
|
365
|
-
|
366
345
|
end
|
367
346
|
end
|
368
347
|
end
|