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
@@ -47,6 +47,16 @@ class FormHelperTest < ActionView::TestCase
47
47
  assert_select 'form.simple_form.user'
48
48
  end
49
49
 
50
+ test 'simple form should not add object class to form if css_class is specified' do
51
+ concat(simple_form_for(:user, :html => {:class => nil}) do |f| end)
52
+ assert_no_select 'form.user'
53
+ end
54
+
55
+ test 'simple form should add custom class to form if css_class is specified' do
56
+ concat(simple_form_for(:user, :html => {:class => 'my_class'}) do |f| end)
57
+ assert_select 'form.my_class'
58
+ end
59
+
50
60
  test 'pass options to simple form' do
51
61
  concat(simple_form_for(:user, :url => '/account', :html => { :id => 'my_form' }) do |f| end)
52
62
  assert_select 'form#my_form'
@@ -58,4 +68,15 @@ class FormHelperTest < ActionView::TestCase
58
68
  assert f.instance_of?(SimpleForm::FormBuilder)
59
69
  end)
60
70
  end
71
+
72
+ test 'fields for with a hash like model yeilds an instance of FormBuilder' do
73
+ @hash_backed_author = HashBackedAuthor.new
74
+
75
+ concat(simple_fields_for(:author, @hash_backed_author) do |f|
76
+ assert f.instance_of?(SimpleForm::FormBuilder)
77
+ f.input :name
78
+ end)
79
+
80
+ assert_select "input[name='author[name]'][value='hash backed author']"
81
+ end
61
82
  end
@@ -14,8 +14,8 @@ class CustomizedInput < SimpleForm::Inputs::StringInput
14
14
  def input
15
15
  "<section>#{super}</section>".html_safe
16
16
  end
17
-
17
+
18
18
  def input_method
19
19
  :text_field
20
20
  end
21
- end
21
+ end
@@ -338,6 +338,8 @@ class FormBuilderTest < ActionView::TestCase
338
338
  assert_select 'input.optional#user_name'
339
339
  end
340
340
 
341
+ # VALIDATORS :if :unless
342
+
341
343
  test 'builder input should not be required when ActiveModel::Validations is included and if option is present' do
342
344
  with_form_for @validating_user, :age
343
345
  assert_no_select 'input.required'
@@ -352,7 +354,53 @@ class FormBuilderTest < ActionView::TestCase
352
354
  assert_select 'input.optional#validating_user_amount'
353
355
  end
354
356
 
357
+ # VALIDATORS :on
358
+
359
+ test 'builder input should be required when validation is on create and is not persisted' do
360
+ @validating_user.new_record!
361
+ with_form_for @validating_user, :action
362
+ assert_select 'input.required'
363
+ assert_select 'input[required]'
364
+ assert_select 'input.required[required]#validating_user_action'
365
+ end
366
+
367
+ test 'builder input should not be required when validation is on create and is persisted' do
368
+ with_form_for @validating_user, :action
369
+ assert_no_select 'input.required'
370
+ assert_no_select 'input[required]'
371
+ assert_select 'input.optional#validating_user_action'
372
+ end
373
+
374
+ test 'builder input should be required when validation is on save' do
375
+ with_form_for @validating_user, :credit_limit
376
+ assert_select 'input.required'
377
+ assert_select 'input[required]'
378
+ assert_select 'input.required[required]#validating_user_credit_limit'
379
+
380
+ @validating_user.new_record!
381
+ with_form_for @validating_user, :credit_limit
382
+ assert_select 'input.required'
383
+ assert_select 'input[required]'
384
+ assert_select 'input.required[required]#validating_user_credit_limit'
385
+ end
386
+
387
+ test 'builder input should be required when validation is on update and is persisted' do
388
+ with_form_for @validating_user, :phone_number
389
+ assert_select 'input.required'
390
+ assert_select 'input[required]'
391
+ assert_select 'input.required[required]#validating_user_phone_number'
392
+ end
393
+
394
+ test 'builder input should not be required when validation is on update and is not persisted' do
395
+ @validating_user.new_record!
396
+ with_form_for @validating_user, :phone_number
397
+ assert_no_select 'input.required'
398
+ assert_no_select 'input[required]'
399
+ assert_select 'input.optional#validating_user_phone_number'
400
+ end
401
+
355
402
  # WRAPPERS
