simple_form 1.4.2 → 1.5.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 (51) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +9 -1
  3. data/CHANGELOG.rdoc +20 -0
  4. data/Gemfile +5 -4
  5. data/README.rdoc +2 -2
  6. data/Rakefile +1 -1
  7. data/lib/generators/simple_form/install_generator.rb +2 -6
  8. data/lib/generators/simple_form/templates/{simple_form.rb → config/initializers/simple_form.rb} +0 -0
  9. data/lib/generators/simple_form/templates/{en.yml → config/locales/simple_form.en.yml} +0 -0
  10. data/lib/simple_form.rb +1 -1
  11. data/lib/simple_form/action_view_extensions/builder.rb +12 -5
  12. data/lib/simple_form/action_view_extensions/form_helper.rb +13 -8
  13. data/lib/simple_form/components.rb +1 -1
  14. data/lib/simple_form/components/errors.rb +10 -2
  15. data/lib/simple_form/components/hints.rb +10 -0
  16. data/lib/simple_form/components/label_input.rb +5 -3
  17. data/lib/simple_form/components/labels.rb +10 -4
  18. data/lib/simple_form/components/placeholders.rb +10 -4
  19. data/lib/simple_form/error_notification.rb +1 -1
  20. data/lib/simple_form/form_builder.rb +10 -7
  21. data/lib/simple_form/helpers.rb +9 -0
  22. data/lib/simple_form/helpers/has_errors.rb +15 -0
  23. data/lib/simple_form/helpers/maxlength.rb +24 -0
  24. data/lib/simple_form/helpers/pattern.rb +28 -0
  25. data/lib/simple_form/helpers/required.rb +36 -0
  26. data/lib/simple_form/helpers/validators.rb +44 -0
  27. data/lib/simple_form/inputs.rb +5 -2
  28. data/lib/simple_form/inputs/base.rb +24 -47
  29. data/lib/simple_form/inputs/boolean_input.rb +1 -1
  30. data/lib/simple_form/inputs/collection_input.rb +1 -1
  31. data/lib/simple_form/inputs/date_time_input.rb +1 -1
  32. data/lib/simple_form/inputs/file_input.rb +9 -0
  33. data/lib/simple_form/inputs/hidden_input.rb +1 -1
  34. data/lib/simple_form/inputs/numeric_input.rb +20 -13
  35. data/lib/simple_form/inputs/password_input.rb +13 -0
  36. data/lib/simple_form/inputs/range_input.rb +16 -0
  37. data/lib/simple_form/inputs/string_input.rb +7 -24
  38. data/lib/simple_form/inputs/text_input.rb +12 -0
  39. data/lib/simple_form/version.rb +1 -1
  40. data/test/action_view_extensions/builder_test.rb +59 -0
  41. data/test/action_view_extensions/form_helper_test.rb +21 -0
  42. data/test/discovery_inputs.rb +2 -2
  43. data/test/form_builder_test.rb +54 -0
  44. data/test/inputs_test.rb +185 -20
  45. data/test/support/models.rb +23 -2
  46. data/test/test_helper.rb +1 -0
  47. metadata +17 -11
  48. data/Gemfile.lock +0 -54
  49. data/init.rb +0 -1
  50. data/lib/simple_form/has_errors.rb +0 -14
  51. data/lib/simple_form/inputs/mapping_input.rb +0 -29
