simple_form 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -25
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +441 -107
  5. data/lib/generators/simple_form/install_generator.rb +4 -3
  6. data/lib/generators/simple_form/templates/README +4 -5
  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 +48 -11
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +417 -23
  12. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +101 -5
  13. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
  14. data/lib/simple_form/action_view_extensions/builder.rb +2 -0
  15. data/lib/simple_form/action_view_extensions/form_helper.rb +10 -3
  16. data/lib/simple_form/components/errors.rb +38 -3
  17. data/lib/simple_form/components/hints.rb +3 -2
  18. data/lib/simple_form/components/html5.rb +16 -5
  19. data/lib/simple_form/components/label_input.rb +21 -2
  20. data/lib/simple_form/components/labels.rb +22 -11
  21. data/lib/simple_form/components/maxlength.rb +9 -5
  22. data/lib/simple_form/components/min_max.rb +2 -1
  23. data/lib/simple_form/components/minlength.rb +38 -0
  24. data/lib/simple_form/components/pattern.rb +2 -1
  25. data/lib/simple_form/components/placeholders.rb +4 -3
  26. data/lib/simple_form/components/readonly.rb +2 -1
  27. data/lib/simple_form/components.rb +2 -0
  28. data/lib/simple_form/error_notification.rb +1 -0
  29. data/lib/simple_form/form_builder.rb +193 -80
  30. data/lib/simple_form/helpers/autofocus.rb +1 -0
  31. data/lib/simple_form/helpers/disabled.rb +1 -0
  32. data/lib/simple_form/helpers/readonly.rb +1 -0
  33. data/lib/simple_form/helpers/required.rb +1 -0
  34. data/lib/simple_form/helpers/validators.rb +2 -1
  35. data/lib/simple_form/helpers.rb +6 -5
  36. data/lib/simple_form/i18n_cache.rb +1 -0
  37. data/lib/simple_form/inputs/base.rb +57 -14
  38. data/lib/simple_form/inputs/block_input.rb +2 -1
  39. data/lib/simple_form/inputs/boolean_input.rb +39 -16
  40. data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
  41. data/lib/simple_form/inputs/collection_input.rb +37 -14
  42. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +9 -13
  43. data/lib/simple_form/inputs/collection_select_input.rb +5 -2
  44. data/lib/simple_form/inputs/date_time_input.rb +24 -9
  45. data/lib/simple_form/inputs/file_input.rb +5 -2
  46. data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
  47. data/lib/simple_form/inputs/hidden_input.rb +5 -2
  48. data/lib/simple_form/inputs/numeric_input.rb +6 -4
  49. data/lib/simple_form/inputs/password_input.rb +6 -3
  50. data/lib/simple_form/inputs/priority_input.rb +5 -2
  51. data/lib/simple_form/inputs/range_input.rb +2 -1
  52. data/lib/simple_form/inputs/string_input.rb +6 -3
  53. data/lib/simple_form/inputs/text_input.rb +6 -3
  54. data/lib/simple_form/inputs.rb +1 -0
  55. data/lib/simple_form/map_type.rb +1 -0
  56. data/lib/simple_form/railtie.rb +8 -0
  57. data/lib/simple_form/tags.rb +7 -0
  58. data/lib/simple_form/version.rb +2 -1
  59. data/lib/simple_form/wrappers/builder.rb +7 -6
  60. data/lib/simple_form/wrappers/leaf.rb +29 -0
  61. data/lib/simple_form/wrappers/many.rb +7 -6
  62. data/lib/simple_form/wrappers/root.rb +3 -1
  63. data/lib/simple_form/wrappers/single.rb +7 -4
  64. data/lib/simple_form/wrappers.rb +2 -0
  65. data/lib/simple_form.rb +117 -18
  66. data/test/action_view_extensions/builder_test.rb +42 -41
  67. data/test/action_view_extensions/form_helper_test.rb +36 -16
  68. data/test/components/custom_components_test.rb +62 -0
  69. data/test/components/label_test.rb +70 -41
  70. data/test/form_builder/association_test.rb +79 -37
  71. data/test/form_builder/button_test.rb +11 -10
  72. data/test/form_builder/error_notification_test.rb +2 -1
  73. data/test/form_builder/error_test.rb +173 -24
  74. data/test/form_builder/general_test.rb +171 -73
  75. data/test/form_builder/hint_test.rb +24 -18
  76. data/test/form_builder/input_field_test.rb +132 -56
  77. data/test/form_builder/label_test.rb +68 -13
  78. data/test/form_builder/wrapper_test.rb +190 -22
  79. data/test/generators/simple_form_generator_test.rb +8 -7
  80. data/test/inputs/boolean_input_test.rb +89 -6
  81. data/test/inputs/collection_check_boxes_input_test.rb +109 -25
  82. data/test/inputs/collection_radio_buttons_input_test.rb +168 -54
  83. data/test/inputs/collection_select_input_test.rb +183 -77
  84. data/test/inputs/datetime_input_test.rb +131 -50
  85. data/test/inputs/disabled_test.rb +16 -15
  86. data/test/inputs/discovery_test.rb +58 -6
  87. data/test/inputs/file_input_test.rb +3 -2
  88. data/test/inputs/general_test.rb +23 -22
  89. data/test/inputs/grouped_collection_select_input_test.rb +54 -17
  90. data/test/inputs/hidden_input_test.rb +5 -4
  91. data/test/inputs/numeric_input_test.rb +48 -44
  92. data/test/inputs/priority_input_test.rb +23 -14
  93. data/test/inputs/readonly_test.rb +20 -19
  94. data/test/inputs/required_test.rb +58 -13
  95. data/test/inputs/string_input_test.rb +62 -33
  96. data/test/inputs/text_input_test.rb +20 -7
  97. data/test/simple_form_test.rb +9 -0
  98. data/test/support/discovery_inputs.rb +33 -2
  99. data/test/support/misc_helpers.rb +113 -5
  100. data/test/support/mock_controller.rb +7 -1
  101. data/test/support/models.rb +137 -37
  102. data/test/test_helper.rb +15 -1
  103. metadata +25 -33
