simple_form 1.2.0 → 1.4.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.
Files changed (58) hide show
  1. data/.gitignore +2 -0
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.rdoc +109 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +82 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +180 -53
  9. data/Rakefile +27 -0
  10. data/lib/generators/simple_form/USAGE +1 -1
  11. data/lib/generators/simple_form/install_generator.rb +5 -6
  12. data/lib/generators/simple_form/templates/_form.html.erb +2 -12
  13. data/lib/generators/simple_form/templates/_form.html.haml +10 -0
  14. data/lib/generators/simple_form/templates/_form.html.slim +10 -0
  15. data/lib/generators/simple_form/templates/en.yml +14 -0
  16. data/lib/generators/simple_form/templates/simple_form.rb +66 -12
  17. data/lib/simple_form/action_view_extensions/builder.rb +99 -40
  18. data/lib/simple_form/action_view_extensions/form_helper.rb +29 -6
  19. data/lib/simple_form/components/errors.rb +16 -6
  20. data/lib/simple_form/components/hints.rb +2 -2
  21. data/lib/simple_form/components/label_input.rb +13 -0
  22. data/lib/simple_form/components/labels.rb +5 -7
  23. data/lib/simple_form/components/placeholders.rb +22 -0
  24. data/lib/simple_form/components/wrapper.rb +19 -2
  25. data/lib/simple_form/components.rb +6 -4
  26. data/lib/simple_form/error_notification.rb +42 -0
  27. data/lib/simple_form/form_builder.rb +187 -72
  28. data/lib/simple_form/has_errors.rb +14 -0
  29. data/lib/simple_form/inputs/base.rb +106 -24
  30. data/lib/simple_form/inputs/block_input.rb +3 -2
  31. data/lib/simple_form/inputs/boolean_input.rb +22 -0
  32. data/lib/simple_form/inputs/collection_input.rb +46 -17
  33. data/lib/simple_form/inputs/date_time_input.rb +11 -5
  34. data/lib/simple_form/inputs/hidden_input.rb +11 -2
  35. data/lib/simple_form/inputs/mapping_input.rb +12 -6
  36. data/lib/simple_form/inputs/numeric_input.rb +55 -6
  37. data/lib/simple_form/inputs/priority_input.rb +5 -1
  38. data/lib/simple_form/inputs/string_input.rb +25 -11
  39. data/lib/simple_form/inputs.rb +1 -0
  40. data/lib/simple_form/map_type.rb +9 -6
  41. data/lib/simple_form/version.rb +1 -1
  42. data/lib/simple_form.rb +92 -8
  43. data/simple_form.gemspec +22 -0
  44. data/test/action_view_extensions/builder_test.rb +187 -51
  45. data/test/action_view_extensions/form_helper_test.rb +17 -5
  46. data/test/components/error_test.rb +23 -12
  47. data/test/components/hint_test.rb +4 -8
  48. data/test/components/label_test.rb +79 -11
  49. data/test/components/wrapper_test.rb +63 -0
  50. data/test/discovery_inputs.rb +21 -0
  51. data/test/error_notification_test.rb +62 -0
  52. data/test/form_builder_test.rb +332 -35
  53. data/test/inputs_test.rb +516 -53
  54. data/test/support/misc_helpers.rb +32 -4
  55. data/test/support/mock_controller.rb +4 -4
  56. data/test/support/models.rb +75 -11
  57. data/test/test_helper.rb +28 -12
  58. metadata +51 -11
@@ -1,17 +1,21 @@
1
1
  module SimpleForm
2
2
  class FormBuilder < ActionView::Helpers::FormBuilder
3
- attr_reader :template, :object_name, :object, :attribute_name, :column,
4
- :reflection, :input_type, :options
3
+ attr_reader :template, :object_name, :object
5
4
 
6
5
  extend MapType
7
6
  include SimpleForm::Inputs
8
7
 
