reform 2.3.0.rc1 → 2.3.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +26 -11
  6. data/CHANGES.md +25 -2
  7. data/Gemfile +6 -3
  8. data/ISSUE_TEMPLATE.md +1 -1
  9. data/README.md +2 -4
  10. data/Rakefile +18 -9
  11. data/lib/reform/contract.rb +7 -7
  12. data/lib/reform/contract/custom_error.rb +41 -0
  13. data/lib/reform/contract/validate.rb +9 -5
  14. data/lib/reform/errors.rb +27 -15
  15. data/lib/reform/form.rb +22 -11
  16. data/lib/reform/form/call.rb +1 -1
  17. data/lib/reform/form/composition.rb +2 -2
  18. data/lib/reform/form/dry.rb +10 -86
  19. data/lib/reform/form/dry/input_hash.rb +37 -0
  20. data/lib/reform/form/dry/new_api.rb +58 -0
  21. data/lib/reform/form/dry/old_api.rb +61 -0
  22. data/lib/reform/form/populator.rb +9 -11
  23. data/lib/reform/form/prepopulate.rb +3 -2
  24. data/lib/reform/form/validate.rb +19 -12
  25. data/lib/reform/result.rb +36 -9
  26. data/lib/reform/validation.rb +10 -8
  27. data/lib/reform/validation/groups.rb +2 -3
  28. data/lib/reform/version.rb +1 -1
  29. data/reform.gemspec +10 -9
  30. data/test/benchmarking.rb +10 -11
  31. data/test/call_new_api.rb +23 -0
  32. data/test/{call_test.rb → call_old_api.rb} +3 -3
  33. data/test/changed_test.rb +7 -7
  34. data/test/coercion_test.rb +50 -18
  35. data/test/composition_new_api.rb +186 -0
  36. data/test/{composition_test.rb → composition_old_api.rb} +23 -26
  37. data/test/contract/custom_error_test.rb +55 -0
  38. data/test/contract_new_api.rb +77 -0
  39. data/test/{contract_test.rb → contract_old_api.rb} +8 -8
  40. data/test/default_test.rb +1 -1
  41. data/test/deserialize_test.rb +8 -11
  42. data/test/errors_new_api.rb +225 -0
  43. data/test/errors_old_api.rb +230 -0
  44. data/test/feature_test.rb +7 -9
  45. data/test/fixtures/dry_error_messages.yml +5 -2
  46. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  47. data/test/form_new_api.rb +57 -0
  48. data/test/{form_test.rb → form_old_api.rb} +2 -2
  49. data/test/form_option_new_api.rb +24 -0
  50. data/test/{form_option_test.rb → form_option_old_api.rb} +1 -1
  51. data/test/from_test.rb +8 -12
  52. data/test/inherit_new_api.rb +105 -0
  53. data/test/{inherit_test.rb → inherit_old_api.rb} +10 -17
  54. data/test/module_new_api.rb +137 -0
  55. data/test/{module_test.rb → module_old_api.rb} +19 -15
  56. data/test/parse_option_test.rb +5 -5
  57. data/test/parse_pipeline_test.rb +2 -2
  58. data/test/populate_new_api.rb +304 -0
  59. data/test/{populate_test.rb → populate_old_api.rb} +28 -34
  60. data/test/populator_skip_test.rb +1 -2
  61. data/test/prepopulator_test.rb +5 -6
  62. data/test/read_only_test.rb +12 -1
  63. data/test/readable_test.rb +5 -5
  64. data/test/reform_new_api.rb +204 -0
  65. data/test/{reform_test.rb → reform_old_api.rb} +17 -23
  66. data/test/save_new_api.rb +101 -0
  67. data/test/{save_test.rb → save_old_api.rb} +10 -13
  68. data/test/setup_test.rb +6 -6
  69. data/test/{skip_if_test.rb → skip_if_new_api.rb} +20 -9
  70. data/test/skip_if_old_api.rb +92 -0
  71. data/test/skip_setter_and_getter_test.rb +2 -3
  72. data/test/test_helper.rb +13 -5
  73. data/test/validate_new_api.rb +408 -0
  74. data/test/{validate_test.rb → validate_old_api.rb} +43 -53
  75. data/test/validation/dry_validation_new_api.rb +826 -0
  76. data/test/validation/{dry_validation_test.rb → dry_validation_old_api.rb} +223 -116
  77. data/test/validation/result_test.rb +20 -22
  78. data/test/validation_library_provided_test.rb +3 -3
  79. data/test/virtual_test.rb +46 -6
  80. data/test/writeable_test.rb +7 -7
  81. metadata +101 -51
  82. data/test/errors_test.rb +0 -180
  83. data/test/readonly_test.rb +0 -14
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "test_helper"
3
2
  require "reform/form/dry"
