mini-sharp-mod 0.0.1

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 (70) hide show
  1. checksums.yaml +7 -0
  2. data/mini-sharp-mod.gemspec +12 -0
  3. data/simple_form-5.4.1/CHANGELOG.md +259 -0
  4. data/simple_form-5.4.1/MIT-LICENSE +21 -0
  5. data/simple_form-5.4.1/README.md +1334 -0
  6. data/simple_form-5.4.1/lib/generators/simple_form/USAGE +3 -0
  7. data/simple_form-5.4.1/lib/generators/simple_form/install_generator.rb +42 -0
  8. data/simple_form-5.4.1/lib/generators/simple_form/templates/README +10 -0
  9. data/simple_form-5.4.1/lib/generators/simple_form/templates/_form.html.erb +15 -0
  10. data/simple_form-5.4.1/lib/generators/simple_form/templates/_form.html.haml +12 -0
  11. data/simple_form-5.4.1/lib/generators/simple_form/templates/_form.html.slim +11 -0
  12. data/simple_form-5.4.1/lib/generators/simple_form/templates/config/initializers/simple_form.rb +176 -0
  13. data/simple_form-5.4.1/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +372 -0
  14. data/simple_form-5.4.1/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +122 -0
  15. data/simple_form-5.4.1/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +31 -0
  16. data/simple_form-5.4.1/lib/simple_form/action_view_extensions/builder.rb +38 -0
  17. data/simple_form-5.4.1/lib/simple_form/action_view_extensions/form_helper.rb +72 -0
  18. data/simple_form-5.4.1/lib/simple_form/components/errors.rb +74 -0
  19. data/simple_form-5.4.1/lib/simple_form/components/hints.rb +24 -0
  20. data/simple_form-5.4.1/lib/simple_form/components/html5.rb +33 -0
  21. data/simple_form-5.4.1/lib/simple_form/components/label_input.rb +34 -0
  22. data/simple_form-5.4.1/lib/simple_form/components/labels.rb +88 -0
  23. data/simple_form-5.4.1/lib/simple_form/components/maxlength.rb +35 -0
  24. data/simple_form-5.4.1/lib/simple_form/components/min_max.rb +41 -0
  25. data/simple_form-5.4.1/lib/simple_form/components/minlength.rb +35 -0
  26. data/simple_form-5.4.1/lib/simple_form/components/pattern.rb +27 -0
  27. data/simple_form-5.4.1/lib/simple_form/components/placeholders.rb +17 -0
  28. data/simple_form-5.4.1/lib/simple_form/components/readonly.rb +23 -0
  29. data/simple_form-5.4.1/lib/simple_form/components.rb +24 -0
  30. data/simple_form-5.4.1/lib/simple_form/error_notification.rb +49 -0
  31. data/simple_form-5.4.1/lib/simple_form/form_builder.rb +723 -0
  32. data/simple_form-5.4.1/lib/simple_form/helpers/autofocus.rb +12 -0
  33. data/simple_form-5.4.1/lib/simple_form/helpers/disabled.rb +16 -0
  34. data/simple_form-5.4.1/lib/simple_form/helpers/readonly.rb +16 -0
  35. data/simple_form-5.4.1/lib/simple_form/helpers/required.rb +36 -0
  36. data/simple_form-5.4.1/lib/simple_form/helpers/validators.rb +66 -0
  37. data/simple_form-5.4.1/lib/simple_form/helpers.rb +13 -0
  38. data/simple_form-5.4.1/lib/simple_form/inputs/base.rb +232 -0
  39. data/simple_form-5.4.1/lib/simple_form/inputs/block_input.rb +15 -0
  40. data/simple_form-5.4.1/lib/simple_form/inputs/boolean_input.rb +108 -0
  41. data/simple_form-5.4.1/lib/simple_form/inputs/collection_check_boxes_input.rb +22 -0
  42. data/simple_form-5.4.1/lib/simple_form/inputs/collection_input.rb +122 -0
  43. data/simple_form-5.4.1/lib/simple_form/inputs/collection_radio_buttons_input.rb +59 -0
  44. data/simple_form-5.4.1/lib/simple_form/inputs/collection_select_input.rb +17 -0
  45. data/simple_form-5.4.1/lib/simple_form/inputs/color_input.rb +14 -0
  46. data/simple_form-5.4.1/lib/simple_form/inputs/date_time_input.rb +39 -0
  47. data/simple_form-5.4.1/lib/simple_form/inputs/file_input.rb +12 -0
  48. data/simple_form-5.4.1/lib/simple_form/inputs/grouped_collection_select_input.rb +54 -0
  49. data/simple_form-5.4.1/lib/simple_form/inputs/hidden_input.rb +20 -0
  50. data/simple_form-5.4.1/lib/simple_form/inputs/numeric_input.rb +20 -0
  51. data/simple_form-5.4.1/lib/simple_form/inputs/password_input.rb +14 -0
  52. data/simple_form-5.4.1/lib/simple_form/inputs/priority_input.rb +37 -0
  53. data/simple_form-5.4.1/lib/simple_form/inputs/range_input.rb +15 -0
  54. data/simple_form-5.4.1/lib/simple_form/inputs/rich_text_area_input.rb +14 -0
  55. data/simple_form-5.4.1/lib/simple_form/inputs/string_input.rb +25 -0
  56. data/simple_form-5.4.1/lib/simple_form/inputs/text_input.rb +14 -0
  57. data/simple_form-5.4.1/lib/simple_form/inputs/weekday_input.rb +14 -0
  58. data/simple_form-5.4.1/lib/simple_form/inputs.rb +27 -0
  59. data/simple_form-5.4.1/lib/simple_form/map_type.rb +17 -0
  60. data/simple_form-5.4.1/lib/simple_form/railtie.rb +19 -0
  61. data/simple_form-5.4.1/lib/simple_form/tags.rb +73 -0
  62. data/simple_form-5.4.1/lib/simple_form/version.rb +4 -0
  63. data/simple_form-5.4.1/lib/simple_form/wrappers/builder.rb +80 -0
  64. data/simple_form-5.4.1/lib/simple_form/wrappers/leaf.rb +29 -0
  65. data/simple_form-5.4.1/lib/simple_form/wrappers/many.rb +74 -0
  66. data/simple_form-5.4.1/lib/simple_form/wrappers/root.rb +43 -0
  67. data/simple_form-5.4.1/lib/simple_form/wrappers/single.rb +27 -0
  68. data/simple_form-5.4.1/lib/simple_form/wrappers.rb +10 -0
  69. data/simple_form-5.4.1/lib/simple_form.rb +340 -0
  70. metadata +109 -0