9
- map_type :boolean, :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
10
- map_type :string, :email, :url, :to => SimpleForm::Inputs::StringInput
11
- map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
12
- map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
13
- map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
14
- map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
8
+ map_type :text, :file, :to => SimpleForm::Inputs::MappingInput
9
+ map_type :string, :password, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
10
+ map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
11
+ map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
12
+ map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
13
+ map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
14
+ map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
15
+
16
+ def self.discovery_cache
17
+ @discovery_cache ||= {}
18
+ end
15
19
 
16
20
  # Basic input helper, combines all components in the stack to generate
17
21
  # input html based on options the user define and some guesses through
@@ -79,18 +83,37 @@ module SimpleForm
79
83
  # given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
80
84
  #
81
85
  def input(attribute_name, options={}, &block)
82
- define_simple_form_attributes(attribute_name, options)
86
+ column = find_attribute_column(attribute_name)
87
+ input_type = default_input_type(attribute_name, column, options)
83
88
 
84
89
  if block_given?
85
- SimpleForm::Inputs::BlockInput.new(self, block).render
90
+ SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block).render
86
91
  else
87
- klass = self.class.mappings[input_type] ||
88
- self.class.const_get(:"#{input_type.to_s.camelize}Input")
89
- klass.new(self).render
92
+ find_mapping(input_type).new(self, attribute_name, column, input_type, options).render
90
93
  end
91
94
  end
92
95
  alias :attribute :input
93
96
 
97
+ # Creates a input tag for the given attribute. All the given options
98
+ # are sent as :input_html.
99
+ #
100
+ # == Examples
101
+ #
102
+ # simple_form_for @user do |f|
103
+ # f.input_field :name
104
+ # end
105
+ #
106
+ # This is the output html (only the input portion, not the form):
107
+ #
108
+ # <input class="string required" id="user_name" maxlength="100"
109
+ # name="user[name]" size="100" type="text" value="Carlos" />
110
+ #
111
+ def input_field(attribute_name, options={})
112
+ options[:input_html] = options.except(:as)
113
+ options.merge!(:components => [:input], :wrapper => false)
114
+ input(attribute_name, options)
115
+ end
116
+
94
117
  # Helper for dealing with association selects/radios, generating the
95
118
  # collection automatically. It's just a wrapper to input, so all options
96
119
  # supported in input are also supported by association. Some extra options
@@ -123,30 +146,34 @@ module SimpleForm
123
146
 
124
147
  raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
125
148
 
149
+ reflection = find_association_reflection(association)
150
+ raise "Association #{association.inspect} not found" unless reflection
151
+
126
152
  options[:as] ||= :select
127
- @reflection = find_association_reflection(association)
128
- raise "Association #{association.inspect} not found" unless @reflection
153
+ options[:collection] ||= reflection.klass.all(reflection.options.slice(:conditions, :order))
129
154
 
130
- case @reflection.macro
155
+ attribute = case reflection.macro
131
156
  when :belongs_to
132
- attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
157
+ reflection.options[:foreign_key] || :"#{reflection.name}_id"
133
158
  when :has_one
134
- raise ":has_one association are not supported by f.association"
159
+ raise ":has_one associations are not supported by f.association"
135
160
  else
136
- attribute = :"#{@reflection.name.to_s.singularize}_ids"
137
-
138
161
  if options[:as] == :select
139
162
  html_options = options[:input_html] ||= {}
140
163
  html_options[:size] ||= 5
141
164
  html_options[:multiple] = true unless html_options.key?(:multiple)
142
165
  end
143
- end
144
166
 
145
- options[:collection] ||= @reflection.klass.all(
146
- :conditions => @reflection.options[:conditions], :order => @reflection.options[:order]
147
- )
167
+ # Force the association to be preloaded for performance.
168
+ if options[:preload] != false && object.respond_to?(association)
169
+ target = object.send(association)
170
+ target.to_a if target.respond_to?(:to_a)
171
+ end
172
+
173
+ :"#{reflection.name.to_s.singularize}_ids"
174
+ end
148
175
 
149
- returning(input(attribute, options)) { @reflection = nil }
176
+ input(attribute, options.merge(:reflection => reflection))
150
177
  end
151
178
 
