simple_form 2.0.0 → 3.5.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.

Potentially problematic release.


This version of simple_form might be problematic. Click here for more details.

Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +97 -198
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +572 -296
  5. data/lib/generators/simple_form/install_generator.rb +17 -7
  6. data/lib/generators/simple_form/templates/README +3 -4
  7. data/lib/generators/simple_form/templates/_form.html.erb +1 -0
  8. data/lib/generators/simple_form/templates/_form.html.haml +1 -0
  9. data/lib/generators/simple_form/templates/config/initializers/{simple_form.rb.tt → simple_form.rb} +57 -63
  10. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +155 -0
  11. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +111 -0
  12. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +14 -7
  13. data/lib/simple_form/action_view_extensions/builder.rb +5 -305
  14. data/lib/simple_form/action_view_extensions/form_helper.rb +18 -20
  15. data/lib/simple_form/components/errors.rb +30 -3
  16. data/lib/simple_form/components/hints.rb +10 -3
  17. data/lib/simple_form/components/html5.rb +17 -3
  18. data/lib/simple_form/components/label_input.rb +21 -2
  19. data/lib/simple_form/components/labels.rb +16 -11
  20. data/lib/simple_form/components/maxlength.rb +19 -12
  21. data/lib/simple_form/components/min_max.rb +4 -2
  22. data/lib/simple_form/components/minlength.rb +48 -0
  23. data/lib/simple_form/components/pattern.rb +5 -4
  24. data/lib/simple_form/components/placeholders.rb +3 -2
  25. data/lib/simple_form/components/readonly.rb +3 -2
  26. data/lib/simple_form/components.rb +15 -11
  27. data/lib/simple_form/error_notification.rb +4 -3
  28. data/lib/simple_form/form_builder.rb +283 -105
  29. data/lib/simple_form/helpers/autofocus.rb +1 -0
  30. data/lib/simple_form/helpers/disabled.rb +1 -0
  31. data/lib/simple_form/helpers/readonly.rb +1 -0
  32. data/lib/simple_form/helpers/required.rb +1 -0
  33. data/lib/simple_form/helpers/validators.rb +4 -3
  34. data/lib/simple_form/helpers.rb +7 -6
  35. data/lib/simple_form/i18n_cache.rb +1 -0
  36. data/lib/simple_form/inputs/base.rb +76 -23
  37. data/lib/simple_form/inputs/block_input.rb +3 -2
  38. data/lib/simple_form/inputs/boolean_input.rb +55 -16
  39. data/lib/simple_form/inputs/collection_check_boxes_input.rb +2 -1
  40. data/lib/simple_form/inputs/collection_input.rb +41 -18
  41. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +11 -19
  42. data/lib/simple_form/inputs/collection_select_input.rb +5 -2
  43. data/lib/simple_form/inputs/date_time_input.rb +23 -12
  44. data/lib/simple_form/inputs/file_input.rb +5 -2
  45. data/lib/simple_form/inputs/grouped_collection_select_input.rb +16 -3
  46. data/lib/simple_form/inputs/hidden_input.rb +5 -2
  47. data/lib/simple_form/inputs/numeric_input.rb +4 -8
  48. data/lib/simple_form/inputs/password_input.rb +6 -4
  49. data/lib/simple_form/inputs/priority_input.rb +5 -2
  50. data/lib/simple_form/inputs/range_input.rb +2 -1
  51. data/lib/simple_form/inputs/string_input.rb +6 -4
  52. data/lib/simple_form/inputs/text_input.rb +6 -3
  53. data/lib/simple_form/inputs.rb +20 -17
  54. data/lib/simple_form/map_type.rb +1 -0
  55. data/lib/simple_form/railtie.rb +15 -0
  56. data/lib/simple_form/tags.rb +69 -0
  57. data/lib/simple_form/version.rb +2 -1
  58. data/lib/simple_form/wrappers/builder.rb +12 -35
  59. data/lib/simple_form/wrappers/leaf.rb +29 -0
  60. data/lib/simple_form/wrappers/many.rb +12 -7
  61. data/lib/simple_form/wrappers/root.rb +7 -4
  62. data/lib/simple_form/wrappers/single.rb +12 -3
  63. data/lib/simple_form/wrappers.rb +3 -1
  64. data/lib/simple_form.rb +118 -63
  65. data/test/action_view_extensions/builder_test.rb +230 -164
  66. data/test/action_view_extensions/form_helper_test.rb +107 -39
  67. data/test/components/label_test.rb +105 -87
  68. data/test/form_builder/association_test.rb +131 -62
  69. data/test/form_builder/button_test.rb +15 -14
  70. data/test/form_builder/error_notification_test.rb +11 -10
  71. data/test/form_builder/error_test.rb +188 -34
  72. data/test/form_builder/general_test.rb +247 -102
  73. data/test/form_builder/hint_test.rb +59 -32
  74. data/test/form_builder/input_field_test.rb +138 -25
  75. data/test/form_builder/label_test.rb +84 -13
  76. data/test/form_builder/wrapper_test.rb +236 -33
  77. data/test/generators/simple_form_generator_test.rb +15 -4
  78. data/test/inputs/boolean_input_test.rb +147 -13
  79. data/test/inputs/collection_check_boxes_input_test.rb +166 -71
  80. data/test/inputs/collection_radio_buttons_input_test.rb +229 -113
  81. data/test/inputs/collection_select_input_test.rb +222 -85
  82. data/test/inputs/datetime_input_test.rb +134 -47
  83. data/test/inputs/disabled_test.rb +62 -21
  84. data/test/inputs/discovery_test.rb +70 -10
  85. data/test/inputs/file_input_test.rb +4 -3
  86. data/test/inputs/general_test.rb +90 -26
  87. data/test/inputs/grouped_collection_select_input_test.rb +88 -23
  88. data/test/inputs/hidden_input_test.rb +7 -5
  89. data/test/inputs/numeric_input_test.rb +56 -46
  90. data/test/inputs/priority_input_test.rb +31 -16
  91. data/test/inputs/readonly_test.rb +68 -27
  92. data/test/inputs/required_test.rb +63 -18
  93. data/test/inputs/string_input_test.rb +76 -51
  94. data/test/inputs/text_input_test.rb +21 -8
  95. data/test/simple_form_test.rb +9 -0
  96. data/test/support/discovery_inputs.rb +39 -2
  97. data/test/support/misc_helpers.rb +176 -20
  98. data/test/support/mock_controller.rb +13 -7
  99. data/test/support/models.rb +187 -71
  100. data/test/test_helper.rb +38 -39
  101. metadata +53 -39
  102. data/lib/simple_form/core_ext/hash.rb +0 -16
  103. data/test/support/mock_response.rb +0 -14
