simple_form 2.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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