152
179
  # Creates a button:
@@ -158,8 +185,11 @@ module SimpleForm
158
185
  # It just acts as a proxy to method name given.
159
186
  #
160
187
  def button(type, *args, &block)
161
- if respond_to?(:"#{type}_button")
162
- send(:"#{type}_button", *args, &block)
188
+ options = args.extract_options!
189
+ options[:class] = "button #{options[:class]}".strip
190
+ args << options
191
+ if respond_to?("#{type}_button")
192
+ send("#{type}_button", *args, &block)
163
193
  else
164
194
  send(type, *args, &block)
165
195
  end
@@ -174,8 +204,27 @@ module SimpleForm
174
204
  # f.error :name, :id => "cool_error"
175
205
  #
176
206
  def error(attribute_name, options={})
177
- define_simple_form_attributes(attribute_name, :error_html => options)
178
- SimpleForm::Inputs::Base.new(self).error
207
+ options[:error_html] = options.dup
208
+ column = find_attribute_column(attribute_name)
209
+ input_type = default_input_type(attribute_name, column, options)
210
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
211
+ end
212
+
213
+ # Return the error but also considering its name. This is used
214
+ # when errors for a hidden field need to be shown.
215
+ #
216
+ # == Examples
217
+ #
218
+ # f.full_error :token #=> <span class="error">Token is invalid</span>
219
+ #
220
+ def full_error(attribute_name, options={})
221
+ options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
222
+ object.class.human_attribute_name(attribute_name.to_s)
223
+ else
224
+ attribute_name.to_s.humanize
225
+ end
226
+
227
+ error(attribute_name, options)
179
228
  end
180
229
 
181
230
  # Creates a hint tag for the given attribute. Accepts a symbol indicating
@@ -189,9 +238,15 @@ module SimpleForm
189
238
  # f.hint "Don't forget to accept this"
190
239
  #
191
240
  def hint(attribute_name, options={})
192
- attribute_name, options[:hint] = nil, attribute_name if attribute_name.is_a?(String)
193
- define_simple_form_attributes(attribute_name, :hint => options.delete(:hint), :hint_html => options)
194
- SimpleForm::Inputs::Base.new(self).hint
241
+ options[:hint_html] = options.dup
242
+ if attribute_name.is_a?(String)
243
+ options[:hint] = attribute_name
244
+ attribute_name, column, input_type = nil, nil, nil
245
+ else
246
+ column = find_attribute_column(attribute_name)
247
+ input_type = default_input_type(attribute_name, column, options)
248
+ end
249
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
195
250
  end
196
251
 
197
252
  # Creates a default label tag for the given attribute. You can give a label
@@ -210,65 +265,125 @@ module SimpleForm
210
265
  def label(attribute_name, *args)
211
266
  return super if args.first.is_a?(String)
212
267
  options = args.extract_options!
213
- define_simple_form_attributes(attribute_name, :label => options.delete(:label),
214
- :label_html => options, :required => options.delete(:required))
215
- SimpleForm::Inputs::Base.new(self).label
268
+ options[:label_html] = options.dup
269
+ options[:label] = options.delete(:label)
270
+ options[:required] = options.delete(:required)
271
+ column = find_attribute_column(attribute_name)
272
+ input_type = default_input_type(attribute_name, column, options)
273
+ SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
216
274
  end
217
275
 
218
- private
219
-
220
- # Setup default simple form attributes.
221
- def define_simple_form_attributes(attribute_name, options) #:nodoc:
222
- @options = options
223
-
224
- if @attribute_name = attribute_name
225
- @column = find_attribute_column
226
- @input_type = default_input_type
227
- end
276
+ # Creates an error notification message that only appears when the form object
277
+ # has some error. You can give a specific message with the :message option,
278
+ # otherwise it will look for a message using I18n. All other options given are
279
+ # passed straight as html options to the html tag.
280
+ #
281
+ # == Examples
282
+ #
283
+ # f.error_notification
284
+ # f.error_notification :message => 'Something went wrong'
285
+ # f.error_notification :id => 'user_error_message', :class => 'form_error'
286
+ #
287
+ def error_notification(options={})
288
+ SimpleForm::ErrorNotification.new(self, options).render
228
289
  end
