reform 2.2.4 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +460 -0
  5. data/.travis.yml +11 -6
  6. data/Appraisals +8 -0
  7. data/CHANGES.md +54 -4
  8. data/CONTRIBUTING.md +31 -0
  9. data/Gemfile +2 -16
  10. data/ISSUE_TEMPLATE.md +25 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +5 -7
  13. data/Rakefile +18 -9
  14. data/gemfiles/0.13.0.gemfile +8 -0
  15. data/gemfiles/1.5.0.gemfile +9 -0
  16. data/lib/reform.rb +1 -0
  17. data/lib/reform/contract.rb +7 -17
  18. data/lib/reform/contract/custom_error.rb +41 -0
  19. data/lib/reform/contract/validate.rb +53 -23
  20. data/lib/reform/errors.rb +61 -0
  21. data/lib/reform/form.rb +36 -10
  22. data/lib/reform/form/call.rb +1 -1
  23. data/lib/reform/form/composition.rb +2 -2
  24. data/lib/reform/form/dry.rb +10 -58
  25. data/lib/reform/form/dry/input_hash.rb +37 -0
  26. data/lib/reform/form/dry/new_api.rb +46 -0
  27. data/lib/reform/form/dry/old_api.rb +61 -0
  28. data/lib/reform/form/populator.rb +11 -27
  29. data/lib/reform/form/prepopulate.rb +4 -3
  30. data/lib/reform/form/validate.rb +28 -13
  31. data/lib/reform/result.rb +90 -0
  32. data/lib/reform/validation.rb +19 -11
  33. data/lib/reform/validation/groups.rb +12 -27
  34. data/lib/reform/version.rb +1 -1
  35. data/reform.gemspec +15 -13
  36. data/test/benchmarking.rb +39 -6
  37. data/test/call_new_api.rb +23 -0
  38. data/test/{call_test.rb → call_old_api.rb} +4 -4
  39. data/test/changed_test.rb +8 -8
  40. data/test/coercion_test.rb +51 -19
  41. data/test/composition_new_api.rb +186 -0
  42. data/test/{composition_test.rb → composition_old_api.rb} +66 -31
  43. data/test/contract/custom_error_test.rb +55 -0
  44. data/test/contract_new_api.rb +77 -0
  45. data/test/{contract_test.rb → contract_old_api.rb} +13 -13
  46. data/test/default_test.rb +2 -2
  47. data/test/deserialize_test.rb +11 -14
  48. data/test/errors_new_api.rb +225 -0
  49. data/test/errors_old_api.rb +230 -0
  50. data/test/feature_test.rb +8 -10
  51. data/test/fixtures/dry_error_messages.yml +73 -23
  52. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  53. data/test/form_new_api.rb +57 -0
  54. data/test/{form_test.rb → form_old_api.rb} +5 -5
  55. data/test/form_option_new_api.rb +24 -0
  56. data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
  57. data/test/from_test.rb +9 -13
  58. data/test/inherit_new_api.rb +105 -0
  59. data/test/inherit_old_api.rb +105 -0
  60. data/test/{module_test.rb → module_new_api.rb} +20 -25
  61. data/test/module_old_api.rb +146 -0
  62. data/test/parse_option_test.rb +40 -0
  63. data/test/parse_pipeline_test.rb +3 -3
  64. data/test/populate_new_api.rb +304 -0
  65. data/test/{populate_test.rb → populate_old_api.rb} +83 -49
  66. data/test/populator_skip_test.rb +9 -9
  67. data/test/prepopulator_test.rb +8 -9
  68. data/test/read_only_test.rb +12 -1
  69. data/test/readable_test.rb +7 -7
  70. data/test/reform_new_api.rb +204 -0
  71. data/test/{reform_test.rb → reform_old_api.rb} +30 -51
  72. data/test/save_new_api.rb +101 -0
  73. data/test/{save_test.rb → save_old_api.rb} +32 -20
  74. data/test/setup_test.rb +8 -8
  75. data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
  76. data/test/skip_if_old_api.rb +92 -0
  77. data/test/skip_setter_and_getter_test.rb +3 -4
  78. data/test/test_helper.rb +25 -14
  79. data/test/validate_new_api.rb +408 -0
  80. data/test/{validate_test.rb → validate_old_api.rb} +59 -69
  81. data/test/validation/dry_validation_new_api.rb +836 -0
  82. data/test/validation/dry_validation_old_api.rb +772 -0
  83. data/test/validation/result_test.rb +77 -0
  84. data/test/validation_library_provided_test.rb +16 -0
  85. data/test/virtual_test.rb +47 -7
  86. data/test/writeable_test.rb +35 -6
  87. metadata +127 -56
  88. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  89. data/lib/reform/contract/errors.rb +0 -43
  90. data/lib/reform/form/mongoid.rb +0 -37
  91. data/lib/reform/form/orm.rb +0 -26
  92. data/lib/reform/mongoid.rb +0 -4
  93. data/test/deprecation_test.rb +0 -27
  94. data/test/errors_test.rb +0 -165
  95. data/test/inherit_test.rb +0 -119
  96. data/test/readonly_test.rb +0 -14
  97. data/test/validation/dry_test.rb +0 -60
  98. data/test/validation/dry_validation_test.rb +0 -352
  99. data/test/validation/errors.yml +0 -4
