simple_form 3.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +148 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +278 -68
  5. data/lib/generators/simple_form/install_generator.rb +1 -0
  6. data/lib/generators/simple_form/templates/README +3 -3
  7. data/lib/generators/simple_form/templates/_form.html.erb +2 -0
  8. data/lib/generators/simple_form/templates/_form.html.haml +2 -0
  9. data/lib/generators/simple_form/templates/_form.html.slim +1 -0
  10. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +19 -9
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +367 -63
  12. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +23 -8
  13. data/lib/simple_form/action_view_extensions/builder.rb +1 -0
  14. data/lib/simple_form/action_view_extensions/form_helper.rb +4 -1
  15. data/lib/simple_form/components/errors.rb +15 -2
  16. data/lib/simple_form/components/hints.rb +1 -0
  17. data/lib/simple_form/components/html5.rb +15 -4
  18. data/lib/simple_form/components/label_input.rb +2 -1
  19. data/lib/simple_form/components/labels.rb +12 -5
  20. data/lib/simple_form/components/maxlength.rb +8 -4
  21. data/lib/simple_form/components/min_max.rb +1 -0
  22. data/lib/simple_form/components/minlength.rb +38 -0
  23. data/lib/simple_form/components/pattern.rb +1 -0
  24. data/lib/simple_form/components/placeholders.rb +2 -1
  25. data/lib/simple_form/components/readonly.rb +1 -0
  26. data/lib/simple_form/components.rb +2 -0
  27. data/lib/simple_form/error_notification.rb +1 -0
  28. data/lib/simple_form/form_builder.rb +117 -35
  29. data/lib/simple_form/helpers/autofocus.rb +1 -0
  30. data/lib/simple_form/helpers/disabled.rb +1 -0
  31. data/lib/simple_form/helpers/readonly.rb +1 -0
  32. data/lib/simple_form/helpers/required.rb +1 -0
  33. data/lib/simple_form/helpers/validators.rb +2 -1
  34. data/lib/simple_form/helpers.rb +1 -0
  35. data/lib/simple_form/i18n_cache.rb +1 -0
  36. data/lib/simple_form/inputs/base.rb +36 -12
  37. data/lib/simple_form/inputs/block_input.rb +1 -0
  38. data/lib/simple_form/inputs/boolean_input.rb +14 -3
  39. data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
  40. data/lib/simple_form/inputs/collection_input.rb +7 -5
  41. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +3 -2
  42. data/lib/simple_form/inputs/collection_select_input.rb +1 -0
  43. data/lib/simple_form/inputs/color_input.rb +14 -0
  44. data/lib/simple_form/inputs/date_time_input.rb +13 -8
  45. data/lib/simple_form/inputs/file_input.rb +1 -0
  46. data/lib/simple_form/inputs/grouped_collection_select_input.rb +1 -0
  47. data/lib/simple_form/inputs/hidden_input.rb +1 -0
  48. data/lib/simple_form/inputs/numeric_input.rb +1 -0
  49. data/lib/simple_form/inputs/password_input.rb +2 -1
  50. data/lib/simple_form/inputs/priority_input.rb +1 -4
  51. data/lib/simple_form/inputs/range_input.rb +1 -0
  52. data/lib/simple_form/inputs/string_input.rb +3 -2
  53. data/lib/simple_form/inputs/text_input.rb +2 -1
  54. data/lib/simple_form/inputs.rb +2 -0
  55. data/lib/simple_form/map_type.rb +1 -0
  56. data/lib/simple_form/railtie.rb +1 -0
  57. data/lib/simple_form/tags.rb +7 -2
  58. data/lib/simple_form/version.rb +2 -1
  59. data/lib/simple_form/wrappers/builder.rb +1 -0
  60. data/lib/simple_form/wrappers/leaf.rb +2 -1
  61. data/lib/simple_form/wrappers/many.rb +1 -0
  62. data/lib/simple_form/wrappers/root.rb +2 -0
  63. data/lib/simple_form/wrappers/single.rb +2 -1
  64. data/lib/simple_form/wrappers.rb +1 -0
  65. data/lib/simple_form.rb +79 -14
  66. data/test/action_view_extensions/builder_test.rb +28 -9
  67. data/test/action_view_extensions/form_helper_test.rb +3 -2
  68. data/test/components/custom_components_test.rb +62 -0
  69. data/test/components/label_test.rb +33 -4
  70. data/test/form_builder/association_test.rb +33 -2
  71. data/test/form_builder/button_test.rb +1 -0
  72. data/test/form_builder/error_notification_test.rb +1 -0
  73. data/test/form_builder/error_test.rb +44 -9
  74. data/test/form_builder/general_test.rb +92 -20
  75. data/test/form_builder/hint_test.rb +6 -0
  76. data/test/form_builder/input_field_test.rb +76 -70
  77. data/test/form_builder/label_test.rb +27 -4
  78. data/test/form_builder/wrapper_test.rb +66 -14
  79. data/test/generators/simple_form_generator_test.rb +4 -3
  80. data/test/inputs/boolean_input_test.rb +35 -0
  81. data/test/inputs/collection_check_boxes_input_test.rb +38 -14
  82. data/test/inputs/collection_radio_buttons_input_test.rb +48 -24
  83. data/test/inputs/collection_select_input_test.rb +40 -39
  84. data/test/inputs/color_input_test.rb +10 -0
  85. data/test/inputs/datetime_input_test.rb +12 -8
  86. data/test/inputs/disabled_test.rb +14 -0
  87. data/test/inputs/discovery_test.rb +23 -0
  88. data/test/inputs/file_input_test.rb +1 -0
  89. data/test/inputs/general_test.rb +3 -2
  90. data/test/inputs/grouped_collection_select_input_test.rb +11 -10
  91. data/test/inputs/hidden_input_test.rb +1 -0
  92. data/test/inputs/numeric_input_test.rb +5 -1
  93. data/test/inputs/priority_input_test.rb +7 -6
  94. data/test/inputs/readonly_test.rb +1 -0
  95. data/test/inputs/required_test.rb +45 -0
  96. data/test/inputs/string_input_test.rb +18 -16
  97. data/test/inputs/text_input_test.rb +13 -0
  98. data/test/simple_form_test.rb +1 -0
  99. data/test/support/discovery_inputs.rb +8 -0
  100. data/test/support/misc_helpers.rb +44 -2
  101. data/test/support/mock_controller.rb +7 -1
  102. data/test/support/models.rb +105 -22
  103. data/test/test_helper.rb +14 -3
  104. metadata +42 -36
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module Errors
@@ -10,7 +11,15 @@ module SimpleForm
10
11
  end
