simple_form 3.0.4 → 3.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -43
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +146 -71
  5. data/lib/generators/simple_form/install_generator.rb +2 -2
  6. data/lib/generators/simple_form/templates/README +3 -4
  7. data/lib/generators/simple_form/templates/config/initializers/simple_form.rb +19 -3
  8. data/lib/generators/simple_form/templates/config/initializers/simple_form_bootstrap.rb +83 -22
  9. data/lib/generators/simple_form/templates/config/initializers/simple_form_foundation.rb +1 -1
  10. data/lib/generators/simple_form/templates/config/locales/simple_form.en.yml +7 -2
  11. data/lib/simple_form.rb +38 -6
  12. data/lib/simple_form/action_view_extensions/form_helper.rb +1 -1
  13. data/lib/simple_form/components/errors.rb +27 -5
  14. data/lib/simple_form/components/hints.rb +2 -2
  15. data/lib/simple_form/components/html5.rb +1 -1
  16. data/lib/simple_form/components/label_input.rb +20 -2
  17. data/lib/simple_form/components/labels.rb +9 -5
  18. data/lib/simple_form/components/maxlength.rb +1 -1
  19. data/lib/simple_form/components/min_max.rb +1 -1
  20. data/lib/simple_form/components/pattern.rb +1 -1
  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/form_builder.rb +92 -59
  24. data/lib/simple_form/helpers.rb +5 -5
  25. data/lib/simple_form/inputs/base.rb +34 -12
  26. data/lib/simple_form/inputs/block_input.rb +1 -1
  27. data/lib/simple_form/inputs/boolean_input.rb +23 -13
  28. data/lib/simple_form/inputs/collection_input.rb +32 -9
  29. data/lib/simple_form/inputs/collection_radio_buttons_input.rb +6 -11
  30. data/lib/simple_form/inputs/collection_select_input.rb +4 -2
  31. data/lib/simple_form/inputs/date_time_input.rb +12 -2
  32. data/lib/simple_form/inputs/file_input.rb +4 -2
  33. data/lib/simple_form/inputs/grouped_collection_select_input.rb +15 -3
  34. data/lib/simple_form/inputs/hidden_input.rb +4 -2
  35. data/lib/simple_form/inputs/numeric_input.rb +5 -4
  36. data/lib/simple_form/inputs/password_input.rb +4 -2
  37. data/lib/simple_form/inputs/priority_input.rb +4 -2
  38. data/lib/simple_form/inputs/range_input.rb +1 -1
  39. data/lib/simple_form/inputs/string_input.rb +4 -2
  40. data/lib/simple_form/inputs/text_input.rb +4 -2
  41. data/lib/simple_form/railtie.rb +7 -0
  42. data/lib/simple_form/tags.rb +7 -0
  43. data/lib/simple_form/version.rb +1 -1
  44. data/lib/simple_form/wrappers.rb +1 -0
  45. data/lib/simple_form/wrappers/builder.rb +5 -5
  46. data/lib/simple_form/wrappers/leaf.rb +28 -0
  47. data/lib/simple_form/wrappers/many.rb +5 -6
  48. data/lib/simple_form/wrappers/root.rb +1 -1
  49. data/lib/simple_form/wrappers/single.rb +5 -3
  50. data/test/action_view_extensions/builder_test.rb +2 -2
  51. data/test/components/label_test.rb +1 -1
  52. data/test/form_builder/association_test.rb +17 -0
  53. data/test/form_builder/error_notification_test.rb +1 -1
  54. data/test/form_builder/error_test.rb +51 -32
  55. data/test/form_builder/general_test.rb +2 -2
  56. data/test/form_builder/input_field_test.rb +21 -37
  57. data/test/form_builder/label_test.rb +24 -1
  58. data/test/form_builder/wrapper_test.rb +67 -0
  59. data/test/generators/simple_form_generator_test.rb +2 -2
  60. data/test/inputs/boolean_input_test.rb +50 -2
  61. data/test/inputs/collection_check_boxes_input_test.rb +40 -11
  62. data/test/inputs/collection_radio_buttons_input_test.rb +76 -17
  63. data/test/inputs/collection_select_input_test.rb +108 -3
  64. data/test/inputs/datetime_input_test.rb +105 -38
  65. data/test/inputs/discovery_test.rb +12 -1
  66. data/test/inputs/grouped_collection_select_input_test.rb +36 -0
  67. data/test/inputs/string_input_test.rb +20 -0
  68. data/test/simple_form_test.rb +8 -0
  69. data/test/support/discovery_inputs.rb +12 -2
  70. data/test/support/misc_helpers.rb +46 -8
  71. data/test/support/models.rb +49 -24
  72. metadata +7 -7