@@ -0,0 +1,772 @@
1
+ require "test_helper"
2
+ require "reform/form/dry"
3
+ require "reform/form/coercion"
4
+
5
+ #---
6
+ # one "nested" Schema per form.
7
+ class DryValidationErrorsAPITest < Minitest::Spec
8
+ Album = Struct.new(:title, :artist, :songs)
9
+ Song = Struct.new(:title)
10
+ Artist = Struct.new(:email, :label)
11
+ Label = Struct.new(:location)
12
+
13
+ class AlbumForm < TestForm
14
+ property :title
15
+
16
+ validation do
17
+ # required(:title).filled
18
+ required(:title).filled(min_size?: 2)
19
+ end
20
+
21
+ property :artist do
22
+ property :email
23
+
24
+ validation do
25
+ required(:email).filled
26
+ end
27
+
28
+ property :label do
29
+ property :location
30
+
31
+ validation do
32
+ required(:location).filled
33
+ end
34
+ end
35
+ end
36
+
37
+ # note the validation block is *in* the collection block, per item, so to speak.
38
+ collection :songs do
39
+ property :title
40
+
41
+ validation do
42
+ configure do
43
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
44
+ end
45
+
46
+ required(:title).filled
47
+ end
48
+ end
49
+ end
50
+
51
+ let(:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
52
+
53
+ it "everything wrong" do
54
+ result = form.(title: nil, artist: {email: ""}, songs: [{title: "Clams have feelings too"}, {title: ""}])
55
+
56
+ result.success?.must_equal false
57
+
58
+ # errors.messages
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"])
62
+ form.songs[0].errors.messages.must_equal({})
63
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
64
+
65
+ # #errors[]
66
+ form.errors[:nonsense].must_equal []
67
+ form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
68
+ form.artist.errors[:email].must_equal ["must be filled"]
69
+ form.artist.label.errors[:location].must_equal ["must be filled"]
70
+ form.songs[0].errors[:title].must_equal []
71
+ form.songs[1].errors[:title].must_equal ["must be filled"]
72
+
73
+ # #to_result
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: [])
83
+ form.songs[0].to_result.errors.must_equal({})
84
+ form.songs[0].to_result.messages.must_equal({})
85
+ form.songs[0].to_result.hints.must_equal({})
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: [])
92
+ end
93
+
94
+ it "only nested property is invalid." do
95
+ result = form.(title: "Black Star", artist: {email: ""})
96
+
97
+ result.success?.must_equal false
98
+
99
+ # errors.messages
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"])
103
+ end
104
+
105
+ it "nested collection invalid" do
106
+ result = form.(title: "Black Star", artist: {email: "uhm", label: {location: "Hannover"}}, songs: [{title: ""}])
107
+
108
+ result.success?.must_equal false
109
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
110
+ end
111
+
112
+ #---
113
+ #- validation .each
114
+ class CollectionExternalValidationsForm < TestForm
115
+ collection :songs do
116
+ property :title
117
+ end
118
+
119
+ validation do
120
+ required(:songs).each do
121
+ schema do
122
+ required(:title).filled
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ it do
129
+ form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
130
+ form.validate(songs: [{title: "Liar"}, {title: ""}])
131
+
132
+ form.errors.messages.must_equal("songs.title": ["must be filled"])
133
+ form.songs[0].errors.messages.must_equal({})
134
+ form.songs[1].errors.messages.must_equal(title: ["must be filled"])
135
+ end
136
+ end
137
+
138
+ class DryValidationExplicitSchemaTest < Minitest::Spec
139
+ Session = Struct.new(:name, :email)
140
+ SessionSchema = Dry::Validation.Schema do
141
+ required(:name).filled
142
+ required(:email).filled
143
+ end
144
+
145
+ class SessionForm < TestForm
146
+ include Coercion
147
+
148
+ property :name
149
+ property :email
150
+
151
+ validation schema: SessionSchema
152
+ end
153
+
154
+ let(:form) { SessionForm.new(Session.new) }
155
+
156
+ # valid.
157
+ it do
158
+ form.validate(name: "Helloween", email: "yep").must_equal true
159
+ form.errors.messages.inspect.must_equal "{}"
160
+ end
161
+
162
+ it "invalid" do
163
+ form.validate(name: "", email: "yep").must_equal false
164
+ form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"]}"
165
+ end
166
+ end
167
+
168
+ class DryValidationDefaultGroupTest < Minitest::Spec
169
+ Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
170
+
171
+ class SessionForm < TestForm
172
+ include Coercion
173
+
174
+ property :username
175
+ property :email
176
+ property :password
177
+ property :confirm_password
178
+ property :starts_at, type: DRY_TYPES_CONSTANT::DateTime
179
+ property :active, type: DRY_TYPES_CONSTANT::Bool
180
+ property :color
181
+
182
+ validation do
183
+ required(:username).filled
184
+ required(:email).filled
185
+ required(:starts_at).filled(:date_time?)
186
+ required(:active).filled(:bool?)
187
+ end
188
+
189
+ validation name: :another_block do
190
+ required(:confirm_password).filled
191
+ end
192
+
193
+ validation name: :dynamic_args, with: {form: true} do
194
+ configure do
195
+ def colors
196
+ form.colors
197
+ end
198
+ end
199
+ required(:color).maybe(included_in?: colors)
200
+ end
201
+
202
+ def colors
203
+ %(red orange green)
204
+ end
205
+ end
206
+
207
+ let(:form) { SessionForm.new(Session.new) }
208
+
209
+ # valid.
210
+ it do
211
+ form.validate(username: "Helloween",
212
+ email: "yep",
213
+ starts_at: "01/01/2000 - 11:00",
214
+ active: "true",
215
+ confirm_password: "pA55w0rd").must_equal true
216
+ form.errors.messages.inspect.must_equal "{}"
217
+ end
218
+
219
+ it "invalid" do
220
+ form.validate(username: "Helloween",
221
+ email: "yep",
222
+ active: "hello",
223
+ starts_at: "01/01/2000 - 11:00",
224
+ color: "purple").must_equal false
225
+ form.errors.messages.inspect.must_equal "{:active=>[\"must be boolean\"], :confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
226
+ end
227
+ end
228
+
229
+ class ValidationGroupsTest < MiniTest::Spec
230
+ describe "basic validations" do
231
+ Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
232
+ SomeClass = Struct.new(:id)
233
+
234
+ class SessionForm < TestForm
235
+ property :username
236
+ property :email
237
+ property :password
238
+ property :confirm_password
239
+ property :special_class
240
+
241
+ validation do
242
+ required(:username).filled
243
+ required(:email).filled
244
+ required(:special_class).filled(type?: SomeClass)
245
+ end
246
+
247
+ validation name: :email, if: :default do
248
+ required(:email).filled(min_size?: 3)
249
+ end
250
+
251
+ validation name: :password, if: :email do
252
+ required(:password).filled(min_size?: 2)
253
+ end
254
+
255
+ validation name: :confirm, if: :default, after: :email do
256
+ required(:confirm_password).filled(min_size?: 2)
257
+ end
258
+ end
259
+
260
+ let(:form) { SessionForm.new(Session.new) }
261
+
262
+ # valid.
263
+ it do
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
269
+ form.errors.messages.inspect.must_equal "{}"
270
+ end
271
+
272
+ # invalid.
273
+ it do
274
+ form.validate({}).must_equal false
275
+ form.errors.messages.must_equal({username: ["must be filled"], email: ["must be filled"], special_class: ["must be filled", "must be ValidationGroupsTest::SomeClass"]})
276
+ end
277
+
278
+ # partially invalid.
279
+ # 2nd group fails.
280
+ it do
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\"]}"
283
+ end
284
+ # 3rd group fails.
285
+ it do
286
+ form.validate(username: "Helloween", email: "yo!", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
287
+ form.errors.messages.inspect
288
+ .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
289
+ end
290
+ # 4th group with after: fails.
291
+ it do
292
+ form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
293
+ form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
294
+ end
295
+ end
296
+
297
+ class ValidationWithOptionsTest < MiniTest::Spec
298
+ describe "basic validations" do
299
+ Session = Struct.new(:username)
300
+ class SessionForm < TestForm
301
+ property :username
302
+
303
+ validation name: :default, with: {user: OpenStruct.new(name: "Nick")} do
304
+ configure do
305
+ def users_name
306
+ user.name
307
+ end
308
+ end
309
+ required(:username).filled(eql?: users_name)
310
+ end
311
+ end
312
+
313
+ let(:form) { SessionForm.new(Session.new) }
314
+
315
+ # valid.
316
+ it do
317
+ form.validate(username: "Nick").must_equal true
318
+ form.errors.messages.inspect.must_equal "{}"
319
+ end
320
+
321
+ # invalid.
322
+ it do
323
+ form.validate(username: "Fred").must_equal false
324
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
325
+ end
326
+ end
327
+ end
328
+
329
+ #---
330
+ #- validation( schema: MySchema )
331
+ describe "with custom schema" do
332
+ Session2 = Struct.new(:username, :email, :password)
333
+
334
+ MySchema = Dry::Validation.Schema do
335
+ configure do
336
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
337
+
338
+ def good_musical_taste?(val)
339
+ val.is_a? String
340
+ end
341
+ end
342
+
343
+ required(:password).filled(min_size?: 6)
344
+ end
345
+
346
+ class Session2Form < TestForm
347
+ property :username
348
+ property :email
349
+ property :password
350
+
351
+ validation schema: MySchema do
352
+ required(:username).filled
353
+ required(:email).filled(:good_musical_taste?)
354
+ end
355
+ end
356
+
357
+ let(:form) { Session2Form.new(Session2.new) }
358
+
359
+ # valid.
360
+ it do
361
+ form.validate(username: "Helloween", email: "yep", password: "extrasafe").must_equal true
362
+ form.errors.messages.inspect.must_equal "{}"
363
+ end
364
+
365
+ # invalid.
366
+ it do
367
+ form.validate({}).must_equal false
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"])
369
+ end
370
+
371
+ it do
372
+ form.validate(email: 1).must_equal false
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\"]}"
374
+ end
375
+ end
376
+
377
+ describe "MIXED nested validations" do
378
+ class AlbumForm < TestForm
379
+ property :title
380
+
381
+ property :hit do
382
+ property :title
383
+
384
+ validation do
385
+ required(:title).filled
386
+ end
387
+ end
388
+
389
+ collection :songs do
390
+ property :title
391
+
392
+ validation do
393
+ required(:title).filled
394
+ end
395
+ end
396
+
397
+ # we test this one by running an each / schema dry-v check on the main block
398
+ collection :producers do
399
+ property :name
400
+ end
401
+
402
+ property :band do
403
+ property :name
404
+ property :label do
405
+ property :location
406
+ end
407
+ end
408
+
409
+ validation do
410
+ configure do
411
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
412
+ # message need to be defined on fixtures/dry_error_messages
413
+ # d-v expects you to define your custome messages on the .yml file
414
+ def good_musical_taste?(value)
415
+ value != "Nickelback"
416
+ end
417
+ end
418
+
419
+ required(:title).filled(:good_musical_taste?)
420
+
421
+ required(:band).schema do
422
+ required(:name).filled
423
+ required(:label).schema do
424
+ required(:location).filled
425
+ end
426
+ end
427
+
428
+ required(:producers).each do
429
+ schema do
430
+ required(:name).filled
431
+ end
432
+ end
433
+ end
434
+ end
435
+
436
+ let(:album) do
437
+ 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],
442
+ )
443
+ end
444
+
445
+ let(:form) { AlbumForm.new(album) }
446
+
447
+ it "maps errors to form objects correctly" do
448
+ result = form.validate(
449
+ "title" => "Nickelback",
450
+ "songs" => [{"title" => ""}, {"title" => ""}],
451
+ "band" => {"size" => "", "label" => {"location" => ""}},
452
+ "producers" => [{"name" => ""}, {"name" => "something lovely"}]
453
+ )
454
+
455
+ result.must_equal false
456
+ # from nested validation
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"])
458
+
459
+ # songs have their own validation.
460
+ form.songs[0].errors.messages.must_equal(title: ["must be filled"])
461
+ # hit got its own validation group.
462
+ form.hit.errors.messages.must_equal(title: ["must be filled"])
463
+
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"])
467
+
468
+ # TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
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: [])
472
+ end
473
+
474
+ # FIXME: fix the "must be filled error"
475
+
476
+ it "renders full messages correctly" do
477
+ result = form.validate(
478
+ "title" => "",
479
+ "songs" => [{"title" => ""}, {"title" => ""}],
480
+ "band" => {"size" => "", "label" => {"name" => ""}},
481
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
482
+ )
483
+
484
+ result.must_equal false
485
+ form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
486
+ form.band.label.errors.full_messages.must_equal ["Location must be filled"]
487
+ form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
488
+ form.errors.full_messages.must_equal ["Title must be filled", "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"]
489
+ end
490
+
491
+ describe "only 1 nested validation" do
492
+ class AlbumFormWith1NestedVal < TestForm
493
+ property :title
494
+ property :band do
495
+ property :name
496
+ property :label do
497
+ property :location
498
+ end
499
+ end
500
+
501
+ validation do
502
+ configure do
503
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
504
+ end
505
+
506
+ required(:title).filled
507
+
508
+ required(:band).schema do
509
+ required(:name).filled
510
+ required(:label).schema do
511
+ required(:location).filled
512
+ end
513
+ end
514
+ end
515
+ end
516
+
517
+ let(:form) { AlbumFormWith1NestedVal.new(album) }
518
+
519
+ it "allows to access dry's result semantics per nested form" do
520
+ form.validate(
521
+ "title" => "",
522
+ "songs" => [{"title" => ""}, {"title" => ""}],
523
+ "band" => {"size" => "", "label" => {"name" => ""}},
524
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
525
+ )
526
+
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"])
530
+
531
+ # with locale: "de"
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"])
535
+ end
536
+ end
537
+ end
538
+
539
+ # describe "same-named group" do
540
+ # class OverwritingForm < TestForm
541
+ # include Reform::Form::Dry::Validations
542
+
543
+ # property :username
544
+ # property :email
545
+
546
+ # validation :email do # FIX ME: is this working for other validator or just bugging here?
547
+ # key(:email, &:filled?) # it's not considered, overitten
548
+ # end
549
+
550
+ # validation :email do # just another group.
551
+ # key(:username, &:filled?)
552
+ # end
553
+ # end
554
+
555
+ # let(:form) { OverwritingForm.new(Session.new) }
556
+
557
+ # # valid.
558
+ # it do
559
+ # form.validate({username: "Helloween"}).must_equal true
560
+ # end
561
+
562
+ # # invalid.
563
+ # it "whoo" do
564
+ # form.validate({}).must_equal false
565
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
566
+ # end
567
+ # end
568
+
569
+ describe "inherit: true in same group" do
570
+ class InheritSameGroupForm < TestForm
571
+ property :username
572
+ property :email
573
+ property :full_name, virtual: true
574
+
575
+ validation name: :username do
576
+ configure do
577
+ config.messages_file = "test/fixtures/dry_error_messages.yml"
578
+ end
579
+
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
587
+ end
588
+ end
589
+
590
+ let(:form) { InheritSameGroupForm.new(Session.new) }
591
+
592
+ # valid.
593
+ it do
594
+ form.validate(full_name: "My name", email: 9).must_equal true
595
+ end
596
+
597
+ # invalid.
598
+ it do
599
+ form.validate({}).must_equal false
600
+ form.errors.messages.must_equal email: ["must be filled"], full_name: ["must be filled"]
601
+ end
602
+ end
603
+
604
+ describe "if: with lambda" do
605
+ class IfWithLambdaForm < TestForm
606
+ property :username
607
+ property :email
608
+ property :password
609
+
610
+ validation name: :email do
611
+ required(:email).filled
612
+ end
613
+
614
+ # run this is :email group is true.
615
+ validation name: :after_email, if: ->(results) { results[:email].success? } do # extends the above.
616
+ required(:username).filled
617
+ end
618
+
619
+ # block gets evaled in form instance context.
620
+ validation name: :password, if: ->(results) { email == "john@trb.org" } do
621
+ required(:password).filled
622
+ end
623
+ end
624
+
625
+ let(:form) { IfWithLambdaForm.new(Session.new) }
626
+
627
+ # valid.
628
+ it do
629
+ form.validate(username: "Strung Out", email: 9).must_equal true
630
+ end
631
+
632
+ # invalid.
633
+ it do
634
+ form.validate(email: 9).must_equal false
635
+ form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
636
+ end
637
+ end
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
746
+
747
+ # Currenty dry-v don't support that option, it doesn't make sense
748
+ # I've talked to @solnic and he plans to add a "hint" feature to show
749
+ # more errors messages than only those that have failed.
750
+ #
751
+ # describe "multiple errors for property" do
752
+ # class MultipleErrorsForPropertyForm < TestForm
753
+ # include Reform::Form::Dry::Validations
754
+
755
+ # property :username
756
+
757
+ # validation :default do
758
+ # key(:username) do |username|
759
+ # username.filled? | (username.min_size?(2) & username.max_size?(3))
760
+ # end
761
+ # end
762
+ # end
763
+
764
+ # let(:form) { MultipleErrorsForPropertyForm.new(Session.new) }
765
+
766
+ # # valid.
767
+ # it do
768
+ # form.validate({username: ""}).must_equal false
769
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\", \"username is not proper size\"]}"
770
+ # end
771
+ # end
772
+ end