@@ -0,0 +1,36 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Required
4
+ private
5
+
6
+ def attribute_required?
7
+ @required
8
+ end
9
+
10
+ def calculate_required
11
+ if !options[:required].nil?
12
+ options[:required]
13
+ elsif has_validators?
14
+ (attribute_validators + reflection_validators).any? do |v|
15
+ v.kind == :presence && valid_validator?(v)
16
+ end
17
+ else
18
+ attribute_required_by_default?
19
+ end
20
+ end
21
+
22
+ # Whether this input is valid for HTML 5 required attribute.
23
+ def has_required?
24
+ attribute_required? && SimpleForm.html5 && SimpleForm.browser_validations
25
+ end
26
+
27
+ def attribute_required_by_default?
28
+ SimpleForm.required_by_default
29
+ end
30
+
31
+ def required_class
32
+ attribute_required? ? :required : :optional
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ module SimpleForm
2
+ module Helpers
3
+ module Validators
4
+ private
5
+
6
+ def has_validators?
7
+ attribute_name && object.class.respond_to?(:validators_on)
8
+ end
9
+
10
+ def attribute_validators
11
+ object.class.validators_on(attribute_name)
12
+ end
13
+
14
+ def reflection_validators
15
+ reflection ? object.class.validators_on(reflection.name) : []
16
+ end
17
+
18
+ def valid_validator?(validator)
19
+ !conditional_validators?(validator) && action_validator_match?(validator)
20
+ end
21
+
22
+ def conditional_validators?(validator)
23
+ validator.options.include?(:if) || validator.options.include?(:unless)
24
+ end
25
+
26
+ def action_validator_match?(validator)
27
+ return true if !validator.options.include?(:on)
28
+
29
+ case validator.options[:on]
30
+ when :save
31
+ true
32
+ when :create
33
+ !object.persisted?
34
+ when :update
35
+ object.persisted?
36
+ end
37
+ end
38
+
39
+ def find_validator(validator)
40
+ attribute_validators.find { |v| validator === v }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -5,10 +5,13 @@ module SimpleForm
5
5
  autoload :BooleanInput, 'simple_form/inputs/boolean_input'
6
6
  autoload :CollectionInput, 'simple_form/inputs/collection_input'
7
7
  autoload :DateTimeInput, 'simple_form/inputs/date_time_input'
8
+ autoload :FileInput, 'simple_form/inputs/file_input'
8
9
  autoload :HiddenInput, 'simple_form/inputs/hidden_input'
9
- autoload :MappingInput, 'simple_form/inputs/mapping_input'
10
10
  autoload :NumericInput, 'simple_form/inputs/numeric_input'
11
+ autoload :PasswordInput, 'simple_form/inputs/password_input'
11
12
  autoload :PriorityInput, 'simple_form/inputs/priority_input'
13
+ autoload :RangeInput, 'simple_form/inputs/range_input'
12
14
  autoload :StringInput, 'simple_form/inputs/string_input'
15
+ autoload :TextInput, 'simple_form/inputs/text_input'
13
16
  end
14
- end
17
+ end
@@ -9,12 +9,26 @@ module SimpleForm
9
9
  :update => :edit
10
10
  }
11
11
 
12
+ include SimpleForm::Helpers::Required
13
+ include SimpleForm::Helpers::Validators
14
+ include SimpleForm::Helpers::Maxlength
15
+ include SimpleForm::Helpers::Pattern
16
+
12
17
  include SimpleForm::Components::Errors
13
18
  include SimpleForm::Components::Hints
14
19
  include SimpleForm::Components::LabelInput
15
20
  include SimpleForm::Components::Placeholders
16
21
  include SimpleForm::Components::Wrapper
17
22
 
23
+ # Enables certain components support to the given input.
24
+ def self.enable(*args)
25
+ args.each { |m| alias_method m, :"enabled_#{m}" }
26
+ end
27
+
28
+ def self.disable(*args)
29
+ args.each { |m| alias_method m, :"disabled_#{m}" }
30
+ end
31
+
18
32
  attr_reader :attribute_name, :column, :input_type, :reflection,
19
33
  :options, :input_html_options
20
34
 
@@ -27,8 +41,9 @@ module SimpleForm
27
41
  @input_type = input_type
28
42
  @reflection = options.delete(:reflection)
29
43
  @options = options
44
+ @required = calculate_required
30
45
  @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
46
+ o[:required] = true if has_required?
32
47
  o[:disabled] = true if disabled?
33
48
  o[:autofocus] = true if has_autofocus? && SimpleForm.html5
34
49
  end
@@ -56,57 +71,24 @@ module SimpleForm
56
71
  wrap(content)
57
72
  end
58
73
 
59
- protected
74
+ private
60
75
 
61
- def components_list
62
- options[:components] || SimpleForm.components
76
+ def add_size!
77
+ input_html_options[:size] ||= [limit, SimpleForm.default_input_size].compact.min
63
78
  end
64
79
 