@@ -1,4 +1,7 @@
1
- require 'simple_form/core_ext/hash'
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'
2
5
 
3
6
  module SimpleForm
4
7
  class FormBuilder < ActionView::Helpers::FormBuilder
@@ -6,26 +9,29 @@ module SimpleForm
6
9
 
7
10
  # When action is create or update, we still should use new and edit
8
11
  ACTIONS = {
9
- :create => :new,
10
- :update => :edit
12
+ 'create' => 'new',
13
+ 'update' => 'edit'
11
14
  }
12
15
 
16
+ ATTRIBUTE_COMPONENTS = %i[html5 min_max maxlength minlength placeholder pattern readonly]
17
+
13
18
  extend MapType
14
19
  include SimpleForm::Inputs
15
20
 
16
- map_type :text, :to => SimpleForm::Inputs::TextInput
17
- map_type :file, :to => SimpleForm::Inputs::FileInput
18
- map_type :string, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
19
- map_type :password, :to => SimpleForm::Inputs::PasswordInput
20
- map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
21
- map_type :range, :to => SimpleForm::Inputs::RangeInput
22
- map_type :check_boxes, :to => SimpleForm::Inputs::CollectionCheckBoxesInput
23
- map_type :radio_buttons, :to => SimpleForm::Inputs::CollectionRadioButtonsInput
24
- map_type :select, :to => SimpleForm::Inputs::CollectionSelectInput
25
- map_type :grouped_select, :to => SimpleForm::Inputs::GroupedCollectionSelectInput
26
- map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
27
- map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
28
- map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
21
+ map_type :text, to: SimpleForm::Inputs::TextInput
22
+ map_type :file, to: SimpleForm::Inputs::FileInput
23
+ map_type :string, :email, :search, :tel, :url, :uuid, 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
29
35
 
