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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +199 -33
- data/MIT-LICENSE +2 -1
- data/README.md +453 -128
- data/lib/generators/simple_form/install_generator.rb +4 -3
- data/lib/generators/simple_form/templates/README +3 -5
- data/lib/generators/simple_form/templates/_form.html.erb +2 -0
- data/lib/generators/simple_form/templates/_form.html.haml +2 -0
- data/lib/generators/simple_form/templates/_form.html.slim +1 -0
- data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +47 -16
- data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +418 -23
- data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +101 -5
- data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
- data/lib/simple_form/action_view_extensions/builder.rb +2 -0
- data/lib/simple_form/action_view_extensions/form_helper.rb +10 -3
- data/lib/simple_form/components/errors.rb +39 -6
- data/lib/simple_form/components/hints.rb +3 -2
- data/lib/simple_form/components/html5.rb +16 -5
- data/lib/simple_form/components/label_input.rb +21 -2
- data/lib/simple_form/components/labels.rb +22 -11
- data/lib/simple_form/components/maxlength.rb +9 -5
- data/lib/simple_form/components/min_max.rb +2 -1
- data/lib/simple_form/components/minlength.rb +38 -0
- data/lib/simple_form/components/pattern.rb +2 -1
- data/lib/simple_form/components/placeholders.rb +4 -3
- data/lib/simple_form/components/readonly.rb +2 -1
- data/lib/simple_form/components.rb +2 -0
- data/lib/simple_form/error_notification.rb +1 -0
- data/lib/simple_form/form_builder.rb +220 -89
- data/lib/simple_form/helpers/autofocus.rb +1 -0
- data/lib/simple_form/helpers/disabled.rb +1 -0
- data/lib/simple_form/helpers/readonly.rb +1 -0
- data/lib/simple_form/helpers/required.rb +1 -0
- data/lib/simple_form/helpers/validators.rb +2 -1
- data/lib/simple_form/helpers.rb +6 -5
- data/lib/simple_form/i18n_cache.rb +1 -0
- data/lib/simple_form/inputs/base.rb +62 -16
- data/lib/simple_form/inputs/block_input.rb +2 -1
- data/lib/simple_form/inputs/boolean_input.rb +40 -16
- data/lib/simple_form/inputs/collection_check_boxes_input.rb +3 -2
- data/lib/simple_form/inputs/collection_input.rb +37 -14
- data/lib/simple_form/inputs/collection_radio_buttons_input.rb +9 -13
- data/lib/simple_form/inputs/collection_select_input.rb +5 -2
- data/lib/simple_form/inputs/color_input.rb +14 -0
- data/lib/simple_form/inputs/date_time_input.rb +24 -9
- data/lib/simple_form/inputs/file_input.rb +5 -2
- data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
- data/lib/simple_form/inputs/hidden_input.rb +5 -2
- data/lib/simple_form/inputs/numeric_input.rb +6 -4
- data/lib/simple_form/inputs/password_input.rb +6 -3
- data/lib/simple_form/inputs/priority_input.rb +5 -6
- data/lib/simple_form/inputs/range_input.rb +2 -1
- data/lib/simple_form/inputs/rich_text_area_input.rb +12 -0
- data/lib/simple_form/inputs/string_input.rb +7 -4
- data/lib/simple_form/inputs/text_input.rb +6 -3
- data/lib/simple_form/inputs.rb +3 -0
- data/lib/simple_form/map_type.rb +1 -0
- data/lib/simple_form/railtie.rb +8 -0
- data/lib/simple_form/tags.rb +13 -2
- data/lib/simple_form/version.rb +2 -1
- data/lib/simple_form/wrappers/builder.rb +7 -6
- data/lib/simple_form/wrappers/leaf.rb +29 -0
- data/lib/simple_form/wrappers/many.rb +7 -6
- data/lib/simple_form/wrappers/root.rb +10 -3
- data/lib/simple_form/wrappers/single.rb +7 -4
- data/lib/simple_form/wrappers.rb +2 -0
- data/lib/simple_form.rb +137 -21
- data/test/action_view_extensions/builder_test.rb +64 -45
- data/test/action_view_extensions/form_helper_test.rb +36 -16
- data/test/components/custom_components_test.rb +62 -0
- data/test/components/label_test.rb +70 -41
- data/test/form_builder/association_test.rb +85 -37
- data/test/form_builder/button_test.rb +11 -10
- data/test/form_builder/error_notification_test.rb +2 -1
- data/test/form_builder/error_test.rb +146 -33
- data/test/form_builder/general_test.rb +183 -81
- data/test/form_builder/hint_test.rb +24 -18
- data/test/form_builder/input_field_test.rb +105 -75
- data/test/form_builder/label_test.rb +68 -13
- data/test/form_builder/wrapper_test.rb +197 -22
- data/test/generators/simple_form_generator_test.rb +8 -7
- data/test/inputs/boolean_input_test.rb +97 -6
- data/test/inputs/collection_check_boxes_input_test.rb +117 -25
- data/test/inputs/collection_radio_buttons_input_test.rb +176 -54
- data/test/inputs/collection_select_input_test.rb +189 -77
- data/test/inputs/color_input_test.rb +10 -0
- data/test/inputs/datetime_input_test.rb +121 -50
- data/test/inputs/disabled_test.rb +29 -15
- data/test/inputs/discovery_test.rb +79 -6
- data/test/inputs/file_input_test.rb +3 -2
- data/test/inputs/general_test.rb +23 -22
- data/test/inputs/grouped_collection_select_input_test.rb +54 -17
- data/test/inputs/hidden_input_test.rb +5 -4
- data/test/inputs/numeric_input_test.rb +48 -44
- data/test/inputs/priority_input_test.rb +17 -16
- data/test/inputs/readonly_test.rb +20 -19
- data/test/inputs/required_test.rb +58 -13
- data/test/inputs/rich_text_area_input_test.rb +15 -0
- data/test/inputs/string_input_test.rb +58 -36
- data/test/inputs/text_input_test.rb +20 -7
- data/test/simple_form_test.rb +9 -0
- data/test/support/discovery_inputs.rb +40 -2
- data/test/support/misc_helpers.rb +113 -5
- data/test/support/mock_controller.rb +7 -1
- data/test/support/models.rb +162 -39
- data/test/test_helper.rb +19 -4
- 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
|
12
|
-
update
|
12
|
+
'create' => 'new',
|
13
|
+
'update' => 'edit'
|
13
14
|
}
|
14
15
|
|
15
|
-
ATTRIBUTE_COMPONENTS = [
|
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,
|
21
|
-
map_type :file,
|
22
|
-
map_type :string, :email, :search, :tel, :url, to: SimpleForm::Inputs::StringInput
|
23
|
-
map_type :password,
|
24
|
-
map_type :integer, :decimal, :float,
|
25
|
-
map_type :range,
|
26
|
-
map_type :check_boxes,
|
27
|
-
map_type :radio_buttons,
|
28
|
-
map_type :
|
29
|
-
map_type :
|
30
|
-
map_type :
|
31
|
-
map_type :
|
32
|
-
map_type :
|
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
|
-
|
114
|
-
|
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
|
-
|
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
|
-
|
139
|
-
|
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
|
-
|
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
|
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
|
-
|
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"
|
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.
|
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)
|
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
|
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)
|
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)
|
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
|
-
|
523
|
-
|
524
|
-
|
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)
|
528
|
-
if @object.respond_to?(:
|
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)
|
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)
|
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
|
-
|
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
|
-
|
558
|
-
|
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
|
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)
|
664
|
+
def mapping_override(klass)
|
572
665
|
name = klass.name
|
573
666
|
if name =~ /^SimpleForm::Inputs/
|
574
|
-
|
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)
|
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 Validators
|
@@ -24,7 +25,7 @@ module SimpleForm
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def action_validator_match?(validator)
|
27
|
-
return true
|
28
|
+
return true unless validator.options.include?(:on)
|
28
29
|
|
29
30
|
case validator.options[:on]
|
30
31
|
when :save
|
data/lib/simple_form/helpers.rb
CHANGED
@@ -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,
|
7
|
-
autoload :Disabled,
|
8
|
-
autoload :Readonly,
|
9
|
-
autoload :Required,
|
10
|
-
autoload :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
|