@@ -1,3 +1,3 @@
1
1
  module SimpleForm
2
- VERSION = "3.0.4".freeze
2
+ VERSION = "3.1.0.rc1".freeze
3
3
  end
@@ -4,5 +4,6 @@ module SimpleForm
4
4
  autoload :Many, 'simple_form/wrappers/many'
5
5
  autoload :Root, 'simple_form/wrappers/root'
6
6
  autoload :Single, 'simple_form/wrappers/single'
7
+ autoload :Leaf, 'simple_form/wrappers/leaf'
7
8
  end
8
9
  end
@@ -45,20 +45,20 @@ module SimpleForm
45
45
  @components = []
46
46
  end
47
47
 
48
- def use(name, options=nil, &block)
48
+ def use(name, options = {}, &block)
49
49
  if options && wrapper = options[:wrap_with]
50
- @components << Single.new(name, wrapper)
50
+ @components << Single.new(name, wrapper, options.except(:wrap_with))
51
51
  else
52
- @components << name
52
+ @components << Leaf.new(name, options)
53
53
  end
54
54
  end
55
55
 
56
- def optional(name, options=nil, &block)
56
+ def optional(name, options = {}, &block)
57
57
  @options[name] = false
58
58
  use(name, options, &block)
59
59
  end
60
60
 
61
- def wrapper(name, options=nil)
61
+ def wrapper(name, options = nil)
62
62
  if block_given?
63
63
  name, options = nil, name if name.is_a?(Hash)
64
64
  builder = self.class.new(@options)
@@ -0,0 +1,28 @@
1
+ module SimpleForm
2
+ module Wrappers
3
+ class Leaf
4
+ attr_reader :namespace
5
+
6
+ def initialize(namespace, options = {})
7
+ @namespace = namespace
8
+ @options = options
9
+ end
10
+
11
+ def render(input)
12
+ method = input.method(@namespace)
13
+
14
+ if method.arity == 0
15
+ ActiveSupport::Deprecation.warn(SimpleForm::CUSTOM_INPUT_DEPRECATION_WARN % { name: @namespace })
16
+
17
+ method.call
18
+ else
19
+ method.call(@options)
20
+ end
21
+ end
22
+
23
+ def find(name)
24
+ self if @namespace == name
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,7 +1,7 @@
1
1
  module SimpleForm
2
2
  module Wrappers
3
3
  # A wrapper is an object that holds several components and render them.
4
- # A component may either be a symbol or any object that responds to `render`.
4
+ # A component may be any object that responds to `render`.
5
5
  # This API allows inputs/components to be easily wrapped, removing the
6
6
  # need to modify the code only to wrap input in an extra tag.
7
7
  #
@@ -10,9 +10,8 @@ module SimpleForm
10
10
  # on demand on input generation.
11
11
  class Many
12
12
  attr_reader :namespace, :defaults, :components
13
- alias :to_sym :namespace
14
13
 
15
- def initialize(namespace, components, defaults={})
14
+ def initialize(namespace, components, defaults = {})
16
15
  @namespace = namespace
17
16
  @components = components
18
17
  @defaults = defaults
@@ -25,8 +24,8 @@ module SimpleForm
25
24
  options = input.options
26
25
 
27
26
  components.each do |component|