229
290
 
291
+ private
292
+
230
293
  # Attempt to guess the better input type given the defined options. By
231
294
  # default alwayls fallback to the user :as option, or to a :select when a
232
295
  # collection is given.
233
- def default_input_type #:nodoc:
234
- return @options[:as].to_sym if @options[:as]
235
- return :select if @options[:collection]
236
-
237
- input_type = @column.try(:type)
296
+ def default_input_type(attribute_name, column, options) #:nodoc:
297
+ return options[:as].to_sym if options[:as]
298
+ return :select if options[:collection]
299
+ custom_type = find_custom_type(attribute_name.to_s) and return custom_type
238
300
 
301
+ input_type = column.try(:type)
239
302
  case input_type
240
- when :timestamp
241
- :datetime
242
- when :string, nil
243
- match = case @attribute_name.to_s
244
- when /password/ then :password
245
- when /time_zone/ then :time_zone
246
- when /country/ then :country
247
- when /email/ then :email
248
- when /url/ then :url
249
- end
250
-
251
- match || input_type || file_method? || :string
303
+ when :timestamp
304
+ :datetime
305
+ when :string, nil
306
+ case attribute_name.to_s
307
+ when /password/ then :password
308
+ when /time_zone/ then :time_zone
309
+ when /country/ then :country
310
+ when /email/ then :email
311
+ when /phone/ then :tel
312
+ when /url/ then :url
252
313
  else
253
- input_type
314
+ file_method?(attribute_name) ? :file : (input_type || :string)
315
+ end
316
+ else
317
+ input_type
254
318
  end
255
319
  end
256
320
 
257
- # Checks if attribute is a file_method.
258
- def file_method? #:nodoc:
259
- file = @object.send(@attribute_name) if @object.respond_to?(@attribute_name)
260
- :file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
321
+ def find_custom_type(attribute_name) #:nodoc:
322
+ SimpleForm.input_mappings.find { |match, type|
323
+ attribute_name =~ match
324
+ }.try(:last) if SimpleForm.input_mappings
325
+ end
326
+
327
+ def file_method?(attribute_name) #:nodoc:
328
+ file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
329
+ file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
261
330
  end
262
331
 
263
- # Finds the database column for the given attribute
264
- def find_attribute_column #:nodoc:
265
- @object.column_for_attribute(@attribute_name) if @object.respond_to?(:column_for_attribute)
332
+ def find_attribute_column(attribute_name) #:nodoc:
333
+ if @object.respond_to?(:column_for_attribute)
334
+ @object.column_for_attribute(attribute_name)
335
+ end
266
336
  end
267
337
 
268
- # Find reflection related to association
269
338
  def find_association_reflection(association) #:nodoc:
270
- @object.class.reflect_on_association(association) if @object.class.respond_to?(:reflect_on_association)
339
+ if @object.class.respond_to?(:reflect_on_association)
340
+ @object.class.reflect_on_association(association)
341
+ end
342
+ end
343
+
344
+ # Attempts to find a mapping. It follows the following rules:
345
+ #
346
+ # 1) It tries to find a registered mapping, if succeeds:
347
+ # a) Try to find an alternative with the same name in the Object scope
348
+ # b) Or use the found mapping
349
+ # 2) If not, fallbacks to #{input_type}Input
350
+ # 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
351
+ def find_mapping(input_type) #:nodoc:
352
+ discovery_cache[input_type] ||=
353
+ if mapping = self.class.mappings[input_type]
354
+ mapping_override(mapping) || mapping
355
+ else
356
+ camelized = "#{input_type.to_s.camelize}Input"
357
+ attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
358
+ raise("No input found for #{input_type}")
359
+ end
360
+ end
361
+
362
+ # If cache_discovery is enabled, use the class level cache that persists
363
+ # between requests, otherwise use the instance one.
364
+ def discovery_cache #:nodoc:
365
+ if SimpleForm.cache_discovery
366
+ self.class.discovery_cache
367
+ else
368
+ @discovery_cache ||= {}
369
+ end
370
+ end
371
+
372
+ def mapping_override(klass) #:nodoc:
373
+ name = klass.name
374
+ if name =~ /^SimpleForm::Inputs/
375
+ attempt_mapping name.split("::").last, Object
376
+ end
271
377
  end