11
12
 
12
13
  def has_errors?
13
- object && object.respond_to?(:errors) && errors.present?
14
+ object_with_errors? || object.nil? && has_custom_error?
15
+ end
16
+
17
+ def has_value?
18
+ object && object.respond_to?(attribute_name) && object.send(attribute_name).present?
19
+ end
20
+
21
+ def valid?
22
+ !has_errors? && has_value?
14
23
  end
15
24
 
16
25
  protected
@@ -25,6 +34,10 @@ module SimpleForm
25
34
  has_custom_error? ? options[:error] : full_errors.send(error_method)
26
35
  end
27
36
 
37
+ def object_with_errors?
38
+ object && object.respond_to?(:errors) && errors.present?
39
+ end
40
+
28
41
  def error_method
29
42
  options[:error_method] || SimpleForm.error_method
30
43
  end
@@ -38,7 +51,7 @@ module SimpleForm
38
51
  end
39
52
 
40
53
  def errors_on_attribute
41
- object.errors[attribute_name]
54
+ object.errors[attribute_name] || []
42
55
  end
43
56
 
44
57
  def full_errors_on_attribute
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module HTML5
@@ -7,10 +8,12 @@ module SimpleForm
7
8
 
8
9
  def html5(wrapper_options = nil)
9
10
  @html5 = true
10
- if has_required?
11
- input_html_options[:required] = true
12
- input_html_options[:'aria-required'] = true
13
- end
11
+
12
+ input_html_options[:required] = input_html_required_option
13
+ input_html_options[:'aria-required'] = input_html_aria_required_option
14
+
15
+ input_html_options[:'aria-invalid'] = has_errors? || nil
16
+
14
17
  nil