@@ -0,0 +1,723 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support/core_ext/object/deep_dup'
3
+ require 'simple_form/map_type'
4
+ require 'simple_form/tags'
5
+
6
+ module SimpleForm
7
+ class FormBuilder < ActionView::Helpers::FormBuilder
8
+ attr_reader :template, :object_name, :object, :wrapper
9
+
10
+ # When action is create or update, we still should use new and edit
11
+ ACTIONS = {
12
+ 'create' => 'new',
13
+ 'update' => 'edit'
14
+ }
15
+
16
+ ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
17
+
18
+ extend MapType
19
+ include SimpleForm::Inputs
20
+
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
36
+
37
+ def self.discovery_cache
38
+ @discovery_cache ||= {}
39
+ end
40
+
41
+ def initialize(*) #:nodoc:
42
+ super
43
+ @object = convert_to_model(@object)
44
+ @defaults = options[:defaults]
45
+ @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
46
+ end
47
+
48
+ # Basic input helper, combines all components in the stack to generate
49
+ # input html based on options the user define and some guesses through
50
+ # database column information. By default a call to input will generate
51
+ # label + input + hint (when defined) + errors (when exists), and all can
52
+ # be configured inside a wrapper html.
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
+ #
59
+ # == Examples
60
+ #
61
+ # # Imagine @user has error "can't be blank" on name
62
+ # simple_form_for @user do |f|
63
+ # f.input :name, hint: 'My hint'
64
+ # end
65
+ #
66
+ # This is the output html (only the input portion, not the form):
67
+ #
68
+ # <label class="string required" for="user_name">
69
+ # <abbr title="required">*</abbr> Super User Name!
70
+ # </label>
71
+ # <input class="string required" id="user_name" maxlength="100"
72
+ # name="user[name]" type="text" value="Carlos" />
73
+ # <span class="hint">My hint</span>
74
+ # <span class="error">can't be blank</span>
75
+ #
76
+ # Each database type will render a default input, based on some mappings and
77
+ # heuristic to determine which is the best option.
78
+ #
79
+ # You have some options for the input to enable/disable some functions:
80
+ #
81
+ # as: allows you to define the input type you want, for instance you
82
+ # can use it to generate a text field for a date column.
83
+ #
84
+ # required: defines whether this attribute is required or not. True
85
+ # by default.
86
+ #
87
+ # The fact SimpleForm is built in components allow the interface to be unified.
88
+ # So, for instance, if you need to disable :hint for a given input, you can pass
89
+ # hint: false. The same works for :error, :label and :wrapper.
90
+ #
91
+ # Besides the html for any component can be changed. So, if you want to change
92
+ # the label html you just need to give a hash to :label_html. To configure the
93
+ # input html, supply :input_html instead and so on.
94
+ #
95
+ # == Options
96
+ #
97
+ # Some inputs, as datetime, time and select allow you to give extra options, like
98
+ # prompt and/or include blank. Such options are given in plainly:
99
+ #
100
+ # f.input :created_at, include_blank: true
101
+ #
102
+ # == Collection
103
+ #
104
+ # When playing with collections (:radio_buttons, :check_boxes and :select
105
+ # inputs), you have three extra options:
106
+ #
107
+ # collection: use to determine the collection to generate the radio or select
108
+ #
109
+ # label_method: the method to apply on the array collection to get the label
110
+ #
111
+ # value_method: the method to apply on the array collection to get the value
112
+ #
113
+ # == Priority
114
+ #
115
+ # Some inputs, as :time_zone and :country accepts a :priority option. If none is
116
+ # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
117
+ #
118
+ def input(attribute_name, options = {}, &block)
119
+ options = @defaults.deep_dup.deep_merge(options) if @defaults
120
+
121
+ input = find_input(attribute_name, options, &block)
122
+ wrapper = find_wrapper(input.input_type, options)
123
+
124
+ wrapper.render input
125
+ end
126
+ alias :attribute :input
127
+
128
+ # Creates a input tag for the given attribute. All the given options
129
+ # are sent as :input_html.
130
+ #
131
+ # == Examples
132
+ #
133
+ # simple_form_for @user do |f|
134
+ # f.input_field :name
135
+ # end
136
+ #
137
+ # This is the output html (only the input portion, not the form):
138
+ #
139
+ # <input class="string required" id="user_name" maxlength="100"
140
+ # name="user[name]" type="text" value="Carlos" />
141
+ #
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)
167
+
168
+ options = options.dup
169
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :disabled, :label_method, :value_method, :prompt, *components)
170
+ options = @defaults.deep_dup.deep_merge(options) if @defaults
171
+
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
177
+ end
178
+
179
+ # Helper for dealing with association selects/radios, generating the
180
+ # collection automatically. It's just a wrapper to input, so all options
181
+ # supported in input are also supported by association. Some extra options
182
+ # can also be given:
183
+ #
184
+ # == Examples
185
+ #
186
+ # simple_form_for @user do |f|
187
+ # f.association :company # Company.all
188
+ # end
189
+ #
190
+ # f.association :company, collection: Company.all(order: 'name')
191
+ # # Same as using :order option, but overriding collection
192
+ #
193
+ # == Block
194
+ #
195
+ # When a block is given, association simple behaves as a proxy to
196
+ # simple_fields_for:
197
+ #
198
+ # f.association :company do |c|
199
+ # c.input :name
200
+ # c.input :type
201
+ # end
202
+ #
203
+ # From the options above, only :collection can also be supplied.
204
+ #
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.
206
+ #
207
+ def association(association, options = {}, &block)
208
+ options = options.dup
209
+
210
+ return simple_fields_for(*[association,
211
+ options.delete(:collection), options].compact, &block) if block_given?
212
+
213
+ raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
214
+
215
+ reflection = find_association_reflection(association)
216
+ raise "Association #{association.inspect} not found" unless reflection
217
+
218
+ options[:as] ||= :select
219
+ options[:collection] ||= fetch_association_collection(reflection, options)
220
+
221
+ attribute = build_association_attribute(reflection, association, options)
222
+
223
+ input(attribute, options.merge(reflection: reflection))
224
+ end
225
+
226
+ # Creates a button:
227
+ #
228
+ # form_for @user do |f|
229
+ # f.button :submit
230
+ # end
231
+ #
232
+ # It just acts as a proxy to method name given. We also alias original Rails
233
+ # button implementation (3.2 forward (to delegate to the original when
234
+ # calling `f.button :button`.
235
+ #
236
+ alias_method :button_button, :button
237
+ def button(type, *args, &block)
238
+ options = args.extract_options!.dup
239
+ options[:class] = [SimpleForm.button_class, options[:class]].compact
240
+ args << options
241
+ if respond_to?(:"#{type}_button")
242
+ send(:"#{type}_button", *args, &block)
243
+ else
244
+ send(type, *args, &block)
245
+ end
246
+ end
247
+
248
+ # Creates an error tag based on the given attribute, only when the attribute
249
+ # contains errors. All the given options are sent as :error_html.
250
+ #
251
+ # == Examples
252
+ #
253
+ # f.error :name
254
+ # f.error :name, id: "cool_error"
255
+ #
256
+ def error(attribute_name, options = {})
257
+ options = options.dup
258
+
259
+ options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
260
+ column = find_attribute_column(attribute_name)
261
+ input_type = default_input_type(attribute_name, column, options)
262
+ wrapper.find(:error).
263
+ render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
264
+ end
265
+
266
+ # Return the error but also considering its name. This is used
267
+ # when errors for a hidden field need to be shown.
268
+ #
269
+ # == Examples
270
+ #
271
+ # f.full_error :token #=> <span class="error">Token is invalid</span>
272
+ #
273
+ def full_error(attribute_name, options = {})
274
+ options = options.dup
275
+
276
+ options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
277
+ object.class.human_attribute_name(attribute_name.to_s, { base: object })
278
+ else
279
+ attribute_name.to_s.humanize
280
+ end
281
+
282
+ error(attribute_name, options)
283
+ end
284
+
285
+ # Creates a hint tag for the given attribute. Accepts a symbol indicating
286
+ # an attribute for I18n lookup or a string. All the given options are sent
287
+ # as :hint_html.
288
+ #
289
+ # == Examples
290
+ #
291
+ # f.hint :name # Do I18n lookup
292
+ # f.hint :name, id: "cool_hint"
293
+ # f.hint "Don't forget to accept this"
294
+ #
295
+ def hint(attribute_name, options = {})
296
+ options = options.dup
297
+
298
+ options[:hint_html] = options.except(:hint_tag, :hint)
299
+ if attribute_name.is_a?(String)
300
+ options[:hint] = attribute_name
301
+ attribute_name, column, input_type = nil, nil, nil
302
+ else
303
+ column = find_attribute_column(attribute_name)
304
+ input_type = default_input_type(attribute_name, column, options)
305
+ end
306
+
307
+ wrapper.find(:hint).
308
+ render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
309
+ end
310
+
311
+ # Creates a default label tag for the given attribute. You can give a label
312
+ # through the :label option or using i18n. All the given options are sent
313
+ # as :label_html.
314
+ #
315
+ # == Examples
316
+ #
317
+ # f.label :name # Do I18n lookup
318
+ # f.label :name, "Name" # Same behavior as Rails, do not add required tag
319
+ # f.label :name, label: "Name" # Same as above, but adds required tag
320
+ #
321
+ # f.label :name, required: false
322
+ # f.label :name, id: "cool_label"
323
+ #
324
+ def label(attribute_name, *args)
325
+ return super if args.first.is_a?(String) || block_given?
326
+
327
+ options = args.extract_options!.dup
328
+ options[:label_html] = options.except(:label, :label_text, :required, :as)
329
+
330
+ column = find_attribute_column(attribute_name)
331
+ input_type = default_input_type(attribute_name, column, options)
332
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
333
+ end
334
+
335
+ # Creates an error notification message that only appears when the form object
336
+ # has some error. You can give a specific message with the :message option,
337
+ # otherwise it will look for a message using I18n. All other options given are
338
+ # passed straight as html options to the html tag.
339
+ #
340
+ # == Examples
341
+ #
342
+ # f.error_notification
343
+ # f.error_notification message: 'Something went wrong'
344
+ # f.error_notification id: 'user_error_message', class: 'form_error'
345
+ #
346
+ def error_notification(options = {})
347
+ SimpleForm::ErrorNotification.new(self, options).render
348
+ end
349
+
350
+ # Create a collection of radio inputs for the attribute. Basically this
351
+ # helper will create a radio input associated with a label for each
352
+ # text/value option in the collection, using value_method and text_method
353
+ # to convert these text/value. You can give a symbol or a proc to both
354
+ # value_method and text_method, that will be evaluated for each item in
355
+ # the collection.
356
+ #
357
+ # == Examples
358
+ #
359
+ # form_for @user do |f|
360
+ # f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
361
+ # end
362
+ #
363
+ # <input id="user_options_true" name="user[options]" type="radio" value="true" />
364
+ # <label class="collection_radio_buttons" for="user_options_true">Yes</label>
365
+ # <input id="user_options_false" name="user[options]" type="radio" value="false" />
366
+ # <label class="collection_radio_buttons" for="user_options_false">No</label>
367
+ #
368
+ # It is also possible to give a block that should generate the radio +
369
+ # label. To wrap the radio with the label, for instance:
370
+ #
371
+ # form_for @user do |f|
372
+ # f.collection_radio_buttons(
373
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
374
+ # ) do |b|
375
+ # b.label { b.radio_button + b.text }
376
+ # end
377
+ # end
378
+ #
379
+ # == Options
380
+ #
381
+ # Collection radio accepts some extra options:
382
+ #
383
+ # * checked => the value that should be checked initially.
384
+ #
385
+ # * disabled => the value or values that should be disabled. Accepts a single
386
+ # item or an array of items.
387
+ #
388
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
389
+ #
390
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
391
+ #
392
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
393
+ #
394
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
395
+ #
396
+ # * a block => to generate the label + radio or any other component.
397
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
398
+ SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
399
+ end
400
+
401
+ # Creates a collection of check boxes for each item in the collection,
402
+ # associated with a clickable label. Use value_method and text_method to
403
+ # convert items in the collection for use as text/value in check boxes.
404
+ # You can give a symbol or a proc to both value_method and text_method,
405
+ # that will be evaluated for each item in the collection.
406
+ #
407
+ # == Examples
408
+ #
409
+ # form_for @user do |f|
410
+ # f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
411
+ # end
412
+ #
413
+ # <input name="user[options][]" type="hidden" value="" />
414
+ # <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
415
+ # <label class="collection_check_boxes" for="user_options_true">Yes</label>
416
+ # <input name="user[options][]" type="hidden" value="" />
417
+ # <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
418
+ # <label class="collection_check_boxes" for="user_options_false">No</label>
419
+ #
420
+ # It is also possible to give a block that should generate the check box +
421
+ # label. To wrap the check box with the label, for instance:
422
+ #
423
+ # form_for @user do |f|
424
+ # f.collection_check_boxes(
425
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
426
+ # ) do |b|
427
+ # b.label { b.check_box + b.text }
428
+ # end
429
+ # end
430
+ #
431
+ # == Options
432
+ #
433
+ # Collection check box accepts some extra options:
434
+ #
435
+ # * checked => the value or values that should be checked initially. Accepts
436
+ # a single item or an array of items. It overrides existing associations.
437
+ #
438
+ # * disabled => the value or values that should be disabled. Accepts a single
439
+ # item or an array of items.
440
+ #
441
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
442
+ #
443
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
444
+ # is ignored if the :collection_wrapper_tag option is blank.
445
+ #
446
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
447
+ #
448
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
449
+ #
450
+ # * a block => to generate the label + check box or any other component.
451
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
452
+ SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
453
+ end
454
+
455
+ # Extract the model names from the object_name mess, ignoring numeric and
456
+ # explicit child indexes.
457
+ #
458
+ # Example:
459
+ #
460
+ # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
461
+ # ["route", "blocks", "blocks_learning_object", "foo"]
462
+ #
463
+ def lookup_model_names #:nodoc:
464
+ @lookup_model_names ||= begin
465
+ child_index = options[:child_index]
466
+ names = object_name.to_s.scan(/(?!\d)\w+/).flatten
467
+ names.delete(child_index) if child_index
468
+ names.each { |name| name.gsub!('_attributes', '') }
469
+ names.freeze
470
+ end
471
+ end
472
+
473
+ # The action to be used in lookup.
474
+ def lookup_action #:nodoc:
475
+ @lookup_action ||= begin
476
+ action = template.controller && template.controller.action_name
477
+ return unless action
478
+ action = action.to_s
479
+ ACTIONS[action] || action
480
+ end
481
+ end
482
+
483
+ private
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) && conditions.present?
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
+
530
+ # Find an input based on the attribute name.
531
+ def find_input(attribute_name, options = {}, &block)
532
+ column = find_attribute_column(attribute_name)
533
+ input_type = default_input_type(attribute_name, column, options)
534
+
535
+ if block_given?
536
+ SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
537
+ else
538
+ find_mapping(input_type).new(self, attribute_name, column, input_type, options)
539
+ end
540
+ end
541
+
542
+ # Attempt to guess the better input type given the defined options. By
543
+ # default always fallback to the user :as option, or to a :select when a
544
+ # collection is given.
545
+ def default_input_type(attribute_name, column, options)
546
+ return options[:as].to_sym if options[:as]
547
+ custom_type = find_custom_type(attribute_name.to_s) and return custom_type
548
+ return :select if options[:collection]
549
+
550
+ input_type = column.try(:type)
551
+ case input_type
552
+ when :timestamp
553
+ :datetime
554
+ when :string, :citext, nil
555
+ case attribute_name.to_s
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
562
+ else
563
+ file_method?(attribute_name) ? :file : (input_type || :string)
564
+ end
565
+ else
566
+ input_type
567
+ end
568
+ end
569
+
570
+ def find_custom_type(attribute_name)
571
+ SimpleForm.input_mappings.find { |match, type|
572
+ attribute_name =~ match
573
+ }.try(:last) if SimpleForm.input_mappings
574
+ end
575
+
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")
598
+ end
599
+
600
+ def find_attribute_column(attribute_name)
601
+ if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
602
+ detected_type = @object.type_for_attribute(attribute_name.to_s)
603
+
604
+ # Some attributes like ActiveRecord::Encryption::EncryptedAttribute are detected
605
+ # as different type, in that case we need to use the original type
606
+ detected_type.respond_to?(:cast_type) ? detected_type.cast_type : detected_type
607
+ elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
608
+ @object.column_for_attribute(attribute_name)
609
+ end
610
+ end
611
+
612
+ def find_association_reflection(association)
613
+ if @object.class.respond_to?(:reflect_on_association)
614
+ @object.class.reflect_on_association(association)
615
+ end
616
+ end
617
+
618
+ # Attempts to find a mapping. It follows the following rules:
619
+ #
620
+ # 1) It tries to find a registered mapping, if succeeds:
621
+ # a) Try to find an alternative with the same name in the Object scope
622
+ # b) Or use the found mapping
623
+ # 2) If not, fallbacks to #{input_type}Input
624
+ # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
625
+ def find_mapping(input_type)
626
+ discovery_cache[input_type] ||=
627
+ if mapping = self.class.mappings[input_type]
628
+ mapping_override(mapping) || mapping
629
+ else
630
+ camelized = "#{input_type.to_s.camelize}Input"
631
+ attempt_mapping_with_custom_namespace(camelized) ||
632
+ attempt_mapping(camelized, Object) ||
633
+ attempt_mapping(camelized, self.class) ||
634
+ raise("No input found for #{input_type}")
635
+ end
636
+ end
637
+
638
+ # Attempts to find a wrapper mapping. It follows the following rules:
639
+ #
640
+ # 1) It tries to find a wrapper for the current form
641
+ # 2) If not, it tries to find a config
642
+ def find_wrapper_mapping(input_type)
643
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
644
+ options[:wrapper_mappings][input_type]
645
+ else
646
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
647
+ end
648
+ end
649
+
650
+ def find_wrapper(input_type, options)
651
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
652
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
653
+ else
654
+ wrapper
655
+ end
656
+ end
657
+
658
+ # If cache_discovery is enabled, use the class level cache that persists
659
+ # between requests, otherwise use the instance one.
660
+ def discovery_cache
661
+ if SimpleForm.cache_discovery
662
+ self.class.discovery_cache
663
+ else
664
+ @discovery_cache ||= {}
665
+ end
666
+ end
667
+
668
+ def mapping_override(klass)
669
+ name = klass.name
670
+ if name =~ /^SimpleForm::Inputs/
671
+ input_name = name.split("::").last
672
+ attempt_mapping_with_custom_namespace(input_name) ||
673
+ attempt_mapping(input_name, Object)
674
+ end
675
+ end
676
+
677
+ def attempt_mapping(mapping, at)
678
+ return if SimpleForm.inputs_discovery == false && at == Object
679
+
680
+ begin
681
+ at.const_get(mapping)
682
+ rescue NameError => e
683
+ raise unless e.message.include?(mapping)
684
+ end
685
+ end
686
+
687
+ def attempt_mapping_with_custom_namespace(input_name)
688
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
689
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
690
+ return mapping
691
+ end
692
+ end
693
+
694
+ nil
695
+ end
696
+
697
+ def build_input_field_components(components)
698
+ components.map do |component|
699
+ if component == :input
700
+ SimpleForm::Wrappers::Leaf.new(component, build_input_field_options)
701
+ else
702
+ SimpleForm::Wrappers::Leaf.new(component)
703
+ end
704
+ end
705
+ end
706
+
707
+ def build_input_field_options
708
+ input_field_options = {}
709
+ valid_class = SimpleForm.input_field_valid_class
710
+ error_class = SimpleForm.input_field_error_class
711
+
712
+ if error_class.present?
713
+ input_field_options[:error_class] = error_class
714
+ end
715
+
716
+ if valid_class.present?
717
+ input_field_options[:valid_class] = valid_class
718
+ end
719
+
720
+ input_field_options
721
+ end
722
+ end
723
+ end