272
378
 
379
+ def attempt_mapping(mapping, at) #:nodoc:
380
+ return if SimpleForm.inputs_discovery == false && at == Object
381
+
382
+ begin
383
+ at.const_get(mapping)
384
+ rescue NameError => e
385
+ e.message =~ /#{mapping}$/ ? nil : raise
386
+ end
387
+ end
273
388
  end
274
389
  end
@@ -0,0 +1,14 @@
1
+ module SimpleForm
2
+ module HasErrors
3
+
4
+ protected
5
+
6
+ def errors
7
+ object.errors
8
+ end
9
+
10
+ def has_errors?
11
+ object && object.respond_to?(:errors) && errors.present?
12
+ end
13
+ end
14
+ end
@@ -11,14 +11,27 @@ module SimpleForm
11
11
 
12
12
  include SimpleForm::Components::Errors
13
13
  include SimpleForm::Components::Hints
14
- include SimpleForm::Components::Labels
14
+ include SimpleForm::Components::LabelInput
15
+ include SimpleForm::Components::Placeholders
15
16
  include SimpleForm::Components::Wrapper
16
17
 
17
- delegate :template, :object, :object_name, :attribute_name, :column,
18
- :reflection, :input_type, :options, :to => :@builder
19
-
20
- def initialize(builder)
21
- @builder = builder
18
+ attr_reader :attribute_name, :column, :input_type, :reflection,
19
+ :options, :input_html_options
20
+
21
+ delegate :template, :object, :object_name, :to => :@builder
22
+
23
+ def initialize(builder, attribute_name, column, input_type, options = {})
24
+ @builder = builder
25
+ @attribute_name = attribute_name
26
+ @column = column
27
+ @input_type = input_type
28
+ @reflection = options.delete(:reflection)
29
+ @options = options
30
+ @input_html_options = html_options_for(:input, input_html_classes).tap do |o|
31
+ o[:required] = true if has_required? # Don't make this conditional on HTML5 here, because we want the CSS class to be set
32
+ o[:disabled] = true if disabled?
33
+ o[:autofocus] = true if has_autofocus? && SimpleForm.html5
34
+ end
22
35
  end
23
36
 
24
37
  def input
@@ -29,35 +42,61 @@ module SimpleForm
29
42
  options
30
43
  end
31
44
 
32
- def input_html_options
33
- html_options_for(:input, input_type, required_class)
45
+ def input_html_classes
46
+ [input_type, required_class]
34
47
  end
35
48
 
36
49
  def render
37
- content = components_list.map do |component|
50
+ content = "".html_safe
51
+ components_list.each do |component|
38
52
  next if options[component] == false
39
- send(component)
53
+ rendered = send(component)
54
+ content.safe_concat rendered.to_s if rendered
40
55
  end
41
- content.compact!
42
- wrap(content.join.html_safe)
56
+ wrap(content)
43
57
  end
44
58
 
45
59
  protected
46
60
 
47
61
  def components_list
48
- SimpleForm.components
62
+ options[:components] || SimpleForm.components
49
63
  end
50
64
 
51
65
  def attribute_required?
52
- if options.key?(:required)
66
+ if !options[:required].nil?
53
67
  options[:required]
54
- elsif defined?(object.class.validators_on)
55
- object.class.validators_on(attribute_name).any? { |v| v.kind == :presence }
68
+ elsif has_validators?
69
+ (attribute_validators + reflection_validators).any? { |v| v.kind == :presence }
56
70
  else
57
- true
71
+ attribute_required_by_default?
58
72
  end
59
73
  end
60
74
 