65
- def attribute_required?
66
- if !options[:required].nil?
67
- options[:required]
68
- elsif has_validators?
69
- (attribute_validators + reflection_validators).any? do |v|
70
- v.kind == :presence && !conditional_validators?(v)
71
- end
72
- else
73
- attribute_required_by_default?
74
- end
80
+ def limit
81
+ column && column.limit
75
82
  end
76
83
 
77
- # Whether this input is valid for HTML 5 required attribute.
78
- def has_required?
79
- attribute_required? && SimpleForm.html5 && SimpleForm.browser_validations
84
+ def components_list
85
+ options[:components] || SimpleForm.components
80
86
  end
81
87
 
82
88
  def has_autofocus?
83
89
  options[:autofocus]
84
90
  end
85
91
 
86
- def has_validators?
87
- attribute_name && object.class.respond_to?(:validators_on)
88
- end
89
-
90
- def attribute_validators
91
- object.class.validators_on(attribute_name)
92
- end
93
-
94
- def reflection_validators
95
- reflection ? object.class.validators_on(reflection.name) : []
96
- end
97
-
98
- def conditional_validators?(validator)
99
- validator.options.include?(:if) || validator.options.include?(:unless)
100
- end
101
-
102
- def attribute_required_by_default?
103
- SimpleForm.required_by_default
104
- end
105
-
106
- def required_class
107
- attribute_required? ? :required : :optional
108
- end
109
-
110
92
  # Find reflection name when available, otherwise use attribute
111
93
  def reflection_or_attribute_name
112
94
  reflection ? reflection.name : attribute_name
@@ -120,7 +102,7 @@ module SimpleForm
120
102
  end
121
103
 
122
104
  def disabled?
123
- options[:disabled]
105
+ options[:disabled] == true
124
106
  end
125
107
 
126
108
  # Lookup translations for the given namespace using I18n, based on object name,
@@ -197,11 +179,6 @@ module SimpleForm
197
179
  action = action.to_sym
198
180
  ACTIONS[action] || action
199
181
  end
200
-
201
- def input_method
202
- self.class.mappings[input_type] or
203
- raise("Could not find method for #{input_type.inspect}")
204
- end
205
182
  end
206
183
  end
207
184
  end
@@ -9,7 +9,7 @@ module SimpleForm
9
9
  input + (options[:label] == false ? "" : label)
10
10
  end
11
11
 
12
- protected
12
+ private
13
13
 
14
14
  # Booleans are not required by default because in most of the cases
15
15
  # it makes no sense marking them as required. The only exception is
@@ -24,7 +24,7 @@ module SimpleForm
24
24
  options
25
25
  end
26
26
 
27
- protected
27
+ private
28
28
 
29
29
  def collection
30
30
  @collection ||= (options.delete(:collection) || self.class.boolean_collection).to_a
@@ -5,7 +5,7 @@ module SimpleForm
5
5
  @builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
6
6
  end
7
7
 
8
- private
8
+ private
9
9
 
10
10
  def has_required?
11
11
  false
@@ -0,0 +1,9 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class FileInput < Base
4
+ def input
5
+ @builder.file_field(attribute_name, input_html_options)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -6,7 +6,7 @@ module SimpleForm
6
6
  end
7
7
  alias :input :render
8
8
 
9
- protected
9
+ private
10
10
 
11
11
  def attribute_required?
12
12
  false
@@ -1,11 +1,15 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class NumericInput < Base
4
+ enable :placeholder
5
+
4
6
  def input
5
- input_html_options[:type] ||= "number" if SimpleForm.html5
6
- input_html_options[:size] ||= SimpleForm.default_input_size
7
- input_html_options[:step] ||= integer? ? 1 : "any" if SimpleForm.html5
8
- infer_attributes_from_validations! if SimpleForm.html5
7
+ add_size!
8
+ if SimpleForm.html5
9
+ input_html_options[:type] ||= "number"
10
+ input_html_options[:step] ||= integer? ? 1 : "any"
11
+ infer_attributes_from_validations!
12
+ end
9
13
  @builder.text_field(attribute_name, input_html_options)
10
14
  end
11
15
 
