simple_form 3.0.4 → 5.0.3

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 (107) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +199 -33
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +453 -128
  5. data/lib/generators/simple_form/install_generator.rb +4 -3
  6. data/lib/generators/simple_form/templates/README +3 -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 +47 -16
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +418 -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 +39 -6
  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 +220 -89
  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 +62 -16
  38. data/lib/simple_form/inputs/block_input.rb +2 -1
  39. data/lib/simple_form/inputs/boolean_input.rb +40 -16
  40. data/lib/simple_form/inputs/collection_check_boxes_input.rb +3 -2
  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/color_input.rb +14 -0
  45. data/lib/simple_form/inputs/date_time_input.rb +24 -9
  46. data/lib/simple_form/inputs/file_input.rb +5 -2
  47. data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
  48. data/lib/simple_form/inputs/hidden_input.rb +5 -2
  49. data/lib/simple_form/inputs/numeric_input.rb +6 -4
  50. data/lib/simple_form/inputs/password_input.rb +6 -3
  51. data/lib/simple_form/inputs/priority_input.rb +5 -6
  52. data/lib/simple_form/inputs/range_input.rb +2 -1
  53. data/lib/simple_form/inputs/rich_text_area_input.rb +12 -0
  54. data/lib/simple_form/inputs/string_input.rb +7 -4
  55. data/lib/simple_form/inputs/text_input.rb +6 -3
  56. data/lib/simple_form/inputs.rb +3 -0
  57. data/lib/simple_form/map_type.rb +1 -0
  58. data/lib/simple_form/railtie.rb +8 -0
  59. data/lib/simple_form/tags.rb +13 -2
  60. data/lib/simple_form/version.rb +2 -1
  61. data/lib/simple_form/wrappers/builder.rb +7 -6
  62. data/lib/simple_form/wrappers/leaf.rb +29 -0
  63. data/lib/simple_form/wrappers/many.rb +7 -6
  64. data/lib/simple_form/wrappers/root.rb +10 -3
  65. data/lib/simple_form/wrappers/single.rb +7 -4
  66. data/lib/simple_form/wrappers.rb +2 -0
  67. data/lib/simple_form.rb +137 -21
  68. data/test/action_view_extensions/builder_test.rb +64 -45
  69. data/test/action_view_extensions/form_helper_test.rb +36 -16
  70. data/test/components/custom_components_test.rb +62 -0
  71. data/test/components/label_test.rb +70 -41
  72. data/test/form_builder/association_test.rb +85 -37
  73. data/test/form_builder/button_test.rb +11 -10
  74. data/test/form_builder/error_notification_test.rb +2 -1
  75. data/test/form_builder/error_test.rb +146 -33
  76. data/test/form_builder/general_test.rb +183 -81
  77. data/test/form_builder/hint_test.rb +24 -18
  78. data/test/form_builder/input_field_test.rb +105 -75
  79. data/test/form_builder/label_test.rb +68 -13
  80. data/test/form_builder/wrapper_test.rb +197 -22
  81. data/test/generators/simple_form_generator_test.rb +8 -7
  82. data/test/inputs/boolean_input_test.rb +97 -6
  83. data/test/inputs/collection_check_boxes_input_test.rb +117 -25
  84. data/test/inputs/collection_radio_buttons_input_test.rb +176 -54
  85. data/test/inputs/collection_select_input_test.rb +189 -77
  86. data/test/inputs/color_input_test.rb +10 -0
  87. data/test/inputs/datetime_input_test.rb +121 -50
  88. data/test/inputs/disabled_test.rb +29 -15
  89. data/test/inputs/discovery_test.rb +79 -6
  90. data/test/inputs/file_input_test.rb +3 -2
  91. data/test/inputs/general_test.rb +23 -22
  92. data/test/inputs/grouped_collection_select_input_test.rb +54 -17
  93. data/test/inputs/hidden_input_test.rb +5 -4
  94. data/test/inputs/numeric_input_test.rb +48 -44
  95. data/test/inputs/priority_input_test.rb +17 -16
  96. data/test/inputs/readonly_test.rb +20 -19
  97. data/test/inputs/required_test.rb +58 -13
  98. data/test/inputs/rich_text_area_input_test.rb +15 -0
  99. data/test/inputs/string_input_test.rb +58 -36
  100. data/test/inputs/text_input_test.rb +20 -7
  101. data/test/simple_form_test.rb +9 -0
  102. data/test/support/discovery_inputs.rb +40 -2
  103. data/test/support/misc_helpers.rb +113 -5
  104. data/test/support/mock_controller.rb +7 -1
  105. data/test/support/models.rb +162 -39
  106. data/test/test_helper.rb +19 -4
  107. metadata +51 -43