28
- next if options[component] == false
29
- rendered = component.respond_to?(:render) ? component.render(input) : input.send(component)
27
+ next if options[component.namespace] == false
28
+ rendered = component.render(input)
30
29
  content.safe_concat rendered.to_s if rendered
31
30
  end
32
31
 
@@ -62,7 +61,7 @@ module SimpleForm
62
61
  end
63
62
 
64
63
  def html_options(options)
65
- options[:"#{namespace}_html"] || {}
64
+ (@defaults[:html] || {}).merge(options[:"#{namespace}_html"] || {})
66
65
  end
67
66
 
68
67
  def html_classes(input, options)
@@ -17,7 +17,7 @@ module SimpleForm
17
17
 
18
18
  # Provide a fallback if name cannot be found.
19
19
  def find(name)
20
- super || SimpleForm::Wrappers::Many.new(name, [name])
20
+ super || SimpleForm::Wrappers::Many.new(name, [Leaf.new(name)])
21
21
  end
22
22
 
23
23
  private
@@ -2,14 +2,16 @@ module SimpleForm
2
2
  module Wrappers
3
3
  # `Single` is an optimization for a wrapper that has only one component.
4
4
  class Single < Many
5
- def initialize(name, options={})
6
- super(name, [name], options)
5
+ def initialize(name, wrapper_options = {}, options = {})
6
+ @component = Leaf.new(name, options)
7
+
8
+ super(name, [@component], wrapper_options)
7
9
  end
8
10
 
9
11
  def render(input)
10
12
  options = input.options
11
13
  if options[namespace] != false
12
- content = input.send(namespace)
14
+ content = @component.render(input)
13
15
  wrap(input, options, content) if content
14
16
  end
15
17
  end
@@ -8,13 +8,13 @@ class BuilderTest < ActionView::TestCase
8
8
  end
9
9
  end
10
10
 