15
18
  end
16
19
 
@@ -18,6 +21,14 @@ module SimpleForm
18
21
  @html5
19
22
  end
20
23
 
24
+ def input_html_required_option
25
+ !options[:required].nil? ? required_field? : has_required?
26
+ end
27
+
28
+ def input_html_aria_required_option
29
+ !options[:required].nil? ? (required_field? || nil) : (has_required? || nil)
30
+ end
31
+
21
32
  def has_required?
22
33
  # We need to check browser_validations because
23
34
  # some browsers are still checking required even
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module LabelInput
@@ -20,7 +21,7 @@ module SimpleForm
20
21
  def deprecated_component(namespace, wrapper_options)
21
22
  method = method(namespace)
22
23
 
23
- if method.arity == 0
24
+ if method.arity.zero?
24
25
  ActiveSupport::Deprecation.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: namespace })
25
26
 
26
27
  method.call
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module Labels
@@ -6,18 +7,24 @@ module SimpleForm
6
7
  module ClassMethods #:nodoc:
7
8
  def translate_required_html
8
9
  i18n_cache :translate_required_html do
9
- I18n.t(:"simple_form.required.html", default:
10
- %[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>]
10
+ I18n.t(:"required.html", scope: i18n_scope, default:
11
+ %(<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr>)
11
12
  )
12
13
  end
13
14
  end
14
15
 
15
16
  def translate_required_text
16
- I18n.t(:"simple_form.required.text", default: 'required')
17
+ I18n.t(:"required.text", scope: i18n_scope, default: 'required')
17
18
  end
18
19
 
19
20
  def translate_required_mark
20
- I18n.t(:"simple_form.required.mark", default: '*')
21
+ I18n.t(:"required.mark", scope: i18n_scope, default: '*')
22
+ end
23
+
24
+ private
25
+
26
+ def i18n_scope
27
+ SimpleForm.i18n_scope
21
28
  end
22
29
  end
23
30
 
@@ -42,7 +49,7 @@ module SimpleForm
42
49
 
43
50
  def label_html_options
44
51
  label_html_classes = SimpleForm.additional_classes_for(:label) {
45
- [input_type, required_class, SimpleForm.label_class].compact
52
+ [input_type, required_class, disabled_class, SimpleForm.label_class].compact
46
53
  }
47
54
 
48
55
  label_options = html_options_for(:label, label_html_classes)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -15,10 +16,7 @@ module SimpleForm
15
16
  maxlength
16
17
  else
17
18
  length_validator = find_length_validator
18
-
19
- if length_validator && !has_tokenizer?(length_validator)
20
- length_validator.options[:is] || length_validator.options[:maximum]
21
- end
19
+ maximum_length_value_from(length_validator)
22
20
  end
23
21
  end
24
22
 
@@ -29,6 +27,12 @@ module SimpleForm
29
27
  def has_tokenizer?(length_validator)
30
28
  length_validator.options[:tokenizer]
31
29
  end
30
+
31
+ def maximum_length_value_from(length_validator)
32
+ if length_validator
33
+ length_validator.options[:is] || length_validator.options[:maximum]
34
+ end
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  module MinMax
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module SimpleForm
3
+ module Components
4
+ # Needs to be enabled in order to do automatic lookups.
5
+ module Minlength
6
+ def minlength(wrapper_options = nil)
7
+ input_html_options[:minlength] ||= minimum_length_from_validation
8
+ nil
9
+ end
10
+
11
+ private
12
+
13
+ def minimum_length_from_validation
14
+ minlength = options[:minlength]
15
+ if minlength.is_a?(String) || minlength.is_a?(Integer)
16
+ minlength
17
+ else
18
+ length_validator = find_length_validator
19
+ minimum_length_value_from(length_validator)
20
+ end
21
+ end
22
+
23
+ def find_length_validator
24
+ find_validator(:length)
25
+ end
26
+
27
+ def has_tokenizer?(length_validator)
28
+ length_validator.options[:tokenizer]
29
+ end
30
+
31
+ def minimum_length_value_from(length_validator)
32
+ if length_validator
33
+ length_validator.options[:is] || length_validator.options[:minimum]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -7,7 +8,7 @@ module SimpleForm
7
8
  nil
8
9
  end
9
10
 
10
- def placeholder_text
11
+ def placeholder_text(wrapper_options = nil)
11
12
  placeholder = options[:placeholder]
12
13
  placeholder.is_a?(String) ? placeholder : translate_from_namespace(:placeholders)
13
14
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Components
3
4
  # Needs to be enabled in order to do automatic lookups.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  # Components are a special type of helpers that can work on their own.
3
4
  # For example, by using a component, it will automatically change the
@@ -15,6 +16,7 @@ module SimpleForm
15
16
  autoload :Labels
16
17
  autoload :MinMax
17
18
  autoload :Maxlength
19
+ autoload :Minlength
18
20
  autoload :Pattern
19
21
  autoload :Placeholders
20
22
  autoload :Readonly
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  class ErrorNotification
3
4
  delegate :object, :object_name, :template, to: :@builder
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'active_support/core_ext/object/deep_dup'
2
3
  require 'simple_form/map_type'
3
4
  require 'simple_form/tags'
@@ -12,24 +13,25 @@ module SimpleForm
12
13
  'update' => 'edit'
13
14
  }