@@ -13,10 +17,11 @@ module SimpleForm
13
17
  super.unshift("numeric")
14
18
  end
15
19
 
16
- protected
20
+ private
17
21
 
18
- def has_placeholder?
19
- placeholder_present?
22
+ # Rails adds the size attr by default, if the :size key does not exist.
23
+ def add_size!
24
+ input_html_options[:size] ||= nil
20
25
  end
21
26
 
22
27
  def infer_attributes_from_validations!
@@ -50,15 +55,17 @@ module SimpleForm
50
55
  end
51
56
 
52
57
  def find_numericality_validator
53
- attribute_validators.find { |v| ActiveModel::Validations::NumericalityValidator === v }
58
+ find_validator(ActiveModel::Validations::NumericalityValidator)
54
59
  end
55
60
 
56
- private
57
-
58
61
  def evaluate_validator_option(option)
59
- return option if option.is_a?(Numeric)
60
- return object.send(option) if option.is_a?(Symbol)
61
- return option.call(object) if option.respond_to?(:call)
62
+ if option.is_a?(Numeric)
63
+ option
64
+ elsif option.is_a?(Symbol)
65
+ object.send(option)
66
+ elsif option.respond_to?(:call)
67
+ option.call(object)
68
+ end
62
69
  end
63
70
  end
64
71
  end
@@ -0,0 +1,13 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class PasswordInput < Base
4
+ enable :placeholder
5
+
6
+ def input
7
+ add_size!
8
+ add_maxlength!
9
+ @builder.password_field(attribute_name, input_html_options)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class RangeInput < NumericInput
4
+ disable :placeholder
5
+
6
+ def input
7
+ if SimpleForm.html5
8
+ input_html_options[:type] ||= "range"
9
+ input_html_options[:step] ||= 1
10
+ end
11
+
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,42 +1,25 @@
1
1
  module SimpleForm
2
2
  module Inputs
3
3
  class StringInput < Base
4
- extend MapType
5
-
6
- map_type :string, :email, :search, :tel, :url, :to => :text_field
7
- map_type :password, :to => :password_field
4
+ enable :placeholder
8
5
 
9
6
  def input
10
- input_html_options[:size] ||= [limit, SimpleForm.default_input_size].compact.min
11
- input_html_options[:maxlength] ||= limit if limit && SimpleForm.html5
12
- if password? || SimpleForm.html5
13
- input_html_options[:type] ||= input_type unless string?
14
- end
15
-
16
- @builder.send(input_method, attribute_name, input_html_options)
7
+ input_html_options[:type] ||= input_type if SimpleForm.html5 && !string?
8
+ add_maxlength!
9
+ add_pattern!
10
+ add_size!
11
+ @builder.text_field(attribute_name, input_html_options)
17
12
  end
18
13
 
19
14
  def input_html_classes
20
15
  string? ? super : super.unshift("string")
21
16
  end
22
17
 
23
- protected
24
-
25
- def limit
26
- column && column.limit
27
- end
28
-
29
- def has_placeholder?
30
- placeholder_present?
31
- end
18
+ private
32
19
 
33
20
  def string?
34
21
  input_type == :string
35
22
  end
36
-
37
- def password?
38
- input_type == :password
39
- end
40
23
  end
41
24
  end
42
25
  end
@@ -0,0 +1,12 @@
1
+ module SimpleForm
2
+ module Inputs
3
+ class TextInput < Base
4
+ enable :placeholder
5
+
6
+ def input
7
+ add_maxlength!
8
+ @builder.text_area(attribute_name, input_html_options)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module SimpleForm
2
- VERSION = "1.4.2".freeze
2
+ VERSION = "1.5.0".freeze
3
3
  end
@@ -96,6 +96,24 @@ class BuilderTest < ActionView::TestCase
96
96
  assert_select 'form ul input[type=radio][value=false]#user_active_false'
97
97
  end
98
98
 