75
+ # Whether this input is valid for HTML 5 required attribute.
76
+ def has_required?
77
+ attribute_required? && SimpleForm.html5
78
+ end
79
+
80
+ def has_autofocus?
81
+ options[:autofocus]
82
+ end
83
+
84
+ def has_validators?
85
+ attribute_name && object.class.respond_to?(:validators_on)
86
+ end
87
+
88
+ def attribute_validators
89
+ object.class.validators_on(attribute_name)
90
+ end
91
+
92
+ def reflection_validators
93
+ reflection ? object.class.validators_on(reflection.name) : []
94
+ end
95
+
96
+ def attribute_required_by_default?
97
+ SimpleForm.required_by_default
98
+ end
99
+
61
100
  def required_class
62
101
  attribute_required? ? :required : :optional
63
102
  end
@@ -68,12 +107,16 @@ module SimpleForm
68
107
  end
69
108
 
70
109
  # Retrieve options for the given namespace from the options hash
71
- def html_options_for(namespace, *extra)
110
+ def html_options_for(namespace, extra)
72
111
  html_options = options[:"#{namespace}_html"] || {}
73
112
  html_options[:class] = (extra << html_options[:class]).join(' ').strip if extra.present?
74
113
  html_options
75
114
  end
76
115
 
116
+ def disabled?
117
+ options[:disabled]
118
+ end
119
+
77
120
  # Lookup translations for the given namespace using I18n, based on object name,
78
121
  # actual action and attribute name. Lookup priority as follows:
79
122
  #
@@ -87,6 +130,15 @@ module SimpleForm
87
130
  # Action is the action being rendered, usually :new or :edit.
88
131
  # And attribute is the attribute itself, :name for example.
89
132
  #
133
+ # The lookup for nested attributes is also done in a nested format using
134
+ # both model and nested object names, such as follow:
135
+ #
136
+ # simple_form.{namespace}.{model}.{nested}.{action}.{attribute}
137
+ # simple_form.{namespace}.{model}.{nested}.{attribute}
138
+ # simple_form.{namespace}.{nested}.{action}.{attribute}
139
+ # simple_form.{namespace}.{nested}.{attribute}
140
+ # simple_form.{namespace}.{attribute}
141
+ #
90
142
  # Example:
91
143
  #
92
144
  # simple_form:
@@ -99,19 +151,49 @@ module SimpleForm
99
151
  #
100
152
  # Take a look at our locale example file.
101
153
  def translate(namespace, default='')
102
- lookups = []
103
- lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
104
- lookups << :"#{object_name}.#{reflection_or_attribute_name}"
154
+ return nil unless SimpleForm.translate
155
+
156
+ model_names = lookup_model_names
157
+ lookups = []
158
+
159
+ while !model_names.empty?
160
+ joined_model_names = model_names.join(".")
161
+ model_names.shift
162
+
163
+ lookups << :"#{joined_model_names}.#{lookup_action}.#{reflection_or_attribute_name}"
164
+ lookups << :"#{joined_model_names}.#{reflection_or_attribute_name}"
165
+ end
105
166
  lookups << :"#{reflection_or_attribute_name}"
106
167
  lookups << default
107
- I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups)
168
+
169
+ I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups).presence
170
+ end
171
+
172
+ # Extract the model names from the object_name mess.
173
+ #
174
+ # Example:
175
+ #
176
+ # route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
177
+ # ["route", "blocks", "blocks_learning_object", "foo"]
178
+ #
179
+ def lookup_model_names
180
+ object_name.to_s.scan(/([a-zA-Z_]+)/).flatten.map do |x|
181
+ x.gsub('_attributes', '')
182
+ end
108
183
  end
109
184
 
110
185
  # The action to be used in lookup.
111
186
  def lookup_action
112
- action = template.controller.action_name.to_sym
187
+ action = template.controller.action_name
188
+ return unless action
189
+ action = action.to_sym
113
190
  ACTIONS[action] || action
114
191
  end
192
+
193
+ def input_method
194
+ self.class.mappings[input_type] or
195
+ raise("Could not find method for #{input_type.inspect}")
196
+ end
115
197
  end