14
15
 
15
- ATTRIBUTE_COMPONENTS = [:html5, :min_max, :maxlength, :placeholder, :pattern, :readonly]
16
+ ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
16
17
 
17
18
  extend MapType
18
19
  include SimpleForm::Inputs
19
20
 
20
- map_type :text, to: SimpleForm::Inputs::TextInput
21
- map_type :file, to: SimpleForm::Inputs::FileInput
22
- map_type :string, :email, :search, :tel, :url, :uuid, to: SimpleForm::Inputs::StringInput
23
- map_type :password, to: SimpleForm::Inputs::PasswordInput
24
- map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
25
- map_type :range, to: SimpleForm::Inputs::RangeInput
26
- map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
27
- map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
28
- map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
29
- map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
30
- map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
31
- map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
32
- map_type :boolean, to: SimpleForm::Inputs::BooleanInput
21
+ map_type :text, :hstore, :json, :jsonb, to: SimpleForm::Inputs::TextInput
22
+ map_type :file, to: SimpleForm::Inputs::FileInput
23
+ map_type :string, :email, :search, :tel, :url, :uuid, :citext, to: SimpleForm::Inputs::StringInput
24
+ map_type :password, to: SimpleForm::Inputs::PasswordInput
25
+ map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
26
+ map_type :range, to: SimpleForm::Inputs::RangeInput
27
+ map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
28
+ map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
29
+ map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
30
+ map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
31
+ map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
32
+ map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
33
+ map_type :boolean, to: SimpleForm::Inputs::BooleanInput
34
+ map_type :hidden, to: SimpleForm::Inputs::HiddenInput
33
35
 
34
36
  def self.discovery_cache
35
37
  @discovery_cache ||= {}
@@ -37,6 +39,7 @@ module SimpleForm
37
39
 
38
40
  def initialize(*) #:nodoc:
39
41
  super
42
+ @object = convert_to_model(@object)
40
43
  @defaults = options[:defaults]
41
44
  @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
42
45
  end
@@ -47,6 +50,11 @@ module SimpleForm
47
50
  # label + input + hint (when defined) + errors (when exists), and all can
48
51
  # be configured inside a wrapper html.
49
52
  #
53
+ # If a block is given, the contents of the block will replace the input
54
+ # field that would otherwise be generated automatically. The content will
55
+ # be given a label and wrapper div to make it consistent with the other
56
+ # elements in the form.
57
+ #
50
58
  # == Examples
51
59
  #
52
60
  # # Imagine @user has error "can't be blank" on name
@@ -130,16 +138,39 @@ module SimpleForm
130
138
  # <input class="string required" id="user_name" maxlength="100"
131
139
  # name="user[name]" type="text" value="Carlos" />
132
140
  #