4
3
  require "reform/form/coercion"
@@ -41,74 +40,73 @@ class DryValidationErrorsAPITest < Minitest::Spec
41
40
 
42
41
  validation do
43
42
  configure do
44
- config.messages_file = 'test/fixtures/dry_error_messages.yml'
43
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
45
44
  end
46
45
 
47
46
  required(:title).filled
48
47
  end
49
48
  end
50
-
51
49
  end
52
50
 
53
- let (:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
51
+ let(:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
54
52
 
55
53
  it "everything wrong" do
56
- result = form.({ title: nil, artist: { email: "" }, songs: [{ title: "Clams have feelings too" }, { title: "" }] })
54
+ result = form.(title: nil, artist: {email: ""}, songs: [{title: "Clams have feelings too"}, {title: ""}])
57
55
 
58
56
  result.success?.must_equal false
59
57
 
60
58
  # errors.messages
61
- form.errors.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"], :"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
62
- form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
63
- form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
59
+ form.errors.messages.must_equal(title: ["must be filled", "size cannot be less than 2"], "artist.email": ["must be filled"], "artist.label.location": ["must be filled"], "songs.title": ["must be filled"])
60
+ form.artist.errors.messages.must_equal(email: ["must be filled"], "label.location": ["must be filled"])
61
+ form.artist.label.errors.messages.must_equal(location: ["must be filled"])
64
62
  form.songs[0].errors.messages.must_equal({})
65
- form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
63
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
66
64
 
67
65
  # #errors[]
68
- form.errors[:nonsense].must_be_nil
66
+ form.errors[:nonsense].must_equal []
69
67
  form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
70
68
  form.artist.errors[:email].must_equal ["must be filled"]
71
69
  form.artist.label.errors[:location].must_equal ["must be filled"]
72
- form.songs[0].errors[:title].must_be_nil
70
+ form.songs[0].errors[:title].must_equal []
73
71
  form.songs[1].errors[:title].must_equal ["must be filled"]
74
72
 
75
73
  # #to_result
76
- form.to_result.errors.must_equal({:title=>["must be filled"]})
77
- form.to_result.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"]})
78
- form.to_result.hints.must_equal({:title=>["size cannot be less than 2"]})
79
- form.artist.to_result.errors.must_equal({:email=>["must be filled"]})
80
- form.artist.to_result.messages.must_equal({:email=>["must be filled"]})
81
- form.artist.to_result.hints.must_equal({:email=>[]})
82
- form.artist.label.to_result.errors.must_equal({:location=>["must be filled"]})
83
- form.artist.label.to_result.messages.must_equal({:location=>["must be filled"]})
84
- form.artist.label.to_result.hints.must_equal({:location=>[]})
74
+ form.to_result.errors.must_equal(title: ["must be filled"])
75
+ form.to_result.messages.must_equal(title: ["must be filled", "size cannot be less than 2"])
76
+ form.to_result.hints.must_equal(title: ["size cannot be less than 2"])
77
+ form.artist.to_result.errors.must_equal(email: ["must be filled"])
78
+ form.artist.to_result.messages.must_equal(email: ["must be filled"])
79
+ form.artist.to_result.hints.must_equal(email: [])
80
+ form.artist.label.to_result.errors.must_equal(location: ["must be filled"])
81
+ form.artist.label.to_result.messages.must_equal(location: ["must be filled"])
82
+ form.artist.label.to_result.hints.must_equal(location: [])
85
83
  form.songs[0].to_result.errors.must_equal({})
86
84
  form.songs[0].to_result.messages.must_equal({})
87
85
  form.songs[0].to_result.hints.must_equal({})
88
- form.songs[1].to_result.errors.must_equal({:title=>["must be filled"]})
89
- form.songs[1].to_result.messages.must_equal({:title=>["must be filled"]})
90
- form.songs[1].to_result.hints.must_equal({:title=>[]})
91
- form.songs[1].to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
92
- form.songs[1].to_result.messages(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
93
- form.songs[1].to_result.hints(locale: :de).must_equal({:title=>[]})
86
+ form.songs[1].to_result.errors.must_equal(title: ["must be filled"])
87
+ form.songs[1].to_result.messages.must_equal(title: ["must be filled"])
88
+ form.songs[1].to_result.hints.must_equal(title: [])
89
+ form.songs[1].to_result.errors(locale: :de).must_equal(title: ["muss abgefüllt sein"])
90
+ form.songs[1].to_result.messages(locale: :de).must_equal(title: ["muss abgefüllt sein"])
91
+ form.songs[1].to_result.hints(locale: :de).must_equal(title: [])
94
92
  end
95
93
 
96
94
  it "only nested property is invalid." do
97
- result = form.({ title: "Black Star", artist: { email: "" } })
95
+ result = form.(title: "Black Star", artist: {email: ""})
98
96
 
99
97
  result.success?.must_equal false
100
98
 
101
99
  # errors.messages
102
- form.errors.messages.must_equal({:"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
103
- form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
104
- form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
100
+ form.errors.messages.must_equal("artist.email": ["must be filled"], "artist.label.location": ["must be filled"], "songs.title": ["must be filled"])
101
+ form.artist.errors.messages.must_equal(email: ["must be filled"], "label.location": ["must be filled"])
102
+ form.artist.label.errors.messages.must_equal(location: ["must be filled"])
105
103
  end
106
104
 
107
105
  it "nested collection invalid" do
108
- result = form.({ title: "Black Star", artist: { email: "uhm", label: { location: "Hannover" } }, songs: [ { title: "" } ] })
106
+ result = form.(title: "Black Star", artist: {email: "uhm", label: {location: "Hannover"}}, songs: [{title: ""}])
109
107
 
110
108
  result.success?.must_equal false
111
- form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
109
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
112
110
  end
113
111
 
114
112
  #---
@@ -129,11 +127,11 @@ class DryValidationErrorsAPITest < Minitest::Spec
129
127
 
130
128
  it do
131
129
  form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
132
- form.validate(songs: [ { title: "Liar"}, { title: ""} ])
130
+ form.validate(songs: [{title: "Liar"}, {title: ""}])
133
131
 
134
- form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
132
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
135
133
  form.songs[0].errors.messages.must_equal({})
136
- form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
134
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
137
135
  end
138
136
  end
139
137
 
@@ -153,7 +151,7 @@ class DryValidationExplicitSchemaTest < Minitest::Spec
153
151
  validation schema: SessionSchema
154
152
  end
155
153
 
156
- let (:form) { SessionForm.new(Session.new) }
154
+ let(:form) { SessionForm.new(Session.new) }
157
155
 
158
156
  # valid.
159
157
  it do
@@ -177,8 +175,8 @@ class DryValidationDefaultGroupTest < Minitest::Spec
177
175
  property :email
178
176
  property :password
179
177
  property :confirm_password
180
- property :starts_at, type: Types::Form::DateTime
181
- property :active, type: Types::Form::Bool
178
+ property :starts_at, type: DRY_TYPES_CONSTANT::DateTime
179
+ property :active, type: DRY_TYPES_CONSTANT::Bool
182
180
  property :color
183
181
 
184
182
  validation do
@@ -192,7 +190,7 @@ class DryValidationDefaultGroupTest < Minitest::Spec
192
190
  required(:confirm_password).filled
193
191
  end
194
192
 
195
- validation name: :dynamic_args, with: { form: true } do
193
+ validation name: :dynamic_args, with: {form: true} do
196
194
  configure do
197
195
  def colors
198
196
  form.colors
@@ -206,7 +204,7 @@ class DryValidationDefaultGroupTest < Minitest::Spec
206
204
  end
207
205
  end
208
206
 
209
- let (:form) { SessionForm.new(Session.new) }
207
+ let(:form) { SessionForm.new(Session.new) }
210
208
 
211
209
  # valid.
212
210
  it do
@@ -214,16 +212,16 @@ class DryValidationDefaultGroupTest < Minitest::Spec
214
212
  email: "yep",
215
213
  starts_at: "01/01/2000 - 11:00",
216
214
  active: "true",
217
- confirm_password: 'pA55w0rd').must_equal true
215
+ confirm_password: "pA55w0rd").must_equal true
218
216
  form.errors.messages.inspect.must_equal "{}"
219
217
  end
220
218
 
221
219
  it "invalid" do
222
220
  form.validate(username: "Helloween",
223
221
  email: "yep",
224
- active: 'hello',
222
+ active: "hello",
225
223
  starts_at: "01/01/2000 - 11:00",
226
- color: 'purple').must_equal false
224
+ color: "purple").must_equal false
227
225
  form.errors.messages.inspect.must_equal "{:active=>[\"must be boolean\"], :confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
228
226
  end
229
227
  end
@@ -231,10 +229,9 @@ end
231
229
  class ValidationGroupsTest < MiniTest::Spec
232
230
  describe "basic validations" do
233
231
  Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
234
- SomeClass= Struct.new(:id)
232
+ SomeClass = Struct.new(:id)
235
233
 
236
234
  class SessionForm < TestForm
237
-
238
235
  property :username
239
236
  property :email
240
237
  property :password
@@ -251,7 +248,7 @@ class ValidationGroupsTest < MiniTest::Spec
251
248
  required(:email).filled(min_size?: 3)
252
249
  end
253
250
 
254
- validation name: :nested, if: :default do
251
+ validation name: :password, if: :email do
255
252
  required(:password).filled(min_size?: 2)
256
253
  end
257
254
 
@@ -260,35 +257,35 @@ class ValidationGroupsTest < MiniTest::Spec
260
257
  end
261
258
  end
262
259
 
263
- let (:form) { SessionForm.new(Session.new) }
260
+ let(:form) { SessionForm.new(Session.new) }
264
261
 
265
262
  # valid.
266
263
  it do
267
- form.validate({ username: "Helloween",
268
- special_class: SomeClass.new(id: 15),
269
- email: "yep",
270
- password: "99",
271
- confirm_password: "99" }).must_equal true
264
+ form.validate(username: "Helloween",
265
+ special_class: SomeClass.new(id: 15),
266
+ email: "yep",
267
+ password: "99",
268
+ confirm_password: "99").must_equal true
272
269
  form.errors.messages.inspect.must_equal "{}"
273
270
  end
274
271
 
275
272
  # invalid.
276
273
  it do
277
274
  form.validate({}).must_equal false
278
- form.errors.messages.must_equal({:username=>["must be filled"], :email=>["must be filled"], :special_class=>["must be filled", "must be ValidationGroupsTest::SomeClass"]})
275
+ form.errors.messages.must_equal({username: ["must be filled"], email: ["must be filled"], special_class: ["must be filled", "must be ValidationGroupsTest::SomeClass"]})
279
276
  end
280
277
 
281
278
  # partially invalid.
282
279
  # 2nd group fails.
283
280
  it do
284
- form.validate(username: "Helloween", email: "yo", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
285
- form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
281
+ form.validate(username: "Helloween", email: "yo", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
282
+ form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"]}"
286
283
  end
287
284
  # 3rd group fails.
288
285
  it do
289
- form.validate(username: "Helloween", email: "yo!", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
286
+ form.validate(username: "Helloween", email: "yo!", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
290
287
  form.errors.messages.inspect
291
- .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
288
+ .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
292
289
  end
293
290
  # 4th group with after: fails.
294
291
  it do
@@ -303,7 +300,7 @@ class ValidationGroupsTest < MiniTest::Spec
303
300
  class SessionForm < TestForm
304
301
  property :username
305
302
 
306
- validation name: :default, with: { user: OpenStruct.new(name: "Nick") } do
303
+ validation name: :default, with: {user: OpenStruct.new(name: "Nick")} do
307
304
  configure do
308
305
  def users_name
309
306
  user.name
@@ -313,17 +310,17 @@ class ValidationGroupsTest < MiniTest::Spec
313
310
  end
314
311
  end
315
312
 
316
- let (:form) { SessionForm.new(Session.new) }
313
+ let(:form) { SessionForm.new(Session.new) }
317
314
 
318
315
  # valid.
319
316
  it do
320
- form.validate({ username: "Nick" }).must_equal true
317
+ form.validate(username: "Nick").must_equal true
321
318
  form.errors.messages.inspect.must_equal "{}"
322
319
  end
323
320
 
324
321
  # invalid.
325
322
  it do
326
- form.validate({ username: 'Fred'}).must_equal false
323
+ form.validate(username: "Fred").must_equal false
327
324
  form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
328
325
  end
329
326
  end
@@ -336,15 +333,14 @@ class ValidationGroupsTest < MiniTest::Spec
336
333
 
337
334
  MySchema = Dry::Validation.Schema do
338
335
  configure do
339
- config.messages_file = 'test/fixtures/dry_error_messages.yml'
336
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
340
337
 
341
338
  def good_musical_taste?(val)
342
339
  val.is_a? String
343
340
  end
344
-
345
341
  end
346
342
 
347
- required(:password).filled(:min_size? => 6)
343
+ required(:password).filled(min_size?: 6)
348
344
  end
349
345
 
350
346
  class Session2Form < TestForm
@@ -358,22 +354,22 @@ class ValidationGroupsTest < MiniTest::Spec
358
354
  end
359
355
  end
360
356
 
361
- let (:form) { Session2Form.new(Session2.new) }
357
+ let(:form) { Session2Form.new(Session2.new) }
362
358
 
363
359
  # valid.
364
360
  it do
365
- form.validate({ username: "Helloween", email: "yep", password: "extrasafe" }).must_equal true
361
+ form.validate(username: "Helloween", email: "yep", password: "extrasafe").must_equal true
366
362
  form.errors.messages.inspect.must_equal "{}"
367
363
  end
368
364
 
369
365
  # invalid.
370
366
  it do
371
367
  form.validate({}).must_equal false
372
- form.errors.messages.must_equal({:password=>["must be filled", "size cannot be less than 6"], :username=>["must be filled"], :email=>["must be filled", "you're a bad person"]})
368
+ form.errors.messages.must_equal(password: ["must be filled", "size cannot be less than 6"], username: ["must be filled"], email: ["must be filled", "you're a bad person"])
373
369
  end
374
370
 
375
371
  it do
376
- form.validate({email: 1}).must_equal false
372
+ form.validate(email: 1).must_equal false
377
373
  form.errors.messages.inspect.must_equal "{:password=>[\"must be filled\", \"size cannot be less than 6\"], :username=>[\"must be filled\"], :email=>[\"you're a bad person\"]}"
378
374
  end
379
375
  end
@@ -416,7 +412,7 @@ class ValidationGroupsTest < MiniTest::Spec
416
412
  # message need to be defined on fixtures/dry_error_messages
417
413
  # d-v expects you to define your custome messages on the .yml file
418
414
  def good_musical_taste?(value)
419
- value != 'Nickelback'
415
+ value != "Nickelback"
420
416
  end
421
417
  end
422
418
 
@@ -434,46 +430,45 @@ class ValidationGroupsTest < MiniTest::Spec
434
430
  required(:name).filled
435
431
  end
436
432
  end
437
-
438
433
  end
439
434
  end
440
435
 
441
- let (:album) do
436
+ let(:album) do
442
437
  OpenStruct.new(
443
- :hit => OpenStruct.new,
444
- :songs => [OpenStruct.new, OpenStruct.new],
445
- :band => Struct.new(:name, :label).new("", OpenStruct.new),
446
- :producers => [OpenStruct.new, OpenStruct.new, OpenStruct.new],
438
+ hit: OpenStruct.new,
439
+ songs: [OpenStruct.new, OpenStruct.new],
440
+ band: Struct.new(:name, :label).new("", OpenStruct.new),
441
+ producers: [OpenStruct.new, OpenStruct.new, OpenStruct.new],
447
442
  )
448
443
  end
449
444
 
450
- let (:form) { AlbumForm.new(album) }
445
+ let(:form) { AlbumForm.new(album) }
451
446
 
452
447
  it "maps errors to form objects correctly" do
453
448
  result = form.validate(
454
449
  "title" => "Nickelback",
455
- "songs" => [ {"title" => ""}, {"title" => ""} ],
450
+ "songs" => [{"title" => ""}, {"title" => ""}],
456
451
  "band" => {"size" => "", "label" => {"location" => ""}},
457
- "producers" => [{"name" => ''}, {"name" => 'something lovely'}]
452
+ "producers" => [{"name" => ""}, {"name" => "something lovely"}]
458
453
  )
459
454
 
460
455
  result.must_equal false
461
456
  # from nested validation
462
- form.errors.messages.must_equal({:title=>["you're a bad person"], :"hit.title"=>["must be filled"], :"songs.title"=>["must be filled"], :"producers.name"=>["must be filled"], :"band.name"=>["must be filled"], :"band.label.location"=>["must be filled"]})
457
+ form.errors.messages.must_equal(title: ["you're a bad person"], "hit.title": ["must be filled"], "songs.title": ["must be filled"], "producers.name": ["must be filled"], "band.name": ["must be filled"], "band.label.location": ["must be filled"])
463
458
 
464
459
  # songs have their own validation.
465
- form.songs[0].errors.messages.must_equal({:title=>["must be filled"]})
460
+ form.songs[0].errors.messages.must_equal(title: ["must be filled"])
466
461
  # hit got its own validation group.
467
- form.hit.errors.messages.must_equal({:title=>["must be filled"]})
462
+ form.hit.errors.messages.must_equal(title: ["must be filled"])
468
463
 
469
- form.band.label.errors.messages.must_equal({:location=>["must be filled"]})
470
- form.band.errors.messages.must_equal({:name=>["must be filled"], :"label.location"=>["must be filled"]})
471
- form.producers[0].errors.messages.must_equal({:name=>["must be filled"]})
464
+ form.band.label.errors.messages.must_equal(location: ["must be filled"])
465
+ form.band.errors.messages.must_equal(name: ["must be filled"], "label.location": ["must be filled"])
466
+ form.producers[0].errors.messages.must_equal(name: ["must be filled"])
472
467
 
473
468
  # TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
474
- form.producers[0].to_result.errors.must_equal({:name=>["must be filled"]})
475
- form.producers[0].to_result.messages.must_equal({:name=>["must be filled"]})
476
- form.producers[0].to_result.hints.must_equal({:name=>[]})
469
+ form.producers[0].to_result.errors.must_equal(name: ["must be filled"])
470
+ form.producers[0].to_result.messages.must_equal(name: ["must be filled"])
471
+ form.producers[0].to_result.hints.must_equal(name: [])
477
472
  end
478
473
 
479
474
  # FIXME: fix the "must be filled error"
@@ -481,9 +476,9 @@ class ValidationGroupsTest < MiniTest::Spec
481
476
  it "renders full messages correctly" do
482
477
  result = form.validate(
483
478
  "title" => "",
484
- "songs" => [ {"title" => ""}, {"title" => ""} ],
479
+ "songs" => [{"title" => ""}, {"title" => ""}],
485
480
  "band" => {"size" => "", "label" => {"name" => ""}},
486
- "producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
481
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
487
482
  )
488
483
 
489
484
  result.must_equal false
@@ -505,7 +500,7 @@ class ValidationGroupsTest < MiniTest::Spec
505
500
 
506
501
  validation do
507
502
  configure do
508
- config.messages_file = 'test/fixtures/dry_error_messages.yml'
503
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
509
504
  end
510
505
 
511
506
  required(:title).filled
@@ -519,24 +514,24 @@ class ValidationGroupsTest < MiniTest::Spec
519
514
  end
520
515
  end
521
516
 
522
- let (:form) { AlbumFormWith1NestedVal.new(album) }
517
+ let(:form) { AlbumFormWith1NestedVal.new(album) }
523
518
 
524
519
  it "allows to access dry's result semantics per nested form" do
525
- result = form.validate(
520
+ form.validate(
526
521
  "title" => "",
527
- "songs" => [ {"title" => ""}, {"title" => ""} ],
522
+ "songs" => [{"title" => ""}, {"title" => ""}],
528
523
  "band" => {"size" => "", "label" => {"name" => ""}},
529
- "producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
524
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
530
525
  )
531
526
 
532
- form.to_result.errors.must_equal({:title=>["must be filled"]})
533
- form.band.to_result.errors.must_equal({:name=>["must be filled"]})
534
- form.band.label.to_result.errors.must_equal({:location=>["must be filled"]})
527
+ form.to_result.errors.must_equal(title: ["must be filled"])
528
+ form.band.to_result.errors.must_equal(name: ["must be filled"])
529
+ form.band.label.to_result.errors.must_equal(location: ["must be filled"])
535
530
 
536
531
  # with locale: "de"
537
- form.to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
538
- form.band.to_result.errors(locale: :de).must_equal({:name=>["muss abgefüllt sein"]})
539
- form.band.label.to_result.errors(locale: :de).must_equal({:location=>["muss abgefüllt sein"]})
532
+ form.to_result.errors(locale: :de).must_equal(title: ["muss abgefüllt sein"])
533
+ form.band.to_result.errors(locale: :de).must_equal(name: ["muss abgefüllt sein"])
534
+ form.band.label.to_result.errors(locale: :de).must_equal(location: ["muss abgefüllt sein"])
540
535
  end
541
536
  end
542
537
  end
@@ -557,7 +552,7 @@ class ValidationGroupsTest < MiniTest::Spec
557
552
  # end
558
553
  # end
559
554
 
560
- # let (:form) { OverwritingForm.new(Session.new) }
555
+ # let(:form) { OverwritingForm.new(Session.new) }
561
556
 
562
557
  # # valid.
563
558
  # it do
@@ -571,36 +566,41 @@ class ValidationGroupsTest < MiniTest::Spec
571
566
  # end
572
567
  # end
573
568
 
574
-
575
569
  describe "inherit: true in same group" do
576
570
  class InheritSameGroupForm < TestForm
577
571
  property :username
578
572
  property :email
573
+ property :full_name, virtual: true
579
574
 
580
- validation name: :email do
581
- required(:email).filled
582
- end
575
+ validation name: :username do
576
+ configure do
577
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
578
+ end
583
579
 
584
- validation name: :email, inherit: true do # extends the above.
585
580
  required(:username).filled
581
+ required(:full_name).filled
582
+ end
583
+
584
+ validation name: :username, inherit: true do # overrides and extends the above.
585
+ optional(:username).maybe
586
+ required(:email).filled
586
587
  end
587
588
  end
588
589
 
589
- let (:form) { InheritSameGroupForm.new(Session.new) }
590
+ let(:form) { InheritSameGroupForm.new(Session.new) }
590
591
 
591
592
  # valid.
592
593
  it do
593
- form.validate({username: "Helloween", email: 9}).must_equal true
594
+ form.validate(full_name: "My name", email: 9).must_equal true
594
595
  end
595
596
 
596
597
  # invalid.
597
598
  it do
598
599
  form.validate({}).must_equal false
599
- form.errors.messages.inspect.must_equal "{:email=>[\"must be filled\"], :username=>[\"must be filled\"]}"
600
+ form.errors.messages.must_equal email: ["must be filled"], full_name: ["must be filled"]
600
601
  end
601
602
  end
602
603
 
603
-
604
604
  describe "if: with lambda" do
605
605
  class IfWithLambdaForm < TestForm
606
606
  property :username
@@ -612,30 +612,137 @@ class ValidationGroupsTest < MiniTest::Spec
612
612
  end
613
613
 
614
614
  # run this is :email group is true.
615
- validation name: :after_email, if: lambda { |results| results[:email].success? } do # extends the above.
615
+ validation name: :after_email, if: ->(results) { results[:email].success? } do # extends the above.
616
616
  required(:username).filled
617
617
  end
618
618
 
619
619
  # block gets evaled in form instance context.
620
- validation name: :password, if: lambda { |results| email == "john@trb.org" } do
620
+ validation name: :password, if: ->(results) { email == "john@trb.org" } do
621
621
  required(:password).filled
622
622
  end
623
623
  end
624
624
 
625
- let (:form) { IfWithLambdaForm.new(Session.new) }
625
+ let(:form) { IfWithLambdaForm.new(Session.new) }
626
626
 
627
627
  # valid.
628
628
  it do
629
- form.validate({username: "Strung Out", email: 9}).must_equal true
629
+ form.validate(username: "Strung Out", email: 9).must_equal true
630
630
  end
631
631
 
632
632
  # invalid.
633
633
  it do
634
- form.validate({email: 9}).must_equal false
634
+ form.validate(email: 9).must_equal false
635
635
  form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
636
636
  end
637
637
  end
638
638
 
639
+ class NestedSchemaValidationTest < MiniTest::Spec
640
+ AddressSchema = Dry::Validation.Schema do
641
+ required(:company).filled(:int?)
642
+ end
643
+
644
+ class OrderForm < TestForm
645
+ property :delivery_address do
646
+ property :company
647
+ end
648
+
649
+ validation do
650
+ required(:delivery_address).schema(AddressSchema)
651
+ end
652
+ end
653
+
654
+ let(:company) { Struct.new(:company) }
655
+ let(:order) { Struct.new(:delivery_address) }
656
+ let(:form) { OrderForm.new(order.new(company.new)) }
657
+
658
+ it "has company error" do
659
+ form.validate(delivery_address: {company: "not int"}).must_equal false
660
+ form.errors.messages.must_equal(:"delivery_address.company" => ["must be an integer"])
661
+ end
662
+ end
663
+
664
+ class NestedSchemaValidationWithFormTest < MiniTest::Spec
665
+ class CompanyForm < TestForm
666
+ property :company
667
+
668
+ validation do
669
+ required(:company).filled(:int?)
670
+ end
671
+ end
672
+
673
+ class OrderFormWithForm < TestForm
674
+ property :delivery_address, form: CompanyForm
675
+ end
676
+
677
+ let(:company) { Struct.new(:company) }
678
+ let(:order) { Struct.new(:delivery_address) }
679
+ let(:form) { OrderFormWithForm.new(order.new(company.new)) }
680
+
681
+ it "has company error" do
682
+ form.validate(delivery_address: {company: "not int"}).must_equal false
683
+ form.errors.messages.must_equal(:"delivery_address.company" => ["must be an integer"])
684
+ end
685
+ end
686
+
687
+ class CollectionPropertyWithCustomRuleTest < MiniTest::Spec
688
+ Artist = Struct.new(:first_name, :last_name)
689
+ Song = Struct.new(:title, :enabled)
690
+ Album = Struct.new(:title, :songs, :artist)
691
+
692
+ class AlbumForm < TestForm
693
+ property :title
694
+
695
+ collection :songs, virtual: true, populate_if_empty: Song do
696
+ property :title
697
+ property :enabled
698
+
699
+ validation do
700
+ required(:title).filled
701
+ end
702
+ end
703
+
704
+ property :artist, populate_if_empty: Artist do
705
+ property :first_name
706
+ property :last_name
707
+ end
708
+
709
+ validation do
710
+ configure do
711
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
712
+
713
+ def a_song?(value)
714
+ value.any? { |el| el && el[:enabled] }
715
+ end
716
+
717
+ def with_last_name?(value)
718
+ !value[:last_name].nil?
719
+ end
720
+ end
721
+
722
+ required(:songs).filled(:a_song?)
723
+ required(:artist).filled(:with_last_name?)
724
+ end
725
+ end
726
+
727
+ it "validates fails and shows the correct errors" do
728
+ form = AlbumForm.new(Album.new(nil, [], nil))
729
+ form.validate(
730
+ "songs" => [
731
+ {"title" => "One", "enabled" => false},
732
+ {"title" => nil, "enabled" => false},
733
+ {"title" => "Three", "enabled" => false}
734
+ ],
735
+ "artist" => {"last_name" => nil}
736
+ ).must_equal false
737
+ form.songs.size.must_equal 3
738
+
739
+ form.errors.messages.must_equal(
740
+ :songs => ["must have at least one enabled song"],
741
+ :artist => ["must have last name"],
742
+ :"songs.title" => ["must be filled"]
743
+ )
744
+ end
745
+ end
639
746
 
640
747
  # Currenty dry-v don't support that option, it doesn't make sense
641
748
  # I've talked to @solnic and he plans to add a "hint" feature to show
@@ -654,7 +761,7 @@ class ValidationGroupsTest < MiniTest::Spec
654
761
  # end
655
762
  # end
656
763
 
657
- # let (:form) { MultipleErrorsForPropertyForm.new(Session.new) }
764
+ # let(:form) { MultipleErrorsForPropertyForm.new(Session.new) }
658
765
 
659
766
  # # valid.
660
767
  # it do