reform 2.2.4 → 2.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -1
  3. data/.travis.yml +11 -6
  4. data/Appraisals +8 -0
  5. data/CHANGES.md +57 -4
  6. data/CONTRIBUTING.md +31 -0
  7. data/Gemfile +2 -16
  8. data/ISSUE_TEMPLATE.md +25 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +5 -7
  11. data/Rakefile +16 -9
  12. data/gemfiles/0.13.0.gemfile +8 -0
  13. data/gemfiles/1.5.0.gemfile +9 -0
  14. data/lib/reform.rb +1 -0
  15. data/lib/reform/contract.rb +7 -17
  16. data/lib/reform/contract/custom_error.rb +41 -0
  17. data/lib/reform/contract/validate.rb +53 -23
  18. data/lib/reform/errors.rb +61 -0
  19. data/lib/reform/form.rb +36 -10
  20. data/lib/reform/form/call.rb +1 -1
  21. data/lib/reform/form/composition.rb +2 -2
  22. data/lib/reform/form/dry.rb +10 -58
  23. data/lib/reform/form/dry/input_hash.rb +37 -0
  24. data/lib/reform/form/dry/new_api.rb +45 -0
  25. data/lib/reform/form/dry/old_api.rb +61 -0
  26. data/lib/reform/form/populator.rb +11 -27
  27. data/lib/reform/form/prepopulate.rb +4 -3
  28. data/lib/reform/form/validate.rb +28 -13
  29. data/lib/reform/result.rb +90 -0
  30. data/lib/reform/validation.rb +19 -11
  31. data/lib/reform/validation/groups.rb +12 -27
  32. data/lib/reform/version.rb +1 -1
  33. data/reform.gemspec +14 -13
  34. data/test/benchmarking.rb +39 -6
  35. data/test/call_new_api.rb +23 -0
  36. data/test/call_old_api.rb +23 -0
  37. data/test/changed_test.rb +14 -14
  38. data/test/coercion_test.rb +57 -25
  39. data/test/composition_new_api.rb +186 -0
  40. data/test/composition_old_api.rb +184 -0
  41. data/test/contract/custom_error_test.rb +55 -0
  42. data/test/contract_new_api.rb +77 -0
  43. data/test/contract_old_api.rb +77 -0
  44. data/test/default_test.rb +4 -4
  45. data/test/deserialize_test.rb +17 -20
  46. data/test/errors_new_api.rb +225 -0
  47. data/test/errors_old_api.rb +230 -0
  48. data/test/feature_test.rb +10 -12
  49. data/test/fixtures/dry_error_messages.yml +73 -23
  50. data/test/fixtures/dry_new_api_error_messages.yml +104 -0
  51. data/test/form_new_api.rb +57 -0
  52. data/test/{form_test.rb → form_old_api.rb} +8 -8
  53. data/test/form_option_new_api.rb +24 -0
  54. data/test/{form_option_test.rb → form_option_old_api.rb} +5 -5
  55. data/test/from_test.rb +18 -22
  56. data/test/inherit_new_api.rb +105 -0
  57. data/test/inherit_old_api.rb +105 -0
  58. data/test/{module_test.rb → module_new_api.rb} +26 -31
  59. data/test/module_old_api.rb +146 -0
  60. data/test/parse_option_test.rb +40 -0
  61. data/test/parse_pipeline_test.rb +4 -4
  62. data/test/populate_new_api.rb +304 -0
  63. data/test/populate_old_api.rb +304 -0
  64. data/test/populator_skip_test.rb +11 -11
  65. data/test/prepopulator_test.rb +23 -24
  66. data/test/read_only_test.rb +12 -1
  67. data/test/readable_test.rb +9 -9
  68. data/test/reform_new_api.rb +204 -0
  69. data/test/{reform_test.rb → reform_old_api.rb} +44 -65
  70. data/test/save_new_api.rb +101 -0
  71. data/test/save_old_api.rb +101 -0
  72. data/test/setup_test.rb +17 -17
  73. data/test/skip_if_new_api.rb +85 -0
  74. data/test/skip_if_old_api.rb +92 -0
  75. data/test/skip_setter_and_getter_test.rb +9 -10
  76. data/test/test_helper.rb +25 -14
  77. data/test/validate_new_api.rb +453 -0
  78. data/test/{validate_test.rb → validate_old_api.rb} +121 -131
  79. data/test/validation/dry_validation_new_api.rb +835 -0
  80. data/test/validation/dry_validation_old_api.rb +772 -0
  81. data/test/validation/result_test.rb +77 -0
  82. data/test/validation_library_provided_test.rb +16 -0
  83. data/test/virtual_test.rb +47 -7
  84. data/test/writeable_test.rb +38 -9
  85. metadata +111 -56
  86. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  87. data/lib/reform/contract/errors.rb +0 -43
  88. data/lib/reform/form/mongoid.rb +0 -37
  89. data/lib/reform/form/orm.rb +0 -26
  90. data/lib/reform/mongoid.rb +0 -4
  91. data/test/call_test.rb +0 -23
  92. data/test/composition_test.rb +0 -149
  93. data/test/contract_test.rb +0 -77
  94. data/test/deprecation_test.rb +0 -27
  95. data/test/errors_test.rb +0 -165
  96. data/test/inherit_test.rb +0 -119
  97. data/test/populate_test.rb +0 -270
  98. data/test/readonly_test.rb +0 -14
  99. data/test/save_test.rb +0 -89
  100. data/test/skip_if_test.rb +0 -74
  101. data/test/validation/dry_test.rb +0 -60
  102. data/test/validation/dry_validation_test.rb +0 -352
  103. 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