@@ -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,30 @@ 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 :rich_text_area, to: SimpleForm::Inputs::RichTextAreaInput
30
+ map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
31
+ map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
32
+ map_type :date, :time, :datetime, to: SimpleForm::Inputs::DateTimeInput
33
+ map_type :country, :time_zone, to: SimpleForm::Inputs::PriorityInput
34
+ map_type :boolean, to: SimpleForm::Inputs::BooleanInput
35
+ map_type :hidden, to: SimpleForm::Inputs::HiddenInput
33
36
 
34
37
  def self.discovery_cache
35
38
  @discovery_cache ||= {}
@@ -37,6 +40,7 @@ module SimpleForm
37
40
 
38
41
  def initialize(*) #:nodoc:
39
42
  super
43
+ @object = convert_to_model(@object)
40
44
  @defaults = options[:defaults]
41
45
  @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
42
46
  end
@@ -47,6 +51,11 @@ module SimpleForm
47
51
  # label + input + hint (when defined) + errors (when exists), and all can
48
52
  # be configured inside a wrapper html.
49
53
  #
54
+ # If a block is given, the contents of the block will replace the input
55
+ # field that would otherwise be generated automatically. The content will
56
+ # be given a label and wrapper div to make it consistent with the other
57
+ # elements in the form.
58
+ #
50
59
  # == Examples
51
60
  #
52
61
  # # Imagine @user has error "can't be blank" on name
@@ -106,18 +115,13 @@ module SimpleForm
106
115
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
107
116
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
108
117
  #
109
- def input(attribute_name, options={}, &block)
118
+ def input(attribute_name, options = {}, &block)
110
119
  options = @defaults.deep_dup.deep_merge(options) if @defaults
111
- input = find_input(attribute_name, options, &block)
112
120
 
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
121
+ input = find_input(attribute_name, options, &block)
122
+ wrapper = find_wrapper(input.input_type, options)
119
123
 
120
- chosen.render input
124
+ wrapper.render input
121
125
  end
122
126
  alias :attribute :input
123
127
 
@@ -135,14 +139,41 @@ module SimpleForm
135
139
  # <input class="string required" id="user_name" maxlength="100"
136
140
  # name="user[name]" type="text" value="Carlos" />
137
141
  #
138
- def input_field(attribute_name, options={})
139
- components = (wrapper.components & ATTRIBUTE_COMPONENTS)
142
+ # It also support validation classes once it is configured.
143
+ #
144
+ # # config/initializers/simple_form.rb
145
+ # SimpleForm.setup do |config|
146
+ # config.input_field_valid_class = 'is-valid'
147
+ # config.input_field_error_class = 'is-invalid'
148
+ # end
149
+ #
150
+ # simple_form_for @user do |f|
151
+ # f.input_field :name
152
+ # end
153
+ #
154
+ # When the validation happens, the input will be rendered with
155
+ # the class configured according to the validation:
156
+ #
157
+ # - when the input is valid:
158
+ #
159
+ # <input class="is-valid string required" id="user_name" value="Carlos" />
160
+ #
161
+ # - when the input is invalid:
162
+ #
163
+ # <input class="is-invalid string required" id="user_name" value="" />
164
+ #
165
+ def input_field(attribute_name, options = {})
166
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
140
167
 
141
168
  options = options.dup
142
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *components)
169
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components)
143
170
  options = @defaults.deep_dup.deep_merge(options) if @defaults
144
171
 
145
- SimpleForm::Wrappers::Root.new(components + [:input], wrapper: false).render find_input(attribute_name, options)
172
+ input = find_input(attribute_name, options)
173
+ wrapper = find_wrapper(input.input_type, options)
174
+ components = build_input_field_components(components.push(:input))
175
+
176
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
146
177
  end
147
178
 
148
179
  # Helper for dealing with association selects/radios, generating the
@@ -173,7 +204,7 @@ module SimpleForm
173
204
  #
174
205
  # Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
175
206
  #
176
- def association(association, options={}, &block)
207
+ def association(association, options = {}, &block)
177
208
  options = options.dup
178
209
 