403
+
356
404
  test 'builder support wrapping around an specific tag' do
357
405
  swap SimpleForm, :wrapper_tag => :p do
358
406
  with_form_for @user, :name
@@ -361,6 +409,12 @@ class FormBuilderTest < ActionView::TestCase
361
409
  end
362
410
  end
363
411
 
412
+ test 'builder support no wrapping when wrapper is false' do
413
+ with_form_for @user, :name, :wrapper => false
414
+ assert_select 'form > label[for=user_name]'
415
+ assert_select 'form > input#user_name.string'
416
+ end
417
+
364
418
  test 'builder wrapping tag adds default css classes' do
365
419
  swap SimpleForm, :wrapper_tag => :p do
366
420
  with_form_for @user, :name
data/test/inputs_test.rb CHANGED
@@ -141,9 +141,9 @@ class InputTest < ActionView::TestCase
141
141
  assert_select "input#user_password.password[type=password][name='user[password]']"
142
142
  end
143
143
 
144
- test 'input should use default text size for decimal attributes' do
144
+ test 'input should not use size attribute for decimal attributes' do
145
145
  with_input_for @user, :credit_limit, :decimal
146
- assert_select 'input.decimal[size=50]'
146
+ assert_no_select 'input.decimal[size]'
147
147
  end
148
148
 
149
149
  test 'input should get maxlength from column definition for string attributes' do
@@ -151,6 +151,11 @@ class InputTest < ActionView::TestCase
151
151
  assert_select 'input.string[maxlength=100]'
152
152
  end
153
153
 
154
+ test 'input should not get maxlength from column without size definition for string attributes' do
155
+ with_input_for @user, :action, :string
156
+ assert_no_select 'input.string[maxlength]'
157
+ end
158
+
154
159
  test 'input should get size from column definition for string attributes respecting maximum value' do
155
160
  with_input_for @user, :name, :string
156
161
  assert_select 'input.string[size=50]'
@@ -166,6 +171,11 @@ class InputTest < ActionView::TestCase
166
171
  assert_select 'input.password[type=password][maxlength=100]'
167
172
  end
168
173
 
174
+ test 'input should infer maxlength column definition from validation when present' do
175
+ with_input_for @validating_user, :name, :string
176
+ assert_select 'input.string[maxlength=25]'
177
+ end
178
+
169
179
  test 'when not using HTML5, does not show maxlength attribute' do
170
180
  swap SimpleForm, :html5 => false do
171
181
  with_input_for @user, :password, :password
@@ -173,6 +183,13 @@ class InputTest < ActionView::TestCase
173
183
  end
174
184
  end
175
185
 
186
+ test 'when not using HTML5, does not show maxlength attribute with validating lenght attribute' do
187
+ swap SimpleForm, :html5 => false do
188
+ with_input_for @validating_user, :name, :string
189
+ assert_no_select 'input.string[maxlength]'
190
+ end
191
+ end
192
+
176
193
  test 'input should not generate placeholder by default' do
177
194
  with_input_for @user, :name, :string
178
195
  assert_no_select 'input[placeholder]'
@@ -212,6 +229,22 @@ class InputTest < ActionView::TestCase
212
229
  end
213
230
  end
214
231
 
232
+ test 'input should infer pattern from attributes when pattern is true' do
233
+ with_input_for @other_validating_user, :country, :string, :pattern => true
234
+ assert_select 'input[pattern="\w+"]'
235
+ end
236
+
237
+ test 'input should use given pattern from attributes' do
238
+ with_input_for @other_validating_user, :country, :string, :pattern => "\\d+"
239
+ assert_select 'input[pattern="\d+"]'
240
+ end
241
+
242
+ test 'input should fail if pattern is true but no pattern exists' do
243
+ assert_raise RuntimeError do
244
+ with_input_for @other_validating_user, :name, :string, :pattern => true
245
+ end
246
+ end
247
+
215
248
  # NumericInput
216
249
  test 'input should generate an integer text field for integer attributes ' do
217
250
  with_input_for @user, :age, :integer
@@ -345,16 +378,28 @@ class InputTest < ActionView::TestCase
345
378
  end
346
379
  end
347
380
 