141
+ # It also support validation classes once it is configured.
142
+ #
143
+ # # config/initializers/simple_form.rb
144
+ # SimpleForm.setup do |config|
145
+ # config.input_field_valid_class = 'is-valid'
146
+ # config.input_field_error_class = 'is-invalid'
147
+ # end
148
+ #
149
+ # simple_form_for @user do |f|
150
+ # f.input_field :name
151
+ # end
152
+ #
153
+ # When the validation happens, the input will be rendered with
154
+ # the class configured according to the validation:
155
+ #
156
+ # - when the input is valid:
157
+ #
158
+ # <input class="is-valid string required" id="user_name" value="Carlos" />
159
+ #
160
+ # - when the input is invalid:
161
+ #
162
+ # <input class="is-invalid string required" id="user_name" value="" />
163
+ #
133
164
  def input_field(attribute_name, options = {})
134
165
  components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
135
166
 
136
167
  options = options.dup
137
- options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, *components)
168
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components)
138
169
  options = @defaults.deep_dup.deep_merge(options) if @defaults
139
170
 
140
171
  input = find_input(attribute_name, options)
141
172
  wrapper = find_wrapper(input.input_type, options)
142
- components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
173
+ components = build_input_field_components(components.push(:input))
143
174
 
144
175
  SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
145
176
  end
@@ -206,8 +237,8 @@ module SimpleForm
206
237
  options = args.extract_options!.dup
207
238
  options[:class] = [SimpleForm.button_class, options[:class]].compact
208
239
  args << options
209
- if respond_to?("#{type}_button")
210
- send("#{type}_button", *args, &block)
240
+ if respond_to?(:"#{type}_button")
241
+ send(:"#{type}_button", *args, &block)
211
242
  else
212
243
  send(type, *args, &block)
213
244
  end
@@ -284,7 +315,7 @@ module SimpleForm
284
315
  #
285
316
  # f.label :name # Do I18n lookup
286
317
  # f.label :name, "Name" # Same behavior as Rails, do not add required tag
287
- # f.label :name, label: "Name" # Same as above, but adds required tag
318
+ # f.label :name, label: "Name" # Same as above, but adds required tag
288
319
  #
289
320
  # f.label :name, required: false
290
321
  # f.label :name, id: "cool_label"
@@ -293,7 +324,7 @@ module SimpleForm
293
324
  return super if args.first.is_a?(String) || block_given?
294
325
 
295
326
  options = args.extract_options!.dup
296
- options[:label_html] = options.except(:label, :required, :as)
327
+ options[:label_html] = options.except(:label, :label_text, :required, :as)
297
328
 
298
329
  column = find_attribute_column(attribute_name)
299
330
  input_type = default_input_type(attribute_name, column, options)
@@ -455,13 +486,17 @@ module SimpleForm
455
486
  relation = reflection.klass.all
456
487
 
457
488
  if reflection.respond_to?(:scope) && reflection.scope
458
- relation = reflection.klass.instance_exec(&reflection.scope)
489
+ if reflection.scope.parameters.any?
490
+ relation = reflection.klass.instance_exec(object, &reflection.scope)
491
+ else
492
+ relation = reflection.klass.instance_exec(&reflection.scope)
493
+ end
459
494
  else
460
495
  order = reflection.options[:order]
461
496
  conditions = reflection.options[:conditions]
462
497
  conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
463
498
 
464
- relation = relation.where(conditions)
499
+ relation = relation.where(conditions) if relation.respond_to?(:where)
465
500
  relation = relation.order(order) if relation.respond_to?(:order)
466
501
  end
467
502
 
@@ -476,7 +511,7 @@ module SimpleForm
476
511
  when :has_one
477
512
  raise ArgumentError, ":has_one associations are not supported by f.association"
478
513
  else
479
- if options[:as] == :select
514
+ if options[:as] == :select || options[:as] == :grouped_select
480
515
  html_options = options[:input_html] ||= {}
481
516
  html_options[:multiple] = true unless html_options.key?(:multiple)
482
517
  end
@@ -504,25 +539,25 @@ module SimpleForm
504
539
  end
505
540
 
506
541
  # Attempt to guess the better input type given the defined options. By