30
36
  def self.discovery_cache
31
37
  @discovery_cache ||= {}
@@ -33,6 +39,7 @@ module SimpleForm
33
39
 
34
40
  def initialize(*) #:nodoc:
35
41
  super
42
+ @object = convert_to_model(@object)
36
43
  @defaults = options[:defaults]
37
44
  @wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
38
45
  end
@@ -43,11 +50,16 @@ module SimpleForm
43
50
  # label + input + hint (when defined) + errors (when exists), and all can
44
51
  # be configured inside a wrapper html.
45
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
+ #
46
58
  # == Examples
47
59
  #
48
60
  # # Imagine @user has error "can't be blank" on name
49
61
  # simple_form_for @user do |f|
50
- # f.input :name, :hint => 'My hint'
62
+ # f.input :name, hint: 'My hint'
51
63
  # end
52
64
  #
53
65
  # This is the output html (only the input portion, not the form):
@@ -56,7 +68,7 @@ module SimpleForm
56
68
  # <abbr title="required">*</abbr> Super User Name!
57
69
  # </label>
58
70
  # <input class="string required" id="user_name" maxlength="100"
59
- # name="user[name]" size="100" type="text" value="Carlos" />
71
+ # name="user[name]" type="text" value="Carlos" />
60
72
  # <span class="hint">My hint</span>
61
73
  # <span class="error">can't be blank</span>
62
74
  #
@@ -65,15 +77,15 @@ module SimpleForm
65
77
  #
66
78
  # You have some options for the input to enable/disable some functions:
67
79
  #
68
- # :as => allows you to define the input type you want, for instance you
80
+ # as: allows you to define the input type you want, for instance you
69
81
  # can use it to generate a text field for a date column.
70
82
  #
71
- # :required => defines whether this attribute is required or not. True
83
+ # required: defines whether this attribute is required or not. True
72
84
  # by default.
73
85
  #
74
86
  # The fact SimpleForm is built in components allow the interface to be unified.
75
87
  # So, for instance, if you need to disable :hint for a given input, you can pass
76
- # :hint => false. The same works for :error, :label and :wrapper.
88
+ # hint: false. The same works for :error, :label and :wrapper.
77
89
  #
78
90
  # Besides the html for any component can be changed. So, if you want to change
79
91
  # the label html you just need to give a hash to :label_html. To configure the
@@ -84,35 +96,31 @@ module SimpleForm
84
96
  # Some inputs, as datetime, time and select allow you to give extra options, like
85
97
  # prompt and/or include blank. Such options are given in plainly:
86
98
  #
87
- # f.input :created_at, :include_blank => true
99
+ # f.input :created_at, include_blank: true
88
100
  #
89
101
  # == Collection
90
102
  #
91
103
  # When playing with collections (:radio_buttons, :check_boxes and :select
92
104
  # inputs), you have three extra options:
93
105
  #
94
- # :collection => use to determine the collection to generate the radio or select
106
+ # collection: use to determine the collection to generate the radio or select
95
107
  #
96
- # :label_method => the method to apply on the array collection to get the label
108
+ # label_method: the method to apply on the array collection to get the label
97
109
  #
98
- # :value_method => the method to apply on the array collection to get the value
110
+ # value_method: the method to apply on the array collection to get the value
99
111
  #
100
112
  # == Priority
101
113
  #
102
114
  # Some inputs, as :time_zone and :country accepts a :priority option. If none is
103
- # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
115
+ # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
104
116
  #
105
- def input(attribute_name, options={}, &block)
117
+ def input(attribute_name, options = {}, &block)
106
118
  options = @defaults.deep_dup.deep_merge(options) if @defaults