179
210
  return simple_fields_for(*[association,
@@ -185,31 +216,9 @@ module SimpleForm
185
216
  raise "Association #{association.inspect} not found" unless reflection
186
217
 
187
218
  options[:as] ||= :select
188
- options[:collection] ||= options.fetch(:collection) {
189
- conditions = reflection.options[:conditions]
190
- conditions = conditions.call if conditions.respond_to?(:call)
191
- reflection.klass.where(conditions).order(reflection.options[:order])
192
- }
193
-
194
- attribute = case reflection.macro
195
- when :belongs_to
196
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
197
- when :has_one
198
- raise ArgumentError, ":has_one associations are not supported by f.association"
199
- else
200
- if options[:as] == :select
201
- html_options = options[:input_html] ||= {}
202
- html_options[:multiple] = true unless html_options.key?(:multiple)
203
- end
219
+ options[:collection] ||= fetch_association_collection(reflection, options)
204
220
 
205
- # Force the association to be preloaded for performance.
206
- if options[:preload] != false && object.respond_to?(association)
207
- target = object.send(association)
208
- target.to_a if target.respond_to?(:to_a)
209
- end
210
-
211
- :"#{reflection.name.to_s.singularize}_ids"
212
- end
221
+ attribute = build_association_attribute(reflection, association, options)
213
222
 
214
223
  input(attribute, options.merge(reflection: reflection))
215
224
  end
@@ -229,8 +238,8 @@ module SimpleForm
229
238
  options = args.extract_options!.dup
230
239
  options[:class] = [SimpleForm.button_class, options[:class]].compact
231
240
  args << options
232
- if respond_to?("#{type}_button")
233
- send("#{type}_button", *args, &block)
241
+ if respond_to?(:"#{type}_button")
242
+ send(:"#{type}_button", *args, &block)
234
243
  else
235
244
  send(type, *args, &block)
236
245
  end
@@ -244,7 +253,7 @@ module SimpleForm
244
253
  # f.error :name
245
254
  # f.error :name, id: "cool_error"
246
255
  #
247
- def error(attribute_name, options={})
256
+ def error(attribute_name, options = {})
248
257
  options = options.dup
249
258
 
250
259
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -261,7 +270,7 @@ module SimpleForm
261
270
  #
262
271
  # f.full_error :token #=> <span class="error">Token is invalid</span>
263
272
  #
264
- def full_error(attribute_name, options={})
273
+ def full_error(attribute_name, options = {})
265
274
  options = options.dup
266
275
 
267
276
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -283,7 +292,7 @@ module SimpleForm
283
292
  # f.hint :name, id: "cool_hint"
284
293
  # f.hint "Don't forget to accept this"
285
294
  #
286
- def hint(attribute_name, options={})
295
+ def hint(attribute_name, options = {})
287
296
  options = options.dup
288
297
 
289
298
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -307,7 +316,7 @@ module SimpleForm
307
316
  #
308
317
  # f.label :name # Do I18n lookup
309
318
  # f.label :name, "Name" # Same behavior as Rails, do not add required tag
310
- # f.label :name, label: "Name" # Same as above, but adds required tag
319
+ # f.label :name, label: "Name" # Same as above, but adds required tag
311
320
  #
312
321
  # f.label :name, required: false
313
322
  # f.label :name, id: "cool_label"
@@ -316,7 +325,7 @@ module SimpleForm
316
325
  return super if args.first.is_a?(String) || block_given?
317
326
 
318
327
  options = args.extract_options!.dup
319
- options[:label_html] = options.except(:label, :required, :as)
328
+ options[:label_html] = options.except(:label, :label_text, :required, :as)
320
329
 
321
330
  column = find_attribute_column(attribute_name)
322
331
  input_type = default_input_type(attribute_name, column, options)
@@ -334,7 +343,7 @@ module SimpleForm
334
343
  # f.error_notification message: 'Something went wrong'
335
344
  # f.error_notification id: 'user_error_message', class: 'form_error'
336
345
  #
337
- def error_notification(options={})
346
+ def error_notification(options = {})
338
347
  SimpleForm::ErrorNotification.new(self, options).render
339
348
  end
340
349
 
@@ -466,15 +475,60 @@ module SimpleForm
466
475
  @lookup_action ||= begin
467
476
  action = template.controller && template.controller.action_name
468
477
  return unless action
469
- action = action.to_sym
478
+ action = action.to_s
470
479
  ACTIONS[action] || action
471
480
  end
472
481
  end
473
482
 
474
483
  private
475
484
 
485
+ def fetch_association_collection(reflection, options)
486
+ options.fetch(:collection) do
487
+ relation = reflection.klass.all
488
+
489
+ if reflection.respond_to?(:scope) && reflection.scope
490
+ if reflection.scope.parameters.any?
491
+ relation = reflection.klass.instance_exec(object, &reflection.scope)
492
+ else
493
+ relation = reflection.klass.instance_exec(&reflection.scope)
494
+ end
495
+ else
496
+ order = reflection.options[:order]
497
+ conditions = reflection.options[:conditions]
498
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
499
+
500
+ relation = relation.where(conditions) if relation.respond_to?(:where)
501
+ relation = relation.order(order) if relation.respond_to?(:order)
502
+ end
503
+
504
+ relation
505
+ end
506
+ end
507
+
508
+ def build_association_attribute(reflection, association, options)
509
+ case reflection.macro
510
+ when :belongs_to
511
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
512
+ when :has_one
513
+ raise ArgumentError, ":has_one associations are not supported by f.association"
514
+ else
515
+ if options[:as] == :select || options[:as] == :grouped_select
516
+ html_options = options[:input_html] ||= {}
517
+ html_options[:multiple] = true unless html_options.key?(:multiple)
518
+ end
519
+
520
+ # Force the association to be preloaded for performance.
521
+ if options[:preload] != false && object.respond_to?(association)
522
+ target = object.send(association)
523
+ target.to_a if target.respond_to?(:to_a)
524
+ end
525
+
526
+ :"#{reflection.name.to_s.singularize}_ids"
527
+ end
528
+ end
529
+
476
530
  # Find an input based on the attribute name.
477
- def find_input(attribute_name, options={}, &block) #:nodoc:
531
+ def find_input(attribute_name, options = {}, &block)
478
532
  column = find_attribute_column(attribute_name)
479
533
  input_type = default_input_type(attribute_name, column, options)
480
534
 
@@ -486,25 +540,25 @@ module SimpleForm
486
540
  end
487
541
 
488
542
  # Attempt to guess the better input type given the defined options. By
489
- # default alwayls fallback to the user :as option, or to a :select when a
543
+ # default always fallback to the user :as option, or to a :select when a
490
544
  # collection is given.
491
- def default_input_type(attribute_name, column, options) #:nodoc:
545
+ def default_input_type(attribute_name, column, options)
492
546
  return options[:as].to_sym if options[:as]
493
- return :select if options[:collection]
494
547
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
548
+ return :select if options[:collection]
495
549
 
496
550
  input_type = column.try(:type)
497
551
  case input_type
498
552
  when :timestamp
499
553
  :datetime
500
- when :string, nil
554
+ when :string, :citext, nil
501
555
  case attribute_name.to_s
502
- when /password/ then :password
503
- when /time_zone/ then :time_zone
504
- when /country/ then :country
505
- when /email/ then :email
506
- when /phone/ then :tel
507
- when /url/ then :url
556
+ when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password
557
+ when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone
558
+ when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country
559
+ when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email
560
+ when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel
561
+ when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url
508
562
  else
509
563
  file_method?(attribute_name) ? :file : (input_type || :string)
510
564
  end
@@ -513,24 +567,45 @@ module SimpleForm
513
567
  end
514
568
  end
515
569
 
516
- def find_custom_type(attribute_name) #:nodoc:
570
+ def find_custom_type(attribute_name)
517
571
  SimpleForm.input_mappings.find { |match, type|
518
572
  attribute_name =~ match
519
573
  }.try(:last) if SimpleForm.input_mappings
520
574
  end
521
575
 
522
- def file_method?(attribute_name) #:nodoc:
523
- file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
524
- file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
576
+ # Internal: Try to discover whether an attribute corresponds to a file or not.
577
+ #
578
+ # Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in.
579
+ # This method tries to guess if an attribute belongs to some of these Gems by checking the presence
580
+ # of their methods using `#respond_to?`.
581
+ #
582
+ # Note: This does not support multiple file upload inputs, as this is very application-specific.
583
+ #
584
+ # The order here was chosen based on the popularity of Gems:
585
+ #
586
+ # - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0`
587
+ # - `remote_#{attribute_name}_url` - Refile >= `0.3.0` and CarrierWave >= `0.2.2`
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.
592
+ def file_method?(attribute_name)
593
+ @object.respond_to?("#{attribute_name}_attachment") ||
594
+ @object.respond_to?("#{attribute_name}_attachments") ||
595
+ @object.respond_to?("remote_#{attribute_name}_url") ||
596
+ @object.respond_to?("#{attribute_name}_attacher") ||
597
+ @object.respond_to?("#{attribute_name}_file_name")
525
598
  end
526
599
 
527
- def find_attribute_column(attribute_name) #:nodoc:
528
- if @object.respond_to?(:column_for_attribute)
600
+ def find_attribute_column(attribute_name)
601
+ if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
602
+ @object.type_for_attribute(attribute_name.to_s)
603
+ elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
529
604
  @object.column_for_attribute(attribute_name)
530
605
  end
531
606
  end
532
607
 
533
- def find_association_reflection(association) #:nodoc:
608
+ def find_association_reflection(association)
534
609
  if @object.class.respond_to?(:reflect_on_association)
535
610
  @object.class.reflect_on_association(association)
536
611
  end
@@ -543,24 +618,42 @@ module SimpleForm
543
618
  # b) Or use the found mapping
544
619
  # 2) If not, fallbacks to #{input_type}Input
545
620
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
546
- def find_mapping(input_type) #:nodoc:
621
+ def find_mapping(input_type)
547
622
  discovery_cache[input_type] ||=
548
623
  if mapping = self.class.mappings[input_type]
549
624
  mapping_override(mapping) || mapping
550
625
  else
551
626
  camelized = "#{input_type.to_s.camelize}Input"
552
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
627
+ attempt_mapping_with_custom_namespace(camelized) ||
628
+ attempt_mapping(camelized, Object) ||
629
+ attempt_mapping(camelized, self.class) ||
553
630
  raise("No input found for #{input_type}")
554
631
  end
555
632
  end
556
633
 
557
- def find_wrapper_mapping(input_type) #:nodoc:
558
- SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
634
+ # Attempts to find a wrapper mapping. It follows the following rules:
635
+ #
636
+ # 1) It tries to find a wrapper for the current form
637
+ # 2) If not, it tries to find a config
638
+ def find_wrapper_mapping(input_type)
639
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
640
+ options[:wrapper_mappings][input_type]
641
+ else
642
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
643
+ end
644
+ end
645
+
646
+ def find_wrapper(input_type, options)
647
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
648
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
649
+ else
650
+ wrapper
651
+ end
559
652
  end
560
653
 
561
654
  # If cache_discovery is enabled, use the class level cache that persists
562
655
  # between requests, otherwise use the instance one.
563
- def discovery_cache #:nodoc:
656
+ def discovery_cache
564
657
  if SimpleForm.cache_discovery
565
658
  self.class.discovery_cache
566
659
  else
@@ -568,14 +661,16 @@ module SimpleForm
568
661
  end
569
662
  end
570
663
 
571
- def mapping_override(klass) #:nodoc:
664
+ def mapping_override(klass)
572
665
  name = klass.name
573
666
  if name =~ /^SimpleForm::Inputs/
574
- attempt_mapping name.split("::").last, Object
667
+ input_name = name.split("::").last
668
+ attempt_mapping_with_custom_namespace(input_name) ||
669
+ attempt_mapping(input_name, Object)
575
670
  end
576
671
  end
577
672
 
578
- def attempt_mapping(mapping, at) #:nodoc:
673
+ def attempt_mapping(mapping, at)
579
674
  return if SimpleForm.inputs_discovery == false && at == Object
580
675
 
581
676
  begin
@@ -584,5 +679,41 @@ module SimpleForm
584
679
  raise if e.message !~ /#{mapping}$/
585
680
  end
586
681
  end
682
+
683
+ def attempt_mapping_with_custom_namespace(input_name)
684
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
685
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
686
+ return mapping
687
+ end
688
+ end
689
+
690
+ nil
691
+ end
692
+
693
+ def build_input_field_components(components)
694
+ components.map do |component|
695
+ if component == :input
696
+ SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
697
+ else
698
+ SimpleForm::Wrappers::Leaf.new(component)
699
+ end
700
+ end
701
+ end
702
+
703
+ def build_input_field_options
704
+ input_field_options = {}
705
+ valid_class = SimpleForm.input_field_valid_class
706
+ error_class = SimpleForm.input_field_error_class
707
+
708
+ if error_class.present?
709
+ input_field_options[:error_class] = error_class
710
+ end
711
+
712
+ if valid_class.present?
713
+ input_field_options[:valid_class] = valid_class
714
+ end
715
+
716
+ input_field_options
717
+ end
587
718
  end
588
719
  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