reform 2.2.4 → 2.3.3

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 (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,835 @@
1
+ require "test_helper"
2
+ require "reform/form/dry"
3
+ require "reform/form/coercion"
4
+ #---
5
+ # one "nested" Schema per form.
6
+ class DryValidationErrorsAPITest < Minitest::Spec
7
+ Album = Struct.new(:title, :artist, :songs)
8
+ Song = Struct.new(:title)
9
+ Artist = Struct.new(:email, :label)
10
+ Label = Struct.new(:location)
11
+
12
+ class AlbumForm < TestForm
13
+ property :title
14
+
15
+ validation do
16
+ params do
17
+ required(:title).filled(min_size?: 2)
18
+ end
19
+ end
20
+
21
+ property :artist do
22
+ property :email
23
+
24
+ validation do
25
+ params { required(:email).filled }
26
+ end
27
+
28
+ property :label do
29
+ property :location
30
+
31
+ validation do
32
+ params { 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
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
43
+
44
+ params { required(:title).filled }
45
+ end
46
+ end
47
+ end
48
+
49
+ let(:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
50
+
51
+ it "everything wrong" do
52
+ result = form.(title: nil, artist: {email: ""}, songs: [{title: "Clams have feelings too"}, {title: ""}])
53
+
54
+ _(result.success?).must_equal false
55
+
56
+ _(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"])
57
+ _(form.artist.errors.messages).must_equal(email: ["must be filled"], "label.location": ["must be filled"])
58
+ _(form.artist.label.errors.messages).must_equal(location: ["must be filled"])
59
+ _(form.songs[0].errors.messages).must_equal({})
60
+ _(form.songs[1].errors.messages).must_equal(title: ["must be filled"])
61
+
62
+ # #errors[]
63
+ _(form.errors[:nonsense]).must_equal []
64
+ _(form.errors[:title]).must_equal ["must be filled", "size cannot be less than 2"]
65
+ _(form.artist.errors[:email]).must_equal ["must be filled"]
66
+ _(form.artist.label.errors[:location]).must_equal ["must be filled"]
67
+ _(form.songs[0].errors[:title]).must_equal []
68
+ _(form.songs[1].errors[:title]).must_equal ["must be filled"]
69
+
70
+ # #to_result
71
+ _(form.to_result.errors).must_equal(title: ["must be filled"])
72
+ _(form.to_result.messages).must_equal(title: ["must be filled", "size cannot be less than 2"])
73
+ _(form.to_result.hints).must_equal(title: ["size cannot be less than 2"])
74
+ _(form.artist.to_result.errors).must_equal(email: ["must be filled"])
75
+ _(form.artist.to_result.messages).must_equal(email: ["must be filled"])
76
+ _(form.artist.to_result.hints).must_equal({})
77
+ _(form.artist.label.to_result.errors).must_equal(location: ["must be filled"])
78
+ _(form.artist.label.to_result.messages).must_equal(location: ["must be filled"])
79
+ _(form.artist.label.to_result.hints).must_equal({})
80
+ _(form.songs[0].to_result.errors).must_equal({})
81
+ _(form.songs[0].to_result.messages).must_equal({})
82
+ _(form.songs[0].to_result.hints).must_equal({})
83
+ _(form.songs[1].to_result.errors).must_equal(title: ["must be filled"])
84
+ _(form.songs[1].to_result.messages).must_equal(title: ["must be filled"])
85
+ _(form.songs[1].to_result.hints).must_equal({})
86
+ _(form.songs[1].to_result.errors(locale: :de)).must_equal(title: ["muss abgefüllt sein"])
87
+ # seems like dry-v when calling Dry::Schema::Result#messages locale option is ignored
88
+ # started a topic in their forum https://discourse.dry-rb.org/t/dry-result-messages-ignore-locale-option/910
89
+ # form.songs[1].to_result.messages(locale: :de).must_equal(title: ["muss abgefüllt sein"])
90
+ _(form.songs[1].to_result.hints(locale: :de)).must_equal({})
91
+ end
92
+
93
+ it "only nested property is invalid." do
94
+ result = form.(title: "Black Star", artist: {email: ""})
95
+
96
+ _(result.success?).must_equal false
97
+
98
+ # errors.messages
99
+ _(form.errors.messages).must_equal("artist.email": ["must be filled"], "artist.label.location": ["must be filled"], "songs.title": ["must be filled"])
100
+ _(form.artist.errors.messages).must_equal(email: ["must be filled"], "label.location": ["must be filled"])
101
+ _(form.artist.label.errors.messages).must_equal(location: ["must be filled"])
102
+ end
103
+
104
+ it "nested collection invalid" do
105
+ result = form.(title: "Black Star", artist: {email: "uhm", label: {location: "Hannover"}}, songs: [{title: ""}])
106
+
107
+ _(result.success?).must_equal false
108
+ _(form.errors.messages).must_equal("songs.title": ["must be filled"])
109
+ end
110
+
111
+ #---
112
+ #- validation .each
113
+ class CollectionExternalValidationsForm < TestForm
114
+ collection :songs do
115
+ property :title
116
+ end
117
+
118
+ validation do
119
+ params do
120
+ required(:songs).each do
121
+ schema do
122
+ required(:title).filled
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ it do
130
+ form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
131
+ form.validate(songs: [{title: "Liar"}, {title: ""}])
132
+
133
+ _(form.errors.messages).must_equal("songs.title": ["must be filled"])
134
+ _(form.songs[0].errors.messages).must_equal({})
135
+ _(form.songs[1].errors.messages).must_equal(title: ["must be filled"])
136
+ end
137
+ end
138
+
139
+ class DryValidationExplicitSchemaTest < Minitest::Spec
140
+ Session = Struct.new(:name, :email)
141
+ SessionContract = Dry::Validation.Contract do
142
+ params do
143
+ required(:name).filled
144
+ required(:email).filled
145
+ end
146
+ end
147
+
148
+ class SessionForm < TestForm
149
+ include Coercion
150
+
151
+ property :name
152
+ property :email
153
+
154
+ validation contract: SessionContract
155
+ end
156
+
157
+ let(:form) { SessionForm.new(Session.new) }
158
+
159
+ # valid.
160
+ it do
161
+ _(form.validate(name: "Helloween", email: "yep")).must_equal true
162
+ _(form.errors.messages.inspect).must_equal "{}"
163
+ end
164
+
165
+ it "invalid" do
166
+ _(form.validate(name: "", email: "yep")).must_equal false
167
+ _(form.errors.messages.inspect).must_equal "{:name=>[\"must be filled\"]}"
168
+ end
169
+ end
170
+
171
+ class DryValidationDefaultGroupTest < Minitest::Spec
172
+ Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
173
+
174
+ class SessionForm < TestForm
175
+ include Coercion
176
+
177
+ property :username
178
+ property :email
179
+ property :password
180
+ property :confirm_password
181
+ property :starts_at, type: DRY_TYPES_CONSTANT::DateTime
182
+ property :active, type: DRY_TYPES_CONSTANT::Bool
183
+ property :color
184
+
185
+ validation do
186
+ params do
187
+ required(:username).filled
188
+ required(:email).filled
189
+ required(:starts_at).filled(:date_time?)
190
+ required(:active).filled(:bool?)
191
+ end
192
+ end
193
+
194
+ validation name: :another_block do
195
+ params { required(:confirm_password).filled }
196
+ end
197
+
198
+ validation name: :dynamic_args do
199
+ option :form
200
+ params { optional(:color) }
201
+ rule(:color) do
202
+ if value
203
+ key.failure("must be one of: #{form.colors}") unless form.colors.include? value
204
+ end
205
+ end
206
+ end
207
+
208
+ def colors
209
+ %(red orange green)
210
+ end
211
+ end
212
+
213
+ let(:form) { SessionForm.new(Session.new) }
214
+
215
+ # valid.
216
+ it do
217
+ assert form.validate(
218
+ username: "Helloween",
219
+ email: "yep",
220
+ starts_at: "01/01/2000 - 11:00",
221
+ active: "true",
222
+ confirm_password: "pA55w0rd"
223
+ )
224
+ assert form.active
225
+ assert_equal "{}", form.errors.messages.inspect
226
+ end
227
+
228
+ it "invalid" do
229
+ _(form.validate(
230
+ username: "Helloween",
231
+ email: "yep",
232
+ active: "1",
233
+ starts_at: "01/01/2000 - 11:00",
234
+ color: "purple"
235
+ )).must_equal false
236
+ _(form.active).must_equal true
237
+ _(form.errors.messages.inspect).must_equal "{:confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
238
+ end
239
+ end
240
+
241
+ class ValidationGroupsTest < MiniTest::Spec
242
+ describe "basic validations" do
243
+ Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
244
+ SomeClass = Struct.new(:id)
245
+
246
+ class SessionForm < TestForm
247
+ property :username
248
+ property :email
249
+ property :password
250
+ property :confirm_password
251
+ property :special_class
252
+
253
+ validation do
254
+ params do
255
+ required(:username).filled
256
+ required(:email).filled
257
+ required(:special_class).filled(type?: SomeClass)
258
+ end
259
+ end
260
+
261
+ validation name: :email, if: :default do
262
+ params { required(:email).filled(min_size?: 3) }
263
+ end
264
+
265
+ validation name: :password, if: :email do
266
+ params { required(:password).filled(min_size?: 2) }
267
+ end
268
+
269
+ validation name: :confirm, if: :default, after: :email do
270
+ params { required(:confirm_password).filled(min_size?: 2) }
271
+ end
272
+ end
273
+
274
+ let(:form) { SessionForm.new(Session.new) }
275
+
276
+ # valid.
277
+ it do
278
+ _(form.validate(username: "Helloween",
279
+ special_class: SomeClass.new(id: 15),
280
+ email: "yep",
281
+ password: "99",
282
+ confirm_password: "99")).must_equal true
283
+ _(form.errors.messages.inspect).must_equal "{}"
284
+ end
285
+
286
+ # invalid.
287
+ it do
288
+ _(form.validate({})).must_equal false
289
+ _(form.errors.messages).must_equal({username: ["must be filled"], email: ["must be filled"], special_class: ["must be filled", "must be ValidationGroupsTest::SomeClass"]})
290
+ end
291
+
292
+ # partially invalid.
293
+ # 2nd group fails.
294
+ it do
295
+ _(form.validate(username: "Helloween", email: "yo", confirm_password: "9", special_class: SomeClass.new(id: 15))).must_equal false
296
+ _(form.errors.messages.inspect).must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"]}"
297
+ end
298
+ # 3rd group fails.
299
+ it do
300
+ _(form.validate(username: "Helloween", email: "yo!", confirm_password: "9", special_class: SomeClass.new(id: 15))).must_equal false
301
+ _(form.errors.messages.inspect)
302
+ .must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
303
+ end
304
+ # 4th group with after: fails.
305
+ it do
306
+ _(form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15))).must_equal false
307
+ _(form.errors.messages.inspect).must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
308
+ end
309
+ end
310
+
311
+ class ValidationWithOptionsTest < MiniTest::Spec
312
+ describe "basic validations" do
313
+ Session = Struct.new(:username)
314
+ class SessionForm < TestForm
315
+ property :username
316
+
317
+ validation name: :default, with: {user: OpenStruct.new(name: "Nick")} do
318
+ option :user
319
+ params do
320
+ required(:username).filled
321
+ end
322
+ rule(:username) do
323
+ key.failure("must be equal to #{user.name}") unless user.name == value
324
+ end
325
+ end
326
+ end
327
+
328
+ let(:form) { SessionForm.new(Session.new) }
329
+
330
+ # valid.
331
+ it do
332
+ _(form.validate(username: "Nick")).must_equal true
333
+ _(form.errors.messages.inspect).must_equal "{}"
334
+ end
335
+
336
+ # invalid.
337
+ it do
338
+ _(form.validate(username: "Fred")).must_equal false
339
+ _(form.errors.messages.inspect).must_equal "{:username=>[\"must be equal to Nick\"]}"
340
+ end
341
+ end
342
+ end
343
+
344
+ #---
345
+ describe "with custom schema" do
346
+ Session2 = Struct.new(:username, :email, :password)
347
+
348
+ MyContract = Dry::Schema.Params do
349
+ config.messages.load_paths << "test/fixtures/dry_error_messages.yml"
350
+
351
+ required(:password).filled(min_size?: 6)
352
+ end
353
+
354
+ class Session2Form < TestForm
355
+ property :username
356
+ property :email
357
+ property :password
358
+
359
+ validation contract: MyContract do
360
+ params do
361
+ required(:username).filled
362
+ required(:email).filled
363
+ end
364
+
365
+ rule(:email) do
366
+ key.failure(:good_musical_taste?) unless value.is_a? String
367
+ end
368
+ end
369
+ end
370
+
371
+ let(:form) { Session2Form.new(Session2.new) }
372
+
373
+ # valid.
374
+ it do
375
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
376
+ _(form.validate(username: "Helloween", email: "yep", password: "extrasafe")).must_equal true
377
+ _(form.errors.messages.inspect).must_equal "{}"
378
+ end
379
+
380
+ # invalid.
381
+ it do
382
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
383
+ _(form.validate({})).must_equal false
384
+ _(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"])
385
+ end
386
+
387
+ it do
388
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
389
+ _(form.validate(email: 1)).must_equal false
390
+ _(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\"]}"
391
+ end
392
+ end
393
+
394
+ describe "MIXED nested validations" do
395
+ class AlbumForm < TestForm
396
+ property :title
397
+
398
+ property :hit do
399
+ property :title
400
+
401
+ validation do
402
+ params { required(:title).filled }
403
+ end
404
+ end
405
+
406
+ collection :songs do
407
+ property :title
408
+
409
+ validation do
410
+ params { required(:title).filled }
411
+ end
412
+ end
413
+
414
+ # we test this one by running an each / schema dry-v check on the main block
415
+ collection :producers do
416
+ property :name
417
+ end
418
+
419
+ property :band do
420
+ property :name
421
+ property :label do
422
+ property :location
423
+ end
424
+ end
425
+
426
+ validation do
427
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
428
+ params do
429
+ required(:title).filled
430
+ required(:band).hash do
431
+ required(:name).filled
432
+ required(:label).hash do
433
+ required(:location).filled
434
+ end
435
+ end
436
+
437
+ required(:producers).each do
438
+ hash { required(:name).filled }
439
+ end
440
+ end
441
+
442
+ rule(:title) do
443
+ key.failure(:good_musical_taste?) unless value != "Nickelback"
444
+ end
445
+ end
446
+ end
447
+
448
+ let(:album) do
449
+ OpenStruct.new(
450
+ hit: OpenStruct.new,
451
+ songs: [OpenStruct.new, OpenStruct.new],
452
+ band: Struct.new(:name, :label).new("", OpenStruct.new),
453
+ producers: [OpenStruct.new, OpenStruct.new, OpenStruct.new],
454
+ )
455
+ end
456
+
457
+ let(:form) { AlbumForm.new(album) }
458
+
459
+ it "maps errors to form objects correctly" do
460
+ result = form.validate(
461
+ "title" => "Nickelback",
462
+ "songs" => [{"title" => ""}, {"title" => ""}],
463
+ "band" => {"size" => "", "label" => {"location" => ""}},
464
+ "producers" => [{"name" => ""}, {"name" => "something lovely"}]
465
+ )
466
+
467
+ _(result).must_equal false
468
+ # from nested validation
469
+ _(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"])
470
+
471
+ # songs have their own validation.
472
+ _(form.songs[0].errors.messages).must_equal(title: ["must be filled"])
473
+ # hit got its own validation group.
474
+ _(form.hit.errors.messages).must_equal(title: ["must be filled"])
475
+
476
+ _(form.band.label.errors.messages).must_equal(location: ["must be filled"])
477
+ _(form.band.errors.messages).must_equal(name: ["must be filled"], "label.location": ["must be filled"])
478
+ _(form.producers[0].errors.messages).must_equal(name: ["must be filled"])
479
+
480
+ # TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
481
+ _(form.producers[0].to_result.errors).must_equal(name: ["must be filled"])
482
+ _(form.producers[0].to_result.messages).must_equal(name: ["must be filled"])
483
+ _(form.producers[0].to_result.hints).must_equal({})
484
+ end
485
+
486
+ # FIXME: fix the "must be filled error"
487
+
488
+ it "renders full messages correctly" do
489
+ result = form.validate(
490
+ "title" => "",
491
+ "songs" => [{"title" => ""}, {"title" => ""}],
492
+ "band" => {"size" => "", "label" => {"name" => ""}},
493
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
494
+ )
495
+
496
+ _(result).must_equal false
497
+ _(form.band.errors.full_messages).must_equal ["Name must be filled", "Label Location must be filled"]
498
+ _(form.band.label.errors.full_messages).must_equal ["Location must be filled"]
499
+ _(form.producers.first.errors.full_messages).must_equal ["Name must be filled"]
500
+ _(form.errors.full_messages).must_equal ["Title must be filled", "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"]
501
+ end
502
+
503
+ describe "only 1 nested validation" do
504
+ class AlbumFormWith1NestedVal < TestForm
505
+ property :title
506
+ property :band do
507
+ property :name
508
+ property :label do
509
+ property :location
510
+ end
511
+ end
512
+
513
+ validation do
514
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
515
+
516
+ params do
517
+ required(:title).filled
518
+
519
+ required(:band).schema do
520
+ required(:name).filled
521
+ required(:label).schema do
522
+ required(:location).filled
523
+ end
524
+ end
525
+ end
526
+ end
527
+ end
528
+
529
+ let(:form) { AlbumFormWith1NestedVal.new(album) }
530
+
531
+ it "allows to access dry's result semantics per nested form" do
532
+ form.validate(
533
+ "title" => "",
534
+ "songs" => [{"title" => ""}, {"title" => ""}],
535
+ "band" => {"size" => "", "label" => {"name" => ""}},
536
+ "producers" => [{"name" => ""}, {"name" => ""}, {"name" => "something lovely"}]
537
+ )
538
+
539
+ _(form.to_result.errors).must_equal(title: ["must be filled"])
540
+ _(form.band.to_result.errors).must_equal(name: ["must be filled"])
541
+ _(form.band.label.to_result.errors).must_equal(location: ["must be filled"])
542
+
543
+ # with locale: "de"
544
+ _(form.to_result.errors(locale: :de)).must_equal(title: ["muss abgefüllt sein"])
545
+ _(form.band.to_result.errors(locale: :de)).must_equal(name: ["muss abgefüllt sein"])
546
+ _(form.band.label.to_result.errors(locale: :de)).must_equal(location: ["muss abgefüllt sein"])
547
+ end
548
+ end
549
+ end
550
+
551
+ # describe "same-named group" do
552
+ # class OverwritingForm < TestForm
553
+ # include Reform::Form::Dry::Validations
554
+
555
+ # property :username
556
+ # property :email
557
+
558
+ # validation :email do # FIX ME: is this working for other validator or just bugging here?
559
+ # key(:email, &:filled?) # it's not considered, overitten
560
+ # end
561
+
562
+ # validation :email do # just another group.
563
+ # key(:username, &:filled?)
564
+ # end
565
+ # end
566
+
567
+ # let(:form) { OverwritingForm.new(Session.new) }
568
+
569
+ # # valid.
570
+ # it do
571
+ # form.validate({username: "Helloween"}).must_equal true
572
+ # end
573
+
574
+ # # invalid.
575
+ # it "whoo" do
576
+ # form.validate({}).must_equal false
577
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
578
+ # end
579
+ # end
580
+
581
+ describe "inherit: true in same group" do
582
+ class InheritSameGroupForm < TestForm
583
+ property :username
584
+ property :email
585
+ property :full_name, virtual: true
586
+
587
+ validation name: :username do
588
+ params do
589
+ required(:username).filled
590
+ required(:full_name).filled
591
+ end
592
+ end
593
+
594
+ validation name: :username, inherit: true do # extends the above.
595
+ params do
596
+ optional(:username).maybe(:string)
597
+ required(:email).filled
598
+ end
599
+ end
600
+ end
601
+
602
+ let(:form) { InheritSameGroupForm.new(Session.new) }
603
+
604
+ # valid.
605
+ it do
606
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
607
+ _(form.validate(email: 9)).must_equal true
608
+ end
609
+
610
+ # invalid.
611
+ it do
612
+ skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
613
+ _(form.validate({})).must_equal false
614
+ _(form.errors.messages).must_equal email: ["must be filled"], full_name: ["must be filled"]
615
+ end
616
+ end
617
+
618
+ describe "if: with lambda" do
619
+ class IfWithLambdaForm < TestForm
620
+ property :username
621
+ property :email
622
+ property :password
623
+
624
+ validation name: :email do
625
+ params { required(:email).filled }
626
+ end
627
+
628
+ # run this is :email group is true.
629
+ validation name: :after_email, if: ->(results) { results[:email].success? } do # extends the above.
630
+ params { required(:username).filled }
631
+ end
632
+
633
+ # block gets evaled in form instance context.
634
+ validation name: :password, if: ->(results) { email == "john@trb.org" } do
635
+ params { required(:password).filled }
636
+ end
637
+ end
638
+
639
+ let(:form) { IfWithLambdaForm.new(Session.new) }
640
+
641
+ # valid.
642
+ it do
643
+ _(form.validate(username: "Strung Out", email: 9)).must_equal true
644
+ end
645
+
646
+ # invalid.
647
+ it do
648
+ _(form.validate(email: 9)).must_equal false
649
+ _(form.errors.messages.inspect).must_equal "{:username=>[\"must be filled\"]}"
650
+ end
651
+ end
652
+
653
+ class NestedSchemaValidationTest < MiniTest::Spec
654
+ AddressSchema = Dry::Schema.Params do
655
+ required(:company).filled(:int?)
656
+ end
657
+
658
+ class OrderForm < TestForm
659
+ property :delivery_address do
660
+ property :company
661
+ end
662
+
663
+ validation do
664
+ params { required(:delivery_address).schema(AddressSchema) }
665
+ end
666
+ end
667
+
668
+ let(:company) { Struct.new(:company) }
669
+ let(:order) { Struct.new(:delivery_address) }
670
+ let(:form) { OrderForm.new(order.new(company.new)) }
671
+
672
+ it "has company error" do
673
+ _(form.validate(delivery_address: {company: "not int"})).must_equal false
674
+ _(form.errors.messages).must_equal(:"delivery_address.company" => ["must be an integer"])
675
+ end
676
+ end
677
+
678
+ class NestedSchemaValidationWithFormTest < MiniTest::Spec
679
+ class CompanyForm < TestForm
680
+ property :company
681
+
682
+ validation do
683
+ params { required(:company).filled(:int?) }
684
+ end
685
+ end
686
+
687
+ class OrderFormWithForm < TestForm
688
+ property :delivery_address, form: CompanyForm
689
+ end
690
+
691
+ let(:company) { Struct.new(:company) }
692
+ let(:order) { Struct.new(:delivery_address) }
693
+ let(:form) { OrderFormWithForm.new(order.new(company.new)) }
694
+
695
+ it "has company error" do
696
+ _(form.validate(delivery_address: {company: "not int"})).must_equal false
697
+ _(form.errors.messages).must_equal(:"delivery_address.company" => ["must be an integer"])
698
+ end
699
+ end
700
+
701
+ class CollectionPropertyWithCustomRuleTest < MiniTest::Spec
702
+ Artist = Struct.new(:first_name, :last_name)
703
+ Song = Struct.new(:title, :enabled)
704
+ Album = Struct.new(:title, :songs, :artist)
705
+
706
+ class AlbumForm < TestForm
707
+ property :title
708
+
709
+ collection :songs, virtual: true, populate_if_empty: Song do
710
+ property :title
711
+ property :enabled
712
+
713
+ validation do
714
+ params { required(:title).filled }
715
+ end
716
+ end
717
+
718
+ property :artist, populate_if_empty: Artist do
719
+ property :first_name
720
+ property :last_name
721
+ end
722
+
723
+ validation do
724
+ config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
725
+
726
+ params do
727
+ required(:songs).filled
728
+ required(:artist).filled
729
+ end
730
+
731
+ rule(:songs) do
732
+ key.failure(:a_song?) unless value.any? { |el| el && el[:enabled] }
733
+ end
734
+
735
+ rule(:artist) do
736
+ key.failure(:with_last_name?) unless value[:last_name]
737
+ end
738
+ end
739
+ end
740
+
741
+ it "validates fails and shows the correct errors" do
742
+ form = AlbumForm.new(Album.new(nil, [], nil))
743
+ _(form.validate(
744
+ "songs" => [
745
+ {"title" => "One", "enabled" => false},
746
+ {"title" => nil, "enabled" => false},
747
+ {"title" => "Three", "enabled" => false}
748
+ ],
749
+ "artist" => {"last_name" => nil}
750
+ )).must_equal false
751
+ _(form.songs.size).must_equal 3
752
+
753
+ _(form.errors.messages).must_equal(
754
+ :songs => ["must have at least one enabled song"],
755
+ :artist => ["must have last name"],
756
+ :"songs.title" => ["must be filled"]
757
+ )
758
+ end
759
+ end
760
+
761
+ class DryVWithSchemaAndParams < MiniTest::Spec
762
+ Foo = Struct.new(:age)
763
+
764
+ class ParamsForm < TestForm
765
+ property :age
766
+
767
+ validation do
768
+ params { required(:age).value(:integer) }
769
+
770
+ rule(:age) { key.failure("value exceeded") if value > 999 }
771
+ end
772
+ end
773
+
774
+ class SchemaForm < TestForm
775
+ property :age
776
+
777
+ validation do
778
+ schema { required(:age).value(:integer) }
779
+
780
+ rule(:age) { key.failure("value exceeded") if value > 999 }
781
+ end
782
+ end
783
+
784
+ it "using params" do
785
+ model = Foo.new
786
+ form = ParamsForm.new(model)
787
+ _(form.validate(age: "99")).must_equal true
788
+ form.sync
789
+ _(model.age).must_equal "99"
790
+
791
+ form = ParamsForm.new(Foo.new)
792
+ _(form.validate(age: "1000")).must_equal false
793
+ _(form.errors.messages).must_equal age: ["value exceeded"]
794
+ end
795
+
796
+ it "using schema" do
797
+ model = Foo.new
798
+ form = SchemaForm.new(model)
799
+ _(form.validate(age: "99")).must_equal false
800
+ _(form.validate(age: 99)).must_equal true
801
+ form.sync
802
+ _(model.age).must_equal 99
803
+
804
+ form = SchemaForm.new(Foo.new)
805
+ _(form.validate(age: 1000)).must_equal false
806
+ _(form.errors.messages).must_equal age: ["value exceeded"]
807
+ end
808
+ end
809
+
810
+ # Currenty dry-v don't support that option, it doesn't make sense
811
+ # I've talked to @solnic and he plans to add a "hint" feature to show
812
+ # more errors messages than only those that have failed.
813
+ #
814
+ # describe "multiple errors for property" do
815
+ # class MultipleErrorsForPropertyForm < TestForm
816
+ # include Reform::Form::Dry::Validations
817
+
818
+ # property :username
819
+
820
+ # validation :default do
821
+ # key(:username) do |username|
822
+ # username.filled? | (username.min_size?(2) & username.max_size?(3))
823
+ # end
824
+ # end
825
+ # end
826
+
827
+ # let(:form) { MultipleErrorsForPropertyForm.new(Session.new) }
828
+
829
+ # # valid.
830
+ # it do
831
+ # form.validate({username: ""}).must_equal false
832
+ # form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\", \"username is not proper size\"]}"
833
+ # end
834
+ # end
835
+ end