107
119
 
108
- chosen =
109
- if name = options[:wrapper]
110
- name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
111
- else
112
- wrapper
113
- end
120
+ input = find_input(attribute_name, options, &block)
121
+ wrapper = find_wrapper(input.input_type, options)
114
122
 
115
- chosen.render find_input(attribute_name, options, &block)
123
+ wrapper.render input
116
124
  end
117
125
  alias :attribute :input
118
126
 
@@ -128,12 +136,20 @@ module SimpleForm
128
136
  # This is the output html (only the input portion, not the form):
129
137
  #
130
138
  # <input class="string required" id="user_name" maxlength="100"
131
- # name="user[name]" size="100" type="text" value="Carlos" />
139
+ # name="user[name]" type="text" value="Carlos" />
132
140
  #
133
- def input_field(attribute_name, options={})
141
+ def input_field(attribute_name, options = {})
142
+ components = (wrapper.components.map(&:namespace) & ATTRIBUTE_COMPONENTS)
143
+
134
144
  options = options.dup
135
- options[:input_html] = options.except(:as, :collection, :label_method, :value_method)
136
- SimpleForm::Wrappers::Root.new([:input], :wrapper => false).render find_input(attribute_name, options)
145
+ options[:input_html] = options.except(:as, :boolean_style, :collection, :label_method, :value_method, :prompt, *components)
146
+ options = @defaults.deep_dup.deep_merge(options) if @defaults
147
+
148
+ input = find_input(attribute_name, options)
149
+ wrapper = find_wrapper(input.input_type, options)
150
+ components = components.concat([:input]).map { |component| SimpleForm::Wrappers::Leaf.new(component) }
151
+
152
+ SimpleForm::Wrappers::Root.new(components, wrapper.options.merge(wrapper: false)).render input
137
153
  end
138
154
 
139
155
  # Helper for dealing with association selects/radios, generating the
@@ -147,7 +163,7 @@ module SimpleForm
147
163
  # f.association :company # Company.all
148
164
  # end
149
165
  #
150
- # f.association :company, :collection => Company.all(:order => 'name')
166
+ # f.association :company, collection: Company.all(order: 'name')
151
167
  # # Same as using :order option, but overriding collection
152
168
  #
153
169
  # == Block
@@ -162,7 +178,9 @@ module SimpleForm
162
178
  #
163
179
  # From the options above, only :collection can also be supplied.
164
180
  #
165
- def association(association, options={}, &block)
181
+ # Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
182
+ #
183
+ def association(association, options = {}, &block)
166
184
  options = options.dup
167
185
 