11
- def with_collection_radio_buttons(object, attribute, collection, value_method, text_method, options={}, html_options={}, &block)
11
+ def with_collection_radio_buttons(object, attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
12
12
  with_concat_form_for(object) do |f|
13
13
  f.collection_radio_buttons attribute, collection, value_method, text_method, options, html_options, &block
14
14
  end
15
15
  end
16
16
 
17
- def with_collection_check_boxes(object, attribute, collection, value_method, text_method, options={}, html_options={}, &block)
17
+ def with_collection_check_boxes(object, attribute, collection, value_method, text_method, options = {}, html_options = {}, &block)
18
18
  with_concat_form_for(object) do |f|
19
19
  f.collection_check_boxes attribute, collection, value_method, text_method, options, html_options, &block
20
20
  end
@@ -7,7 +7,7 @@ class IsolatedLabelTest < ActionView::TestCase
7
7
  SimpleForm::Inputs::Base.reset_i18n_cache :translate_required_html
8
8
  end
9
9
 
10
- def with_label_for(object, attribute_name, type, options={})
10
+ def with_label_for(object, attribute_name, type, options = {})
11
11
  with_concat_form_for(object) do |f|
12
12
  options[:reflection] = Association.new(Company, :company, {}) if options.delete(:setup_association)
13
13
  SimpleForm::Inputs::Base.new(f, attribute_name, nil, type, options).label
@@ -113,6 +113,14 @@ class AssociationTest < ActionView::TestCase
113
113
  assert_no_select 'form select option[value=3]'
114
114
  end
115
115
 
116
+ test 'builder allows collection to have a scope' do
117
+ with_association_for @user, :special_pictures
118
+ assert_select 'form select.select#user_special_picture_ids'
119
+ assert_select 'form select option[value=3]', '3'
120
+ assert_no_select 'form select option[value=1]'
121
+ assert_no_select 'form select option[value=2]'
122
+ end
123
+
116
124
  test 'builder marks the record which already belongs to the user' do
117
125
  @user.company_id = 2
118
126
  with_association_for @user, :company, as: :radio_buttons
@@ -146,6 +154,15 @@ class AssociationTest < ActionView::TestCase
146
154
  end
147
155
  end
148
156
 
157
+ test 'builder does not call order if the given association does not respond to it' do
158
+ with_association_for @user, :pictures
159
+ assert_select 'form select.select#user_picture_ids'
160
+ assert_select 'form select[multiple=multiple]'
161
+ assert_select 'form select option[value=1]', 'Picture 1'
162
+ assert_select 'form select option[value=2]', 'Picture 2'
163
+ assert_select 'form select option[value=3]', 'Picture 3'
164
+ end
165
+
149
166
  test 'builder creates a select with multiple options for collection associations' do
150
167
  with_association_for @user, :tags
151
168
  assert_select 'form select.select#user_tag_ids'
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
 
4
4
  # Tests for f.error_notification
5
5
  class ErrorNotificationTest < ActionView::TestCase
6
- def with_error_notification_for(object, options={}, &block)
6
+ def with_error_notification_for(object, options = {}, &block)
7
7
  with_concat_form_for(object) do |f|
8
8
  f.error_notification(options)
9
9
  end
@@ -14,10 +14,6 @@ class ErrorTest < ActionView::TestCase
14
14
  end
15
15
  end
16
16
 
17
- def with_custom_error_for(object, *args)
18
- with_form_for(object, *args)
19
- end
20
-
21
17
  test 'error should not generate content for attribute without errors' do
22
18
  with_error_for @user, :active
23
19
  assert_no_select 'span.error'
@@ -36,7 +32,7 @@ class ErrorTest < ActionView::TestCase
36
32
 
37
33
  test 'error should generate messages for attribute with single error' do
38
34
  with_error_for @user, :name
39
- assert_select 'span.error', "can&#39;t be blank"
35
+ assert_select 'span.error', "can't be blank"
40
36
  end
41
37
 
42
38
  test 'error should generate messages for attribute with one error when using first' do
@@ -86,21 +82,12 @@ class ErrorTest < ActionView::TestCase
86
82
 
87
83
  test 'error should escape error prefix text' do
88
84
  with_error_for @user, :name, error_prefix: '<b>Name</b>'
89
- assert_select 'span.error', "&lt;b&gt;Name&lt;/b&gt; can&#39;t be blank"
90
- end
91
-
92
- test 'error escapes error text' do
93
- @user.errors.merge!(action: ['must not contain <b>markup</b>'])
94
-
95
- with_error_for @user, :action
96
-
97
- assert_select 'span.error'
98
- assert_no_select 'span.error b', 'markup'
85
+ assert_select 'span.error', "&lt;b&gt;Name&lt;/b&gt; can't be blank"
99
86
  end
100
87
 
101
88
  test 'error should generate an error message with raw HTML tags' do
102
89
  with_error_for @user, :name, error_prefix: '<b>Name</b>'.html_safe
103
- assert_select 'span.error', "Name can&#39;t be blank"
90
+ assert_select 'span.error', "Name can't be blank"
104
91
  assert_select 'span.error b', "Name"
105
92
  end
106
93
 
@@ -108,7 +95,7 @@ class ErrorTest < ActionView::TestCase
108
95
 
109
96
  test 'full error should generate an full error tag for the attribute' do
110
97
  with_full_error_for @user, :name
111
- assert_select 'span.error', "Super User Name! can&#39;t be blank"
98
+ assert_select 'span.error', "Super User Name! can't be blank"
112
99
  end
113
100
 
114
101
  test 'full error should generate an full error tag with a clean HTML' do
@@ -118,13 +105,13 @@ class ErrorTest < ActionView::TestCase
118
105
 
119
106
  test 'full error should allow passing options to full error tag' do
120
107
  with_full_error_for @user, :name, id: 'name_error', error_prefix: "Your name"
121
- assert_select 'span.error#name_error', "Your name can&#39;t be blank"
108
+ assert_select 'span.error#name_error', "Your name can't be blank"
122
109
  end
123
110
 
124
111
  test 'full error should not modify the options hash' do
125
112
  options = { id: 'name_error' }
126
113
  with_full_error_for @user, :name, options
127
- assert_select 'span.error#name_error', "Super User Name! can&#39;t be blank"
114
+ assert_select 'span.error#name_error', "Super User Name! can't be blank"
128
115
  assert_equal({ id: 'name_error' }, options)
129
116
  end
130
117
 
@@ -133,36 +120,68 @@ class ErrorTest < ActionView::TestCase
133
120
  test 'error with custom wrappers works' do
134
121
  swap_wrapper do
135
122
  with_error_for @user, :name
136
- assert_select 'span.omg_error', "can&#39;t be blank"
123
+ assert_select 'span.omg_error', "can't be blank"
124
+ end
125
+ end
126
+
127
+ # FULL_ERROR_WRAPPER
128
+
129
+ test 'full error should find errors on association' do
130
+ swap_wrapper :default, self.custom_wrapper_with_full_error do
131
+ with_form_for @user, :company_id, as: :select
132
+ assert_select 'span.error', 'Company must be valid'
133
+ end
134
+ end
135
+
136
+ test 'full error can be disabled' do
137
+ swap_wrapper :default, self.custom_wrapper_with_full_error do
138
+ with_form_for @user, :company_id, as: :select, full_error: false
139
+ assert_no_select 'span.error'
140
+ end
141
+ end
142
+
143
+ test 'full error can be disabled setting error to false' do
144
+ swap_wrapper :default, self.custom_wrapper_with_full_error do
145
+ with_form_for @user, :company_id, as: :select, error: false
146
+ assert_no_select 'span.error'
137
147
  end
138
148
  end
139
149
 
140
150
  # CUSTOM ERRORS
141
151
 
142
152
  test 'input with custom error works' do
143
- with_custom_error_for(@user, :name, error: "Super User Name! can't be blank")
153
+ error_text = "Super User Name! can't be blank"
154
+ with_form_for @user, :name, error: error_text
144
155
 
145
- assert_select 'span.error', "Super User Name! can&#39;t be blank"
156
+ assert_select 'span.error', error_text
157
+ end
158
+
159
+ test 'input with error option as true does not use custom error' do
160
+ with_form_for @user, :name, error: true
161
+
162
+ assert_select 'span.error', "can't be blank"
146
163
  end
147
164
 
148
165
  test 'input with custom error does not generate the error if there is no error on the attribute' do
149
- error_text = "Super User Active! can't be blank"
150
- with_form_for @user, :active, error: error_text
166
+ with_form_for @user, :active, error: "Super User Active! can't be blank"
151
167
 
152
168
  assert_no_select 'span.error'
153
169
  end
154
170
 
155
- test 'input with custom error escapes the error text' do
156
- with_form_for @user, :name, error: 'error must not contain <b>markup</b>'
171
+ test 'input with custom error works when using full_error component' do
172
+ swap_wrapper :default, self.custom_wrapper_with_full_error do
173
+ error_text = "Super User Name! can't be blank"
174
+ with_form_for @user, :name, error: error_text
157
175
 
158
- assert_select 'span.error'
159
- assert_no_select 'span.error b', 'markup'
176
+ assert_select 'span.error', error_text
177
+ end
160
178
  end
161
179
 
162
- test 'input with custom error does not escape the error text if it is safe' do
163
- with_form_for @user, :name, error: 'error must contain <b>markup</b>'.html_safe
180
+ test 'input with custom error when using full_error component does not generate the error if there is no error on the attribute' do
181
+ swap_wrapper :default, self.custom_wrapper_with_full_error do
182
+ with_form_for @user, :active, error: "Super User Active! can't be blank"
164
183
 
165
- assert_select 'span.error'
166
- assert_select 'span.error b', 'markup'
184
+ assert_no_select 'span.error'
185
+ end
167
186
  end
168
187
  end
@@ -302,7 +302,7 @@ class FormBuilderTest < ActionView::TestCase
302
302
 
303
303
  test 'builder should generate errors for attribute with errors' do
304
304
  with_form_for @user, :name
305
- assert_select 'span.error', "can&#39;t be blank"
305
+ assert_select 'span.error', "can't be blank"
306
306
  end
307
307
 
308
308
  test 'builder should be able to disable showing errors for a input' do
@@ -312,7 +312,7 @@ class FormBuilderTest < ActionView::TestCase
312
312
 
313
313
  test 'builder should pass options to errors' do
314
314
  with_form_for @user, :name, error_html: { id: "cool" }
315
- assert_select 'span.error#cool', "can&#39;t be blank"
315
+ assert_select 'span.error#cool', "can't be blank"
316
316
  end
317
317
 
318
318
  test 'placeholder should not be generated when set to false' do
@@ -88,14 +88,30 @@ class InputFieldTest < ActionView::TestCase
88
88
  assert_select 'input[min=18]'
89
89
  end
90
90
 
91
- test 'builder input_field should use pattern component' do
91
+ test 'builder input_field should not use pattern component by default' do
92
92
  with_concat_form_for(@other_validating_user) do |f|
93
93
  f.input_field :country, as: :string
94
94
  end
95
95
 
96
+ assert_no_select 'input[pattern="\w+"]'
97
+ end
98
+
99
+ test 'builder input_field should infer pattern from attributes' do
100
+ with_concat_form_for(@other_validating_user) do |f|
101
+ f.input_field :country, as: :string, pattern: true
102
+ end
103
+
96
104
  assert_select 'input[pattern="\w+"]'
97
105
  end
98
106
 
107
+ test 'builder input_field should accept custom patter' do
108
+ with_concat_form_for(@other_validating_user) do |f|
109
+ f.input_field :country, as: :string, pattern: '\d+'
110
+ end
111
+
112
+ assert_select 'input[pattern="\d+"]'
113
+ end
114
+
99
115
  test 'builder input_field should use readonly component' do
100
116
  with_concat_form_for(@other_validating_user) do |f|
101
117
  f.input_field :age, as: :integer, readonly: true
@@ -123,43 +139,11 @@ class InputFieldTest < ActionView::TestCase
123
139
  assert_no_select 'select.status[value_method]'
124
140
  end
125
141
 
126
- test 'build input_field without pattern component use the pattern string' do
127
- swap_wrapper :default, self.custom_wrapper_with_html5_components do
128
- with_concat_form_for(@user) do |f|
129
- f.input_field :name, pattern: '\w+'
130
- end
131
-
132
- assert_select 'input[pattern="\w+"]'
133
- end
134
- end
135
-
136
- test 'build input_field without placeholder component use the placeholder string' do
137
- swap_wrapper :default, self.custom_wrapper_with_html5_components do
138
- with_concat_form_for(@user) do |f|
139
- f.input_field :name, placeholder: 'Placeholder'
140
- end
141
-
142
- assert_select 'input[placeholder="Placeholder"]'
143
- end
144
- end
145
-
146
- test 'build input_field without maxlength component use the maxlength string' do
147
- swap_wrapper :default, self.custom_wrapper_with_html5_components do
148
- with_concat_form_for(@user) do |f|
149
- f.input_field :name, maxlength: 5
150
- end
151
-
152
- assert_select 'input[maxlength="5"]'
142
+ test 'build input_field does not treat "boolean_style" as an HTML attribute' do
143
+ with_concat_form_for(@user) do |f|
144
+ f.input_field :active, boolean_style: :nested
153
145
  end
154
- end
155
146
 
156
- test 'build input_field without readonly component use the readonly string' do
157
- swap_wrapper :default, self.custom_wrapper_with_html5_components do
158
- with_concat_form_for(@user) do |f|
159
- f.input_field :name, readonly: true
160
- end
161
-
162
- assert_select 'input[readonly="readonly"]'
163
- end
147
+ assert_no_select 'input.boolean[boolean_style]'
164
148
  end
165
149
  end