381
+ [:integer, :float, :decimal].each do |type|
382
+ test "#{type} input should infer min value from attributes with greater than or equal validation" do
383
+ with_input_for @validating_user, :age, type
384
+ assert_select 'input[min=18]'
385
+ end
386
+
387
+ test "#{type} input should infer the max value from attributes with less than or equal to validation" do
388
+ with_input_for @validating_user, :age, type
389
+ assert_select 'input[max=99]'
390
+ end
391
+ end
392
+
348
393
  # Numeric input but HTML5 disabled
349
- test ' when not using HTML5 input should not generate field with type number and use text instead' do
394
+ test 'when not using HTML5 input should not generate field with type number and use text instead' do
350
395
  swap SimpleForm, :html5 => false do
351
396
  with_input_for @user, :age, :integer
352
397
  assert_no_select "input[type=number]"
353
- assert_no_select "input#user_age[text]"
398
+ assert_select "input#user_age[type=text]"
354
399
  end
355
400
  end
356
401
 
357
- test 'when not using HTML5 input should not use min or max or step attributes' do
402
+ test 'when not using HTML5 input should not use min or max or step attributes for numeric type' do
358
403
  swap SimpleForm, :html5 => false do
359
404
  with_input_for @validating_user, :age, :integer
360
405
  assert_no_select "input[min]"
@@ -363,15 +408,63 @@ class InputTest < ActionView::TestCase
363
408
  end
364
409
  end
365
410
 
366
- [:integer, :float, :decimal].each do |type|
367
- test "#{type} input should infer min value from attributes with greater than or equal validation" do
368
- with_input_for @validating_user, :age, type
369
- assert_select 'input[min=18]'
411
+ # RangeInput
412
+ test 'range input generates a input type range, based on numeric input' do
413
+ with_input_for @user, :age, :range
414
+ assert_select "input#user_age.range[type=range]"
415
+ end
416
+
417
+ test 'range input does not generate placeholder' do
418
+ with_input_for @user, :age, :range, :placeholder => "Select your age"
419
+ assert_select "input[type=range]"
420
+ assert_no_select "input[placeholder]"
421
+ end
422
+
423
+ test 'range input allows givin min and max attributes' do
424
+ with_input_for @user, :age, :range, :input_html => { :min => 10, :max => 50 }
425
+ assert_select "input[type=range][min=10][max=50]"
426
+ end
427
+
428
+ test 'range input infers min and max attributes from validations' do
429
+ with_input_for @validating_user, :age, :range
430
+ assert_select "input[type=range][min=18][max=99]"
431
+ end
432
+
433
+ test 'range input add default step attribute' do
434
+ with_input_for @validating_user, :age, :range
435
+ assert_select "input[type=range][step=1]"
436
+ end
437
+
438
+ test 'range input allows givin a step through input html options' do
439
+ with_input_for @validating_user, :age, :range, :input_html => { :step => 2 }
440
+ assert_select "input[type=range][step=2]"
441
+ end
442
+
443
+ test 'range input should not generate min attribute by default' do
444
+ with_input_for @user, :age, :range
445
+ assert_no_select 'input[min]'
446
+ end
447
+
448
+ test 'range input should not generate max attribute by default' do
449
+ with_input_for @user, :age, :range
450
+ assert_no_select 'input[max]'
451
+ end
452
+
453
+ # RangeInput iwth HTML5 disabled
454
+ test 'when not using HTML5, range input does not generate field with range type, and use text instead' do
455
+ swap SimpleForm, :html5 => false do
456
+ with_input_for @user, :age, :range
457
+ assert_no_select "input[type=number]"
458
+ assert_select "input[type=text]"
370
459
  end
460
+ end
371
461
 
372
- test "#{type} input should infer the max value from attributes with less than or equal to validation" do
373
- with_input_for @validating_user, :age, type
374
- assert_select 'input[max=99]'
462
+ test 'when not using HTML5, range input should not use min or max or step attributes' do
463
+ swap SimpleForm, :html5 => false do
464
+ with_input_for @validating_user, :age, :range
465
+ assert_no_select "input[min]"
466
+ assert_no_select "input[max]"
467
+ assert_no_select "input[step]"
375
468
  end
376
469
  end
377
470
 
@@ -393,6 +486,30 @@ class InputTest < ActionView::TestCase
393
486
  assert_select 'textarea.text[placeholder=Put in some text]'