168
186
  return simple_fields_for(*[association,
@@ -174,30 +192,11 @@ module SimpleForm
174
192
  raise "Association #{association.inspect} not found" unless reflection
175
193
 
176
194
  options[:as] ||= :select
177
- options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
178
-
179
- attribute = case reflection.macro
180
- when :belongs_to
181
- (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
182
- when :has_one
183
- raise ":has_one associations are not supported by f.association"
184
- else
185
- if options[:as] == :select
186
- html_options = options[:input_html] ||= {}
187
- html_options[:size] ||= 5
188
- html_options[:multiple] = true unless html_options.key?(:multiple)
189
- end
195
+ options[:collection] ||= fetch_association_collection(reflection, options)
190
196
 
191
- # Force the association to be preloaded for performance.
192
- if options[:preload] != false && object.respond_to?(association)
193
- target = object.send(association)
194
- target.to_a if target.respond_to?(:to_a)
195
- end
196
-
197
- :"#{reflection.name.to_s.singularize}_ids"
198
- end
197
+ attribute = build_association_attribute(reflection, association, options)
199
198
 
200
- input(attribute, options.merge(:reflection => reflection))
199
+ input(attribute, options.merge(reflection: reflection))
201
200
  end
202
201
 
203
202
  # Creates a button:
@@ -210,14 +209,13 @@ module SimpleForm
210
209
  # button implementation (3.2 forward (to delegate to the original when
211
210
  # calling `f.button :button`.
212
211
  #
213
- # TODO: remove if condition when supporting only Rails 3.2 forward.
214
- alias_method :button_button, :button if method_defined?(:button)
212
+ alias_method :button_button, :button
215
213
  def button(type, *args, &block)
216
214
  options = args.extract_options!.dup
217
215
  options[:class] = [SimpleForm.button_class, options[:class]].compact
218
216
  args << options
219
- if respond_to?("#{type}_button")
220
- send("#{type}_button", *args, &block)
217
+ if respond_to?(:"#{type}_button")
218
+ send(:"#{type}_button", *args, &block)
221
219
  else
222
220
  send(type, *args, &block)
223
221
  end
@@ -229,9 +227,9 @@ module SimpleForm
229
227
  # == Examples
230
228
  #
231
229
  # f.error :name
232
- # f.error :name, :id => "cool_error"
230
+ # f.error :name, id: "cool_error"
233
231
  #
234
- def error(attribute_name, options={})
232
+ def error(attribute_name, options = {})
235
233
  options = options.dup
236
234
 
237
235
  options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
@@ -248,7 +246,7 @@ module SimpleForm
248
246
  #
249
247
  # f.full_error :token #=> <span class="error">Token is invalid</span>
250
248
  #
251
- def full_error(attribute_name, options={})
249
+ def full_error(attribute_name, options = {})
252
250
  options = options.dup
253
251
 
254
252
  options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
@@ -267,10 +265,10 @@ module SimpleForm
267
265
  # == Examples
268
266
  #
269
267
  # f.hint :name # Do I18n lookup
270
- # f.hint :name, :id => "cool_hint"
268
+ # f.hint :name, id: "cool_hint"
271
269
  # f.hint "Don't forget to accept this"
272
270
  #
273
- def hint(attribute_name, options={})
271
+ def hint(attribute_name, options = {})
274
272
  options = options.dup
275
273
 
276
274
  options[:hint_html] = options.except(:hint_tag, :hint)
@@ -294,16 +292,16 @@ module SimpleForm
294
292
  #
295
293
  # f.label :name # Do I18n lookup
296
294
  # f.label :name, "Name" # Same behavior as Rails, do not add required tag
297
- # f.label :name, :label => "Name" # Same as above, but adds required tag
295
+ # f.label :name, label: "Name" # Same as above, but adds required tag
298
296
  #
299
- # f.label :name, :required => false
300
- # f.label :name, :id => "cool_label"
297
+ # f.label :name, required: false
298
+ # f.label :name, id: "cool_label"
301
299
  #
302
300
  def label(attribute_name, *args)
303
301
  return super if args.first.is_a?(String) || block_given?
304
302
 
305
303
  options = args.extract_options!.dup
306
- options[:label_html] = options.except(:label, :required)
304
+ options[:label_html] = options.except(:label, :label_text, :required, :as)
307
305
 
308
306
  column = find_attribute_column(attribute_name)
309
307
  input_type = default_input_type(attribute_name, column, options)
@@ -318,13 +316,118 @@ module SimpleForm
318
316
  # == Examples
319
317
  #
320
318
  # f.error_notification
321
- # f.error_notification :message => 'Something went wrong'
322
- # f.error_notification :id => 'user_error_message', :class => 'form_error'
319
+ # f.error_notification message: 'Something went wrong'
320
+ # f.error_notification id: 'user_error_message', class: 'form_error'
323
321
  #
324
- def error_notification(options={})
322
+ def error_notification(options = {})
325
323
  SimpleForm::ErrorNotification.new(self, options).render
326
324
  end
327
325
 
326
+ # Create a collection of radio inputs for the attribute. Basically this
327
+ # helper will create a radio input associated with a label for each
328
+ # text/value option in the collection, using value_method and text_method
329
+ # to convert these text/value. You can give a symbol or a proc to both
330
+ # value_method and text_method, that will be evaluated for each item in
331
+ # the collection.
332
+ #
333
+ # == Examples
334
+ #
335
+ # form_for @user do |f|
336
+ # f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
337
+ # end
338
+ #
339
+ # <input id="user_options_true" name="user[options]" type="radio" value="true" />
340
+ # <label class="collection_radio_buttons" for="user_options_true">Yes</label>
341
+ # <input id="user_options_false" name="user[options]" type="radio" value="false" />
342
+ # <label class="collection_radio_buttons" for="user_options_false">No</label>
343
+ #
344
+ # It is also possible to give a block that should generate the radio +
345
+ # label. To wrap the radio with the label, for instance:
346
+ #
347
+ # form_for @user do |f|
348
+ # f.collection_radio_buttons(
349
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
350
+ # ) do |b|
351
+ # b.label { b.radio_button + b.text }
352
+ # end
353
+ # end
354
+ #
355
+ # == Options
356
+ #
357
+ # Collection radio accepts some extra options:
358
+ #
359
+ # * checked => the value that should be checked initially.
360
+ #
361
+ # * disabled => the value or values that should be disabled. Accepts a single
362
+ # item or an array of items.
363
+ #
364
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
365
+ #
366
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
367
+ #
368
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
369
+ #
370
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
371
+ #
372
+ # * a block => to generate the label + radio or any other component.
373
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
374
+ SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
375
+ end
376
+
377
+ # Creates a collection of check boxes for each item in the collection,
378
+ # associated with a clickable label. Use value_method and text_method to
379
+ # convert items in the collection for use as text/value in check boxes.
380
+ # You can give a symbol or a proc to both value_method and text_method,
381
+ # that will be evaluated for each item in the collection.
382
+ #
383
+ # == Examples
384
+ #
385
+ # form_for @user do |f|
386
+ # f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
387
+ # end
388
+ #
389
+ # <input name="user[options][]" type="hidden" value="" />
390
+ # <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
391
+ # <label class="collection_check_boxes" for="user_options_true">Yes</label>
392
+ # <input name="user[options][]" type="hidden" value="" />
393
+ # <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
394
+ # <label class="collection_check_boxes" for="user_options_false">No</label>
395
+ #
396
+ # It is also possible to give a block that should generate the check box +
397
+ # label. To wrap the check box with the label, for instance:
398
+ #
399
+ # form_for @user do |f|
400
+ # f.collection_check_boxes(
401
+ # :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
402
+ # ) do |b|
403
+ # b.label { b.check_box + b.text }
404
+ # end
405
+ # end
406
+ #
407
+ # == Options
408
+ #
409
+ # Collection check box accepts some extra options:
410
+ #
411
+ # * checked => the value or values that should be checked initially. Accepts
412
+ # a single item or an array of items. It overrides existing associations.
413
+ #
414
+ # * disabled => the value or values that should be disabled. Accepts a single
415
+ # item or an array of items.
416
+ #
417
+ # * collection_wrapper_tag => the tag to wrap the entire collection.
418
+ #
419
+ # * collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
420
+ # is ignored if the :collection_wrapper_tag option is blank.
421
+ #
422
+ # * item_wrapper_tag => the tag to wrap each item in the collection.
423
+ #
424
+ # * item_wrapper_class => the CSS class to use for item_wrapper_tag
425
+ #
426
+ # * a block => to generate the label + check box or any other component.
427
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
428
+ SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
429
+ end
430
+
328
431
  # Extract the model names from the object_name mess, ignoring numeric and
329
432
  # explicit child indexes.
330
433
  #
@@ -333,10 +436,10 @@ module SimpleForm
333
436
  # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
334
437
  # ["route", "blocks", "blocks_learning_object", "foo"]
335
438
  #
336
- def lookup_model_names
439
+ def lookup_model_names #:nodoc:
337
440
  @lookup_model_names ||= begin
338
441
  child_index = options[:child_index]
339
- names = object_name.to_s.scan(/([a-zA-Z_]+)/).flatten
442
+ names = object_name.to_s.scan(/(?!\d)\w+/).flatten
340
443
  names.delete(child_index) if child_index
341
444
  names.each { |name| name.gsub!('_attributes', '') }
342
445
  names.freeze
@@ -344,28 +447,67 @@ module SimpleForm
344
447
  end
345
448
 
346
449
  # The action to be used in lookup.
347
- def lookup_action
450
+ def lookup_action #:nodoc:
348
451
  @lookup_action ||= begin
349
- action = template.controller.action_name
452
+ action = template.controller && template.controller.action_name
350
453
  return unless action
351
- action = action.to_sym
454
+ action = action.to_s
352
455
  ACTIONS[action] || action
353
456
  end
354
457
  end
355
458
 
356
459
  private
357
460
 
461
+ def fetch_association_collection(reflection, options)
462
+ options.fetch(:collection) do
463
+ relation = reflection.klass.all
464
+
465
+ if reflection.respond_to?(:scope) && reflection.scope
466
+ if reflection.scope.parameters.any?
467
+ relation = reflection.klass.instance_exec(object, &reflection.scope)
468
+ else
469
+ relation = reflection.klass.instance_exec(&reflection.scope)
470
+ end
471
+ else
472
+ order = reflection.options[:order]
473
+ conditions = reflection.options[:conditions]
474
+ conditions = object.instance_exec(&conditions) if conditions.respond_to?(:call)
475
+
476
+ relation = relation.where(conditions) if relation.respond_to?(:where)
477
+ relation = relation.order(order) if relation.respond_to?(:order)
478
+ end
479
+
480
+ relation
481
+ end
482
+ end
483
+
484
+ def build_association_attribute(reflection, association, options)
485
+ case reflection.macro
486
+ when :belongs_to
487
+ (reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
488
+ when :has_one
489
+ raise ArgumentError, ":has_one associations are not supported by f.association"
490
+ else
491
+ if options[:as] == :select
492
+ html_options = options[:input_html] ||= {}
493
+ html_options[:multiple] = true unless html_options.key?(:multiple)
494
+ end
495
+
496
+ # Force the association to be preloaded for performance.
497
+ if options[:preload] != false && object.respond_to?(association)
498
+ target = object.send(association)
499
+ target.to_a if target.respond_to?(:to_a)
500
+ end
501
+
502
+ :"#{reflection.name.to_s.singularize}_ids"
503
+ end
504
+ end
505
+
358
506
  # Find an input based on the attribute name.
359
- def find_input(attribute_name, options={}, &block) #:nodoc:
507
+ def find_input(attribute_name, options = {}, &block)
360
508
  column = find_attribute_column(attribute_name)
361
509
  input_type = default_input_type(attribute_name, column, options)
362
510
 
363
- if input_type == :radio
364
- SimpleForm.deprecation_warn "Using `:as => :radio` as input type is " \
365
- "deprecated, please change it to `:as => :radio_buttons`."
366
- input_type = :radio_buttons
367
- end
368
-
369
511
  if block_given?
370
512
  SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
371
513
  else
@@ -374,12 +516,12 @@ module SimpleForm
374
516
  end
375
517
 
376
518
  # Attempt to guess the better input type given the defined options. By
377
- # default alwayls fallback to the user :as option, or to a :select when a
519
+ # default always fallback to the user :as option, or to a :select when a
378
520
  # collection is given.
379
- def default_input_type(attribute_name, column, options) #:nodoc:
521
+ def default_input_type(attribute_name, column, options)
380
522
  return options[:as].to_sym if options[:as]
381
- return :select if options[:collection]
382
523
  custom_type = find_custom_type(attribute_name.to_s) and return custom_type
524
+ return :select if options[:collection]
383
525
 
384
526
  input_type = column.try(:type)
385
527
  case input_type
@@ -401,24 +543,26 @@ module SimpleForm
401
543
  end
402
544
  end
403
545
 
404
- def find_custom_type(attribute_name) #:nodoc:
546
+ def find_custom_type(attribute_name)
405
547
  SimpleForm.input_mappings.find { |match, type|
406
548
  attribute_name =~ match
407
549
  }.try(:last) if SimpleForm.input_mappings
408
550
  end
409
551
 
410
- def file_method?(attribute_name) #:nodoc:
552
+ def file_method?(attribute_name)
411
553
  file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
412
554
  file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
413
555
  end
414
556
 
415
- def find_attribute_column(attribute_name) #:nodoc:
416
- if @object.respond_to?(:column_for_attribute)
557
+ def find_attribute_column(attribute_name)
558
+ if @object.respond_to?(:type_for_attribute) && @object.has_attribute?(attribute_name)
559
+ @object.type_for_attribute(attribute_name.to_s)
560
+ elsif @object.respond_to?(:column_for_attribute) && @object.has_attribute?(attribute_name)
417
561
  @object.column_for_attribute(attribute_name)
418
562
  end
419
563
  end
420
564
 
421
- def find_association_reflection(association) #:nodoc:
565
+ def find_association_reflection(association)
422
566
  if @object.class.respond_to?(:reflect_on_association)
423
567
  @object.class.reflect_on_association(association)
424
568
  end
@@ -431,20 +575,42 @@ module SimpleForm
431
575
  # b) Or use the found mapping
432
576
  # 2) If not, fallbacks to #{input_type}Input
433
577
  # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
434
- def find_mapping(input_type) #:nodoc:
578
+ def find_mapping(input_type)
435
579
  discovery_cache[input_type] ||=
436
580
  if mapping = self.class.mappings[input_type]
437
581
  mapping_override(mapping) || mapping
438
582
  else
439
583
  camelized = "#{input_type.to_s.camelize}Input"
440
- attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
584
+ attempt_mapping_with_custom_namespace(camelized) ||
585
+ attempt_mapping(camelized, Object) ||
586
+ attempt_mapping(camelized, self.class) ||
441
587
  raise("No input found for #{input_type}")
442
588
  end
443
589
  end
444
590
 
591
+ # Attempts to find a wrapper mapping. It follows the following rules:
592
+ #
593
+ # 1) It tries to find a wrapper for the current form
594
+ # 2) If not, it tries to find a config
595
+ def find_wrapper_mapping(input_type)
596
+ if options[:wrapper_mappings] && options[:wrapper_mappings][input_type]
597
+ options[:wrapper_mappings][input_type]
598
+ else
599
+ SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
600
+ end
601
+ end
602
+
603
+ def find_wrapper(input_type, options)
604
+ if name = options[:wrapper] || find_wrapper_mapping(input_type)
605
+ name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
606
+ else
607
+ wrapper
608
+ end
609
+ end
610
+
445
611
  # If cache_discovery is enabled, use the class level cache that persists
446
612
  # between requests, otherwise use the instance one.
447
- def discovery_cache #:nodoc:
613
+ def discovery_cache
448
614
  if SimpleForm.cache_discovery
449
615
  self.class.discovery_cache
450
616
  else
@@ -452,21 +618,33 @@ module SimpleForm
452
618
  end
453
619
  end
454
620
 
455
- def mapping_override(klass) #:nodoc:
621
+ def mapping_override(klass)
456
622
  name = klass.name
457
623
  if name =~ /^SimpleForm::Inputs/
458
- attempt_mapping name.split("::").last, Object
624
+ input_name = name.split("::").last
625
+ attempt_mapping_with_custom_namespace(input_name) ||
626
+ attempt_mapping(input_name, Object)
459
627
  end
460
628
  end
461
629
 
462
- def attempt_mapping(mapping, at) #:nodoc:
630
+ def attempt_mapping(mapping, at)
463
631
  return if SimpleForm.inputs_discovery == false && at == Object
464
632
 
465
633
  begin
466
634
  at.const_get(mapping)
467
635
  rescue NameError => e
468
- e.message =~ /#{mapping}$/ ? nil : raise
636
+ raise if e.message !~ /#{mapping}$/
469
637
  end
470
638
  end
639
+
640
+ def attempt_mapping_with_custom_namespace(input_name)
641
+ SimpleForm.custom_inputs_namespaces.each do |namespace|
642
+ if (mapping = attempt_mapping(input_name, namespace.constantize))
643
+ return mapping
644
+ end
645
+ end
646
+
647
+ nil
648
+ end
471
649
  end
472
650
  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