507
- # default alwayls fallback to the user :as option, or to a :select when a
542
+ # default always fallback to the user :as option, or to a :select when a
508
543
  # collection is given.
509
544
  def default_input_type(attribute_name, column, options)
510
545
  return options[:as].to_sym if options[:as]
511
- return :select if options[:collection]
512
546
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
547
+ return :select if options[:collection]
513
548
 
514
549
  input_type = column.try(:type)
515
550
  case input_type
516
551
  when :timestamp
517
552
  :datetime
518
- when :string, nil
553
+ when :string, :citext, nil
519
554
  case attribute_name.to_s
520
- when /password/ then :password
521
- when /time_zone/ then :time_zone
522
- when /country/ then :country
523
- when /email/ then :email
524
- when /phone/ then :tel
525
- when /url/ then :url
555
+ when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password
556
+ when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone
557
+ when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country
558
+ when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email
559
+ when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel
560
+ when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url
526
561
  else
527
562
  file_method?(attribute_name) ? :file : (input_type || :string)
528
563
  end
@@ -537,13 +572,34 @@ module SimpleForm
537
572
  }.try(:last) if SimpleForm.input_mappings
538
573
  end
539
574
 
575
+ # Internal: Try to discover whether an attribute corresponds to a file or not.
576
+ #
577
+ # Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in.
578
+ # This method tries to guess if an attribute belongs to some of these Gems by checking the presence
579
+ # of their methods using `#respond_to?`.
580
+ #
581
+ # Note: This does not support multiple file upload inputs, as this is very application-specific.
582
+ #
583
+ # The order here was choosen based on the popularity of Gems and for commodity - e.g. the method
584
+ # with the suffix `_url` is present in three Gems, so it's checked with priority:
585
+ #
586
+ # - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
587
+ # - `#{attribute_name}_url` - Shrine >= `0.9.0`, Refile >= `0.6.0` and CarrierWave >= `0.2.1`
588
+ # - `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0`
589
+ # - `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility)
590
+ #
591
+ # Returns a Boolean.
540
592
  def file_method?(attribute_name)
541
- file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
542
- file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
593
+ @object.respond_to?("#{attribute_name}_attachment") ||
594
+ @object.respond_to?("#{attribute_name}_url") ||
595
+ @object.respond_to?("#{attribute_name}_attacher") ||
596
+ @object.respond_to?("#{attribute_name}_file_name")
543
597
  end
544
598
 
545
599
  def find_attribute_column(attribute_name)
546
- if @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
600
+ if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
601
+ @object.type_for_attribute(attribute_name.to_s)
602
+ elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
547
603
  @object.column_for_attribute(attribute_name)
548
604
  end
549
605
  end
@@ -632,5 +688,31 @@ module SimpleForm
632
688
 
633
689
  nil
634
690
  end
691
+
692
+ def build_input_field_components(components)
693
+ components.map do |component|
694
+ if component == :input
695
+ SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
696
+ else
697
+ SimpleForm::Wrappers::Leaf.new(component)
698
+ end
699
+ end
700
+ end
701
+
702
+ def build_input_field_options
703
+ input_field_options = {}
704
+ valid_class = SimpleForm.input_field_valid_class
705
+ error_class = SimpleForm.input_field_error_class
706
+
707
+ if error_class.present?
708
+ input_field_options[:error_class] = error_class
709
+ end
710
+
711
+ if valid_class.present?
712
+ input_field_options[:valid_class] = valid_class
713
+ end
714
+
715
+ input_field_options
716
+ end
635
717
  end
636
718
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Autofocus
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Disabled
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Readonly
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Required
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Helpers
3
4
  module Validators
@@ -24,7 +25,7 @@ module SimpleForm
24
25
  end
25
26
 
26
27
  def action_validator_match?(validator)
27
- return true if !validator.options.include?(:on)
28
+ return true unless validator.options.include?(:on)
28
29
 
29
30
  case validator.options[:on]
30
31
  when :save
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  # Helpers are made of several helpers that cannot be turned on automatically.
3
4
  # For instance, disabled cannot be turned on automatically, it requires the
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  # A lot of configuration values are retrived from I18n,
3
4
  # like boolean collection, required string. This module provides