394
487
  end
395
488
 
489
+ test 'input should get maxlength from column definition for text attributes' do
490
+ with_input_for @user, :description, :text
491
+ assert_select 'textarea.text[maxlength=200]'
492
+ end
493
+
494
+ test 'input should infer maxlength column definition from validation when present for text attributes' do
495
+ with_input_for @validating_user, :description, :text
496
+ assert_select 'textarea.text[maxlength=50]'
497
+ end
498
+
499
+ test 'when not using HTML5, does not show maxlength attribute for text attributes' do
500
+ swap SimpleForm, :html5 => false do
501
+ with_input_for @user, :description, :text
502
+ assert_no_select 'textarea.text[maxlength]'
503
+ end
504
+ end
505
+
506
+ test 'when not using HTML5, does not show maxlength attribute with validating lenght text attribute' do
507
+ swap SimpleForm, :html5 => false do
508
+ with_input_for @validating_user, :name, :string
509
+ assert_no_select 'input.string[maxlength]'
510
+ end
511
+ end
512
+
396
513
  test 'input should generate a file field' do
397
514
  with_input_for @user, :name, :file
398
515
  assert_select 'input#user_name[type=file]'
@@ -403,14 +520,6 @@ class InputTest < ActionView::TestCase
403
520
  assert_no_select 'input[placeholder]'
404
521
  end
405
522
 
406
- test 'mapping input should generate an error if type is not found' do
407
- with_concat_form_for(@user) do |f|
408
- assert_raise(RuntimeError, "Could not find method for nil") do
409
- SimpleForm::Inputs::MappingInput.new(f, "unknown", nil, nil, {}).input
410
- end
411
- end
412
- end
413
-
414
523
  # HiddenInput
415
524
  test 'input should generate a hidden field' do
416
525
  with_input_for @user, :name, :hidden
@@ -674,6 +783,62 @@ class InputTest < ActionView::TestCase
674
783
  assert_select 'select option[value=2]', 'Carlos'
675
784
  end
676
785
 
786
+ test 'input should disable the anothers components when the option is a object' do
787
+ with_input_for @user, :description, :select, :collection => ["Jose", "Carlos"], :disabled => true
788
+ assert_no_select 'select option[value=Jose][disabled=disabled]', 'Jose'
789
+ assert_no_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
790
+ assert_select 'select[disabled=disabled]'
791
+ assert_select 'div.disabled'
792
+ end
793
+
794
+ test 'input should not disable the anothers components when the option is a object' do
795
+ with_input_for @user, :description, :select, :collection => ["Jose", "Carlos"], :disabled => 'Jose'
796
+ assert_select 'select option[value=Jose][disabled=disabled]', 'Jose'
797
+ assert_no_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
798
+ assert_no_select 'select[disabled=disabled]'
799
+ assert_no_select 'div.disabled'
800
+ end
801
+
802
+ test 'input should allow disabled options with a lambda for collection select' do
803
+ with_input_for @user, :name, :select, :collection => ["Carlos", "Antonio"],
804
+ :disabled => lambda { |x| x == "Carlos" }
805
+ assert_select 'select option[value=Carlos][disabled=disabled]', 'Carlos'
806
+ assert_select 'select option[value=Antonio]', 'Antonio'
807
+ assert_no_select 'select option[value=Antonio][disabled]'
808
+ end
809
+
810
+ test 'input should allow disabled and label method with lambdas for collection select' do
811
+ with_input_for @user, :name, :select, :collection => ["Carlos", "Antonio"],
812
+ :disabled => lambda { |x| x == "Carlos" }, :label_method => lambda { |x| x.upcase }
813
+ assert_select 'select option[value=Carlos][disabled=disabled]', 'CARLOS'
814
+ assert_select 'select option[value=Antonio]', 'ANTONIO'
815
+ assert_no_select 'select option[value=Antonio][disabled]'
816
+ end
817
+
818
+ test 'input should allow a non lambda disabled option with lambda label method for collections' do
819
+ with_input_for @user, :name, :select, :collection => ["Carlos", "Antonio"],
820
+ :disabled => "Carlos", :label_method => lambda { |x| x.upcase }
821
+ assert_select 'select option[value=Carlos][disabled=disabled]', 'CARLOS'
822
+ assert_select 'select option[value=Antonio]', 'ANTONIO'
823
+ assert_no_select 'select option[value=Antonio][disabled]'
824
+ end
825
+
826
+ test 'input should allow selected and label method with lambdas for collection select' do
827
+ with_input_for @user, :name, :select, :collection => ["Carlos", "Antonio"],
828
+ :selected => lambda { |x| x == "Carlos" }, :label_method => lambda { |x| x.upcase }
829
+ assert_select 'select option[value=Carlos][selected=selected]', 'CARLOS'
830
+ assert_select 'select option[value=Antonio]', 'ANTONIO'
831
+ assert_no_select 'select option[value=Antonio][selected]'
832
+ end
833
+
834
+ test 'input should allow a non lambda selected option with lambda label method for collection select' do
835
+ with_input_for @user, :name, :select, :collection => ["Carlos", "Antonio"],
836
+ :selected => "Carlos", :label_method => lambda { |x| x.upcase }
837
+ assert_select 'select option[value=Carlos][selected=selected]', 'CARLOS'
838
+ assert_select 'select option[value=Antonio]', 'ANTONIO'
839
+ assert_no_select 'select option[value=Antonio][selected]'
840
+ end
841
+
677
842
  test 'input should allow overriding collection for radio types' do
