reform 2.2.4 → 2.6.2

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