@@ -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'
@@ -8,28 +9,29 @@ module SimpleForm
8
9
 
9
10
  # When action is create or update, we still should use new and edit
10
11
  ACTIONS = {
11
- create: :new,
12
- update: :edit
12
+ 'create' => 'new',
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, 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
@@ -106,18 +114,13 @@ module SimpleForm
106
114
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
107
115
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
108
116
  #
109
- def input(attribute_name, options={}, &block)
117
+ def input(attribute_name, options = {}, &block)
110
118
  options = @defaults.deep_dup.deep_merge(options) if @defaults
111
- input = find_input(attribute_name, options, &block)
112
119
 
113
- chosen =
114
- if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
115
- name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
116
- else
117
- wrapper
118
- end
120
+ input = find_input(attribute_name, options, &block)
121
+ wrapper = find_wrapper(input.input_type, options)
119
122
 
120
- chosen.render input
123
+ wrapper.render input
121
124
  end
122
125
  alias :attribute :input
123
126
 
@@ -135,12 +138,41 @@ module SimpleForm
135
138
  # <input class="string required" id="user_name" maxlength="100"
136
139
  # name="user[name]" type="text" value="Carlos" />
137
140
  #
138
- def input_field(attribute_name, options={})
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
+ #
164
+ def input_field(attribute_name, options = {})
165
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
166
+
139
167
  options = options.dup
140
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
168
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, :prompt, *components)
141
169
  options = @defaults.deep_dup.deep_merge(options) if @defaults
142
170
 
143
- SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
171
+ input = find_input(attribute_name, options)
172
+ wrapper = find_wrapper(input.input_type, options)
173
+ components = build_input_field_components(components.push(:input))
174
+
175
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
144
176
  end
145
177
 
146
178
  # Helper for dealing with association selects/radios, generating the
@@ -171,7 +203,7 @@ module SimpleForm
171
203
  #
172
204
  # Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
173
205
  #
174
- def association(association, options={}, &block)
206
+ def association(association, options = {}, &block)
175
207
  options = options.dup
176
208
 