116
198
  end
117
- end
199
+ end
@@ -1,8 +1,9 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class BlockInput < Base
4
- def initialize(builder, block)
5
- @builder, @block = builder, block
4
+ def initialize(*args, &block)
5
+ super
6
+ @block = block
6
7
  end
7
8
 
8
9
  def input
@@ -0,0 +1,22 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class BooleanInput < Base
4
+ def input
5
+ @builder.check_box(attribute_name, input_html_options)
6
+ end
7
+
8
+ def label_input
9
+ input + (options[:label] == false ? "" : label)
10
+ end
11
+
12
+ protected
13
+
14
+ # Booleans are not required by default because in most of the cases
15
+ # it makes no sense marking them as required. The only exception is
16
+ # Terms of Use usually presented at most sites sign up screen.
17
+ def attribute_required_by_default?
18
+ false
19
+ end
20
+ end
21
+ end
22
+ end
@@ -13,10 +13,9 @@ module SimpleForm
13
13
  end
14
14
 
15
15
  def input
16
- collection = (options[:collection] || self.class.boolean_collection).to_a
17
- detect_collection_methods(collection, options)
18
- @builder.send(:"collection_#{input_type}", attribute_name, collection, options[:value_method],
19
- options[:label_method], input_options, input_html_options)
16
+ label_method, value_method = detect_collection_methods
17
+ @builder.send(:"collection_#{input_type}", attribute_name, collection,
18
+ value_method, label_method, input_options, input_html_options)
20
19
  end
21
20
 
22
21
  def input_options
@@ -27,6 +26,15 @@ module SimpleForm
27
26
 
28
27
  protected
29
28
 
29
+ def collection
30
+ @collection ||= (options.delete(:collection) || self.class.boolean_collection).to_a
31
+ end
32
+
33
+ # Select components does not allow the required html tag.
34
+ def has_required?
35
+ super && input_type != :select
36
+ end
37
+
30
38
  # Check if :include_blank must be included by default.
31
39
  def skip_include_blank?
32
40
  (options.keys & [:prompt, :include_blank, :default, :selected]).any? ||
@@ -38,21 +46,42 @@ module SimpleForm
38
46
  # on default label and value methods that can be configured through
39
47
  # SimpleForm.collection_label_methods and
40
48
  # SimpleForm.collection_value_methods.
41
- def detect_collection_methods(collection, options)
42
- sample = collection.first || collection.last
43
-
44
- case sample
45
- when Array
46
- label, value = :first, :last
47
- when Integer
48
- label, value = :to_s, :to_i
49
- when String, NilClass
50
- label, value = :to_s, :to_s
49
+ def detect_collection_methods
50
+ label, value = options.delete(:label_method), options.delete(:value_method)
51
+
52
+ unless label && value
53
+ common_method_for = detect_common_display_methods
54
+ label ||= common_method_for[:label]
55
+ value ||= common_method_for[:value]
56
+ end
57
+
58
+ [label, value]
59
+ end
60
+
61
+ def detect_common_display_methods
62
+ collection_classes = detect_collection_classes
63
+
64
+ if collection_classes.include?(Array)
65
+ { :label => :first, :value => :last }
66
+ elsif collection_includes_basic_objects?(collection_classes)
67
+ { :label => :to_s, :value => :to_s }
68
+ else
69
+ sample = collection.first || collection.last
70
+
71
+ { :label => SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) },
72
+ :value => SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) } }
51
73
  end
74
+ end
75
+
76
+ def detect_collection_classes
77
+ collection.map { |e| e.class }.uniq
78
+ end
52
79
 
53
- options[:label_method] ||= label || SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) }
54
- options[:value_method] ||= value || SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) }
80
+ def collection_includes_basic_objects?(collection_classes)
81
+ (collection_classes & [
82
+ String, Integer, Fixnum, Bignum, Float, NilClass, Symbol, TrueClass, FalseClass
83
+ ]).any?
55
84
  end
56
85
  end
57
86
  end
58
- end
87
+ end