99
+ test 'collection radio does not wrap the collection in the explicitly false collection wrapper tag' do
100
+ swap SimpleForm, :collection_wrapper_tag => :ul do
101
+ with_collection_radio @user, :active, [true, false], :to_s, :to_s, :collection_wrapper_tag => false
102
+
103
+ assert_no_select 'form ul'
104
+ assert_no_select 'form ul'
105
+ end
106
+ end
107
+
108
+ test 'collection radio does not wrap the collection in the explicitly nil collection wrapper tag' do
109
+ swap SimpleForm, :collection_wrapper_tag => :ul do
110
+ with_collection_radio @user, :active, [true, false], :to_s, :to_s, :collection_wrapper_tag => nil
111
+
112
+ assert_no_select 'form ul'
113
+ assert_no_select 'form ul'
114
+ end
115
+ end
116
+
99
117
  test 'collection radio does not wrap the collection by default' do
100
118
  with_collection_radio @user, :active, [true, false], :to_s, :to_s
101
119
 
@@ -118,6 +136,20 @@ class BuilderTest < ActionView::TestCase
118
136
  assert_select 'form li input[type=radio][value=false]#user_active_false'
119
137
  end
120
138
 
139
+ test 'collection radio does not wrap each label/radio in the explicitly false item wrapper tag' do
140
+ with_collection_radio @user, :active, [true, false], :to_s, :to_s, :item_wrapper_tag => false
141
+
142
+ assert_no_select 'form span input[type=radio][value=true]#user_active_true'
143
+ assert_no_select 'form span input[type=radio][value=false]#user_active_false'
144
+ end
145
+
146
+ test 'collection radio does not wrap each label/radio in the explicitly nil item wrapper tag' do
147
+ with_collection_radio @user, :active, [true, false], :to_s, :to_s, :item_wrapper_tag => nil
148
+
149
+ assert_no_select 'form span input[type=radio][value=true]#user_active_true'
150
+ assert_no_select 'form span input[type=radio][value=false]#user_active_false'
151
+ end
152
+
121
153
  test 'collection radio wrap items in a span tag by default' do
122
154
  with_collection_radio @user, :active, [true, false], :to_s, :to_s
123
155
 
@@ -246,6 +278,13 @@ class BuilderTest < ActionView::TestCase
246
278
  assert_select 'form ul input[type=checkbox][value=false]#user_active_false'
247
279
  end
248
280
 
281
+ test 'collection check box does not wrap the collection in the explicitly false collection wrapper tag' do
282
+ with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, :collection_wrapper_tag => false, :item_wrapper_tag => false
283
+
284
+ assert_select 'form > input[type=checkbox][value=true]#user_active_true'
285
+ assert_select 'form > input[type=checkbox][value=false]#user_active_false'
286
+ end
287
+
249
288
  test 'collection check box does not wrap the collection by default' do
250
289
  with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
251
290
 
@@ -268,6 +307,13 @@ class BuilderTest < ActionView::TestCase
268
307
  assert_select 'form li input[type=checkbox][value=false]#user_active_false'
269
308
  end
270
309
 
310
+ test 'collection check box does not wrapp each label/radio in the explicitly false item wrapper tag' do
311
+ with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s, :item_wrapper_tag => false
312
+
313
+ assert_select 'form > input[type=checkbox][value=true]#user_active_true'
314
+ assert_select 'form > input[type=checkbox][value=false]#user_active_false'
315
+ end
316
+
271
317
  test 'collection check box wrap items in a span tag by default' do
272
318
  with_collection_check_boxes @user, :active, [true, false], :to_s, :to_s
273
319
 
@@ -290,6 +336,19 @@ class BuilderTest < ActionView::TestCase
290
336
  end
291
337
  end
292
338
 
339
+ test 'fields for with a hash like model yeilds an instance of FormBuilder' do
340
+ @hash_backed_author = HashBackedAuthor.new
341
+
342
+ with_concat_form_for(:user) do |f|
343
+ f.simple_fields_for(:author, @hash_backed_author) do |author|
344
+ assert author.instance_of?(SimpleForm::FormBuilder)
345
+ author.input :name
346
+ end
347
+ end
348
+
349
+ assert_select "input[name='user[author][name]'][value='hash backed author']"
350
+ end
351
+
293
352
  test 'fields for yields an instance of CustomBuilder if main builder is a CustomBuilder' do
294
353
  with_custom_form_for(:user) do |f|
295
354
  f.simple_fields_for(:company) do |company|