177
209
  return simple_fields_for(*[association,
@@ -183,31 +215,9 @@ module SimpleForm
183
215
  raise "Association #{association.inspect} not found" unless reflection
184
216
 
185
217
  options[:as] ||= :select
186
- options[:collection] ||= options.fetch(:collection) {
187
- conditions = reflection.options[:conditions]
188
- conditions = conditions.call if conditions.respond_to?(:call)
189
- reflection.klass.where(conditions).order(reflection.options[:order])
190
- }
191
-
192
- attribute = case reflection.macro
193
- when :belongs_to
194
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
195
- when :has_one
196
- raise ArgumentError, ":has_one associations are not supported by f.association"
197
- else
198
- if options[:as] == :select
199
- html_options = options[:input_html] ||= {}
200
- html_options[:multiple] = true unless html_options.key?(:multiple)
201
- end
202
-
203
- # Force the association to be preloaded for performance.
204
- if options[:preload] != false && object.respond_to?(association)
205
- target = object.send(association)
206
- target.to_a if target.respond_to?(:to_a)
207
- end
218
+ options[:collection] ||= fetch_association_collection(reflection, options)
208
219
 
209
- :"#{reflection.name.to_s.singularize}_ids"
210
- end
220
+ attribute = build_association_attribute(reflection, association, options)
211
221
 
212
222
  input(attribute, options.merge(reflection: reflection))
213
223
  end
@@ -227,8 +237,8 @@ module SimpleForm
227
237
  options = args.extract_options!.dup
228
238
  options[:class] = [SimpleForm.button_class, options[:class]].compact
229
239
  args << options
230
- if respond_to?("#{type}_button")
231
- send("#{type}_button", *args, &block)
240
+ if respond_to?(:"#{type}_button")
241
+ send(:"#{type}_button", *args, &block)
232
242
  else
233
243
  send(type, *args, &block)
234
244
  end
@@ -242,7 +252,7 @@ module SimpleForm
242
252
  # f.error :name
243
253
  # f.error :name, id: "cool_error"
244
254
  #
245
- def error(attribute_name, options={})
255
+ def error(attribute_name, options = {})
246
256
  options = options.dup
247
257
 
248
258
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -259,7 +269,7 @@ module SimpleForm
259
269
  #
260
270
  # f.full_error :token #=> <span class="error">Token is invalid</span>
261
271
  #
262
- def full_error(attribute_name, options={})
272
+ def full_error(attribute_name, options = {})
263
273
  options = options.dup
264
274
 
265
275
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -281,7 +291,7 @@ module SimpleForm
281
291
  # f.hint :name, id: "cool_hint"
282
292
  # f.hint "Don't forget to accept this"
283
293
  #
284
- def hint(attribute_name, options={})
294
+ def hint(attribute_name, options = {})
285
295
  options = options.dup
286
296
 
287
297
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -305,7 +315,7 @@ module SimpleForm
305
315
  #
306
316
  # f.label :name # Do I18n lookup
307
317
  # f.label :name, "Name" # Same behavior as Rails, do not add required tag
308
- # 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
309
319
  #
310
320
  # f.label :name, required: false
311
321
  # f.label :name, id: "cool_label"
@@ -314,7 +324,7 @@ module SimpleForm
314
324
  return super if args.first.is_a?(String) || block_given?
315
325
 
316
326
  options = args.extract_options!.dup
317
- options[:label_html] = options.except(:label, :required, :as)
327
+ options[:label_html] = options.except(:label, :label_text, :required, :as)
318
328
 
319
329
  column = find_attribute_column(attribute_name)
320
330
  input_type = default_input_type(attribute_name, column, options)
@@ -332,7 +342,7 @@ module SimpleForm
332
342
  # f.error_notification message: 'Something went wrong'
333
343
  # f.error_notification id: 'user_error_message', class: 'form_error'
334
344
  #
335
- def error_notification(options={})
345
+ def error_notification(options = {})
336
346
  SimpleForm::ErrorNotification.new(self, options).render
337
347
  end
338
348
 
@@ -464,15 +474,60 @@ module SimpleForm
464
474
  @lookup_action ||= begin
465
475
  action = template.controller && template.controller.action_name
466
476
  return unless action
467
- action = action.to_sym
477
+ action = action.to_s
468
478
  ACTIONS[action] || action
469
479
  end
470
480
  end
471
481
 
472
482
  private
473
483
 
484
+ def fetch_association_collection(reflection, options)
485
+ options.fetch(:collection) do
486
+ relation = reflection.klass.all
487
+
488
+ if reflection.respond_to?(:scope) && 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
494
+ else
495
+ order = reflection.options[:order]
496
+ conditions = reflection.options[:conditions]
497
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
498
+
499
+ relation = relation.where(conditions) if relation.respond_to?(:where)
500
+ relation = relation.order(order) if relation.respond_to?(:order)
501
+ end
502
+
503
+ relation
504
+ end
505
+ end
506
+
507
+ def build_association_attribute(reflection, association, options)
508
+ case reflection.macro
509
+ when :belongs_to
510
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
511
+ when :has_one
512
+ raise ArgumentError, ":has_one associations are not supported by f.association"
513
+ else
514
+ if options[:as] == :select
515
+ html_options = options[:input_html] ||= {}
516
+ html_options[:multiple] = true unless html_options.key?(:multiple)
517
+ end
518
+
519
+ # Force the association to be preloaded for performance.
520
+ if options[:preload] != false && object.respond_to?(association)
521
+ target = object.send(association)
522
+ target.to_a if target.respond_to?(:to_a)
523
+ end
524
+
525
+ :"#{reflection.name.to_s.singularize}_ids"
526
+ end
527
+ end
528
+
474
529
  # Find an input based on the attribute name.
475
- def find_input(attribute_name, options={}, &block) #:nodoc:
530
+ def find_input(attribute_name, options = {}, &block)
476
531
  column = find_attribute_column(attribute_name)
477
532
  input_type = default_input_type(attribute_name, column, options)
478
533
 
@@ -484,18 +539,18 @@ module SimpleForm
484
539
  end
485
540
 
486
541
  # Attempt to guess the better input type given the defined options. By
487
- # 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
488
543
  # collection is given.
489
- def default_input_type(attribute_name, column, options) #:nodoc:
544
+ def default_input_type(attribute_name, column, options)
490
545
  return options[:as].to_sym if options[:as]
491
- return :select if options[:collection]
492
546
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
547
+ return :select if options[:collection]
493
548
 
494
549
  input_type = column.try(:type)
495
550
  case input_type
496
551
  when :timestamp
497
552
  :datetime
498
- when :string, nil
553
+ when :string, :citext, nil
499
554
  case attribute_name.to_s
500
555
  when /password/ then :password
501
556
  when /time_zone/ then :time_zone
@@ -511,24 +566,26 @@ module SimpleForm
511
566
  end
512
567
  end
513
568
 
514
- def find_custom_type(attribute_name) #:nodoc:
569
+ def find_custom_type(attribute_name)
515
570
  SimpleForm.input_mappings.find { |match, type|
516
571
  attribute_name =~ match
517
572
  }.try(:last) if SimpleForm.input_mappings
518
573
  end
519
574
 
520
- def file_method?(attribute_name) #:nodoc:
575
+ def file_method?(attribute_name)
521
576
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
522
577
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
523
578
  end
524
579
 
525
- def find_attribute_column(attribute_name) #:nodoc:
526
- if @object.respond_to?(:column_for_attribute)
580
+ def find_attribute_column(attribute_name)
581
+ if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
582
+ @object.type_for_attribute(attribute_name.to_s)
583
+ elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
527
584
  @object.column_for_attribute(attribute_name)
528
585
  end
529
586
  end
530
587
 
531
- def find_association_reflection(association) #:nodoc:
588
+ def find_association_reflection(association)
532
589
  if @object.class.respond_to?(:reflect_on_association)
533
590
  @object.class.reflect_on_association(association)
534
591
  end
@@ -541,24 +598,42 @@ module SimpleForm
541
598
  # b) Or use the found mapping
542
599
  # 2) If not, fallbacks to #{input_type}Input
543
600
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
544
- def find_mapping(input_type) #:nodoc:
601
+ def find_mapping(input_type)
545
602
  discovery_cache[input_type] ||=
546
603
  if mapping = self.class.mappings[input_type]
547
604
  mapping_override(mapping) || mapping
548
605
  else
549
606
  camelized = "#{input_type.to_s.camelize}Input"
550
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
607
+ attempt_mapping_with_custom_namespace(camelized) ||
608
+ attempt_mapping(camelized, Object) ||
609
+ attempt_mapping(camelized, self.class) ||
551
610
  raise("No input found for #{input_type}")
552
611
  end
553
612
  end
554
613
 
555
- def find_wrapper_mapping(input_type) #:nodoc:
556
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
614
+ # Attempts to find a wrapper mapping. It follows the following rules:
615
+ #
616
+ # 1) It tries to find a wrapper for the current form
617
+ # 2) If not, it tries to find a config
618
+ def find_wrapper_mapping(input_type)
619
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
620
+ options[:wrapper_mappings][input_type]
621
+ else
622
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
623
+ end
624
+ end
625
+
626
+ def find_wrapper(input_type, options)
627
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
628
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
629
+ else
630
+ wrapper
631
+ end
557
632
  end
558
633
 
559
634
  # If cache_discovery is enabled, use the class level cache that persists
560
635
  # between requests, otherwise use the instance one.
561
- def discovery_cache #:nodoc:
636
+ def discovery_cache
562
637
  if SimpleForm.cache_discovery
563
638
  self.class.discovery_cache
564
639
  else
@@ -566,14 +641,16 @@ module SimpleForm
566
641
  end
567
642
  end
568
643
 
569
- def mapping_override(klass) #:nodoc:
644
+ def mapping_override(klass)
570
645
  name = klass.name
571
646
  if name =~ /^SimpleForm::Inputs/
572
- attempt_mapping name.split("::").last, Object
647
+ input_name = name.split("::").last
648
+ attempt_mapping_with_custom_namespace(input_name) ||
649
+ attempt_mapping(input_name, Object)
573
650
  end
574
651
  end
575
652
 
576
- def attempt_mapping(mapping, at) #:nodoc:
653
+ def attempt_mapping(mapping, at)
577
654
  return if SimpleForm.inputs_discovery == false && at == Object
578
655
 
579
656
  begin
@@ -582,5 +659,41 @@ module SimpleForm
582
659
  raise if e.message !~ /#{mapping}$/
583
660
  end
584
661
  end
662
+
663
+ def attempt_mapping_with_custom_namespace(input_name)
664
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
665
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
666
+ return mapping
667
+ end
668
+ end
669
+
670
+ nil
671
+ end
672
+
673
+ def build_input_field_components(components)
674
+ components.map do |component|
675
+ if component == :input
676
+ SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
677
+ else
678
+ SimpleForm::Wrappers::Leaf.new(component)
679
+ end
680
+ end
681
+ end
682
+
683
+ def build_input_field_options
684
+ input_field_options = {}
685
+ valid_class = SimpleForm.input_field_valid_class
686
+ error_class = SimpleForm.input_field_error_class
687
+
688
+ if error_class.present?
689
+ input_field_options[:error_class] = error_class
690
+ end
691
+
692
+ if valid_class.present?
693
+ input_field_options[:valid_class] = valid_class
694
+ end
695
+
696
+ input_field_options
697
+ end
585
698
  end
586
699
  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,12 +1,13 @@
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
4
5
  # user to explicitly pass the option disabled: true so it may work.
5
6
  module Helpers
6
- autoload :Autofocus, 'simple_form/helpers/autofocus'
7
- autoload :Disabled, 'simple_form/helpers/disabled'
8
- autoload :Readonly, 'simple_form/helpers/readonly'
9
- autoload :Required, 'simple_form/helpers/required'
10
- autoload :Validators, 'simple_form/helpers/validators'
7
+ autoload :Autofocus, 'simple_form/helpers/autofocus'
8
+ autoload :Disabled, 'simple_form/helpers/disabled'
9
+ autoload :Readonly, 'simple_form/helpers/readonly'
10
+ autoload :Required, 'simple_form/helpers/required'
11
+ autoload :Validators, 'simple_form/helpers/validators'
11
12
  end
12
13
  end
@@ -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
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  require 'simple_form/i18n_cache'
2
3
  require 'active_support/core_ext/string/output_safety'
4
+ require 'action_view/helpers'
3
5
 
4
6
  module SimpleForm
5
7
  module Inputs
6
8
  class Base
7
9
  include ERB::Util
10
+ include ActionView::Helpers::TranslationHelper
8
11
 
9
12
  extend I18nCache
10
13
 
@@ -19,6 +22,7 @@ module SimpleForm
19
22
  include SimpleForm::Components::HTML5
20
23
  include SimpleForm::Components::LabelInput
21
24
  include SimpleForm::Components::Maxlength
25
+ include SimpleForm::Components::Minlength
22
26
  include SimpleForm::Components::MinMax
23
27
  include SimpleForm::Components::Pattern
24
28
  include SimpleForm::Components::Placeholders
@@ -48,19 +52,19 @@ module SimpleForm
48
52
  enable :hint
49
53
 
50
54
  # Usually disabled, needs to be enabled explicitly passing true as option.
51
- disable :maxlength, :placeholder, :pattern, :min_max
55
+ disable :maxlength, :minlength, :placeholder, :pattern, :min_max
52
56
 
53
57
  def initialize(builder, attribute_name, column, input_type, options = {})
54
58
  super
55
59
 
56
- options = options.dup
57
- @builder = builder
58
- @attribute_name = attribute_name
59
- @column = column
60
- @input_type = input_type
61
- @reflection = options.delete(:reflection)
62
- @options = options.reverse_merge!(self.class.default_options)
63
- @required = calculate_required
60
+ options = options.dup
61
+ @builder = builder
62
+ @attribute_name = attribute_name
63
+ @column = column
64
+ @input_type = input_type
65
+ @reflection = options.delete(:reflection)
66
+ @options = options.reverse_merge!(self.class.default_options)
67
+ @required = calculate_required
64
68
 
65
69
  # Notice that html_options_for receives a reference to input_html_classes.
66
70
  # This means that classes added dynamically to input_html_classes will
@@ -79,7 +83,7 @@ module SimpleForm
79
83
  end
80
84
  end
81
85
 
82
- def input
86
+ def input(wrapper_options = nil)
83
87
  raise NotImplementedError
84
88
  end
85
89
 
@@ -92,7 +96,7 @@ module SimpleForm
92
96
  end
93
97
 
94
98
  def input_class
95
- "#{lookup_model_names.join("_")}_#{reflection_or_attribute_name}"
99
+ "#{lookup_model_names.join('_')}_#{reflection_or_attribute_name}"
96
100
  end
97
101
 
98
102
  private
@@ -113,7 +117,7 @@ module SimpleForm
113
117
  end
114
118
 
115
119
  def decimal_or_float?
116
- column.number? && column.type != :integer
120
+ column.type == :float || column.type == :decimal
117
121
  end
118
122
 
119
123
  def nested_boolean_style?
@@ -167,7 +171,7 @@ module SimpleForm
167
171
  # email: 'E-mail.'
168
172
  #
169
173
  # Take a look at our locale example file.
170
- def translate(namespace, default='')
174
+ def translate_from_namespace(namespace, default = '')
171
175
  model_names = lookup_model_names.dup
172
176
  lookups = []
173
177
 
@@ -182,7 +186,46 @@ module SimpleForm
182
186
  lookups << :"defaults.#{reflection_or_attribute_name}"
183
187
  lookups << default
184
188
 
185
- I18n.t(lookups.shift, scope: :"simple_form.#{namespace}", default: lookups).presence
189
+ I18n.t(lookups.shift, scope: :"#{i18n_scope}.#{namespace}", default: lookups).presence
190
+ end
191
+
192
+ def merge_wrapper_options(options, wrapper_options)
193
+ if wrapper_options
194
+ wrapper_options = set_input_classes(wrapper_options)
195
+
196
+ wrapper_options.merge(options) do |key, oldval, newval|
197
+ case key.to_s
198
+ when "class"
199
+ Array(oldval) + Array(newval)
200
+ when "data", "aria"
201
+ oldval.merge(newval)
202
+ else
203
+ newval
204
+ end
205
+ end
206
+ else
207
+ options
208
+ end
209
+ end
210
+
211
+ def set_input_classes(wrapper_options)
212
+ wrapper_options = wrapper_options.dup
213
+ error_class = wrapper_options.delete(:error_class)
214
+ valid_class = wrapper_options.delete(:valid_class)
215
+
216
+ if error_class.present? && has_errors?
217
+ wrapper_options[:class] = "#{wrapper_options[:class]} #{error_class}"
218
+ end
219
+
220
+ if valid_class.present? && valid?
221
+ wrapper_options[:class] = "#{wrapper_options[:class]} #{valid_class}"
222
+ end
223
+
224
+ wrapper_options
225
+ end
226
+
227
+ def i18n_scope
228
+ SimpleForm.i18n_scope
186
229
  end
187
230
  end
188
231
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SimpleForm
2
3
  module Inputs
3
4
  class BlockInput < Base
@@ -6,7 +7,7 @@ module SimpleForm
6
7
  @block = block
7
8
  end
8
9
 
9
- def input
10
+ def input(wrapper_options = nil)
10
11
  template.capture(&@block)
11
12
  end
12
13
  end