678
843
  with_input_for @user, :name, :radio, :collection => ['Jose', 'Carlos']
679
844
  assert_select 'input[type=radio][value=Jose]'
@@ -42,7 +42,7 @@ class User
42
42
  :description, :created_at, :updated_at, :credit_limit, :password, :url,
43
43
  :delivery_time, :born_at, :special_company_id, :country, :tags, :tag_ids,
44
44
  :avatar, :home_picture, :email, :status, :residence_country, :phone_number,
45
- :post_count, :lock_version, :amount, :attempts
45
+ :post_count, :lock_version, :amount, :attempts, :action
46
46
 
47
47
  def initialize(options={})
48
48
  options.each do |key, value|
@@ -67,7 +67,7 @@ class User
67
67
  def column_for_attribute(attribute)
68
68
  column_type, limit = case attribute.to_sym
69
69
  when :name, :status, :password then [:string, 100]
70
- when :description then :text
70
+ when :description then [:text, 200]
71
71
  when :age then :integer
72
72
  when :credit_limit then [:decimal, 15]
73
73
  when :active then :boolean
@@ -79,6 +79,7 @@ class User
79
79
  when :home_picture then :string
80
80
  when :amount then :integer
81
81
  when :attempts then :integer
82
+ when :action then :string
82
83
  end
83
84
  Column.new(attribute, column_type, limit)
84
85
  end
@@ -129,6 +130,11 @@ class ValidatingUser < User
129
130
  validates :company, :presence => true
130
131
  validates :age, :presence => true, :if => Proc.new { |user| user.name }
131
132
  validates :amount, :presence => true, :unless => Proc.new { |user| user.age }
133
+
134
+ validates :action, :presence => true, :on => :create
135
+ validates :credit_limit, :presence => true, :on => :save
136
+ validates :phone_number, :presence => true, :on => :update
137
+
132
138
  validates_numericality_of :age,
133
139
  :greater_than_or_equal_to => 18,
134
140
  :less_than_or_equal_to => 99,
@@ -141,6 +147,8 @@ class ValidatingUser < User
141
147
  :greater_than_or_equal_to => :min_attempts,
142
148
  :less_than_or_equal_to => :max_attempts,
143
149
  :only_integer => true
150
+ validates_length_of :name, :maximum => 25
151
+ validates_length_of :description, :maximum => 50
144
152
 
145
153
  def min_amount
146
154
  10
@@ -173,4 +181,17 @@ class OtherValidatingUser < User
173
181
  :greater_than_or_equal_to => Proc.new { |user| user.age },
174
182
  :less_than_or_equal_to => Proc.new { |user| user.age + 100},
175
183
  :only_integer => true
184
+
185
+ validates_format_of :country, :with => /\w+/
186
+ end
187
+
188
+ class HashBackedAuthor < Hash
189
+ extend ActiveModel::Naming
190
+ include ActiveModel::Conversion
191
+
192
+ def persisted?; false; end
193
+
194
+ def name
195
+ 'hash backed author'
196
+ end
176
197
  end