reform 2.2.4 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +5 -1
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +460 -0
- data/.travis.yml +11 -6
- data/Appraisals +8 -0
- data/CHANGES.md +54 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +2 -16
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +5 -7
- data/Rakefile +18 -9
- data/gemfiles/0.13.0.gemfile +8 -0
- data/gemfiles/1.5.0.gemfile +9 -0
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +7 -17
- data/lib/reform/contract/custom_error.rb +41 -0
- data/lib/reform/contract/validate.rb +53 -23
- data/lib/reform/errors.rb +61 -0
- data/lib/reform/form.rb +36 -10
- data/lib/reform/form/call.rb +1 -1
- data/lib/reform/form/composition.rb +2 -2
- data/lib/reform/form/dry.rb +10 -58
- data/lib/reform/form/dry/input_hash.rb +37 -0
- data/lib/reform/form/dry/new_api.rb +46 -0
- data/lib/reform/form/dry/old_api.rb +61 -0
- data/lib/reform/form/populator.rb +11 -27
- data/lib/reform/form/prepopulate.rb +4 -3
- data/lib/reform/form/validate.rb +28 -13
- data/lib/reform/result.rb +90 -0
- data/lib/reform/validation.rb +19 -11
- data/lib/reform/validation/groups.rb +12 -27
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +15 -13
- data/test/benchmarking.rb +39 -6
- data/test/call_new_api.rb +23 -0
- data/test/{call_test.rb → call_old_api.rb} +4 -4
- data/test/changed_test.rb +8 -8
- data/test/coercion_test.rb +51 -19
- data/test/composition_new_api.rb +186 -0
- data/test/{composition_test.rb → composition_old_api.rb} +66 -31
- data/test/contract/custom_error_test.rb +55 -0
- data/test/contract_new_api.rb +77 -0
- data/test/{contract_test.rb → contract_old_api.rb} +13 -13
- data/test/default_test.rb +2 -2
- data/test/deserialize_test.rb +11 -14
- data/test/errors_new_api.rb +225 -0
- data/test/errors_old_api.rb +230 -0
- data/test/feature_test.rb +8 -10
- data/test/fixtures/dry_error_messages.yml +73 -23
- data/test/fixtures/dry_new_api_error_messages.yml +104 -0
- data/test/form_new_api.rb +57 -0
- data/test/{form_test.rb → form_old_api.rb} +5 -5
- data/test/form_option_new_api.rb +24 -0
- data/test/{form_option_test.rb → form_option_old_api.rb} +4 -4
- data/test/from_test.rb +9 -13
- data/test/inherit_new_api.rb +105 -0
- data/test/inherit_old_api.rb +105 -0
- data/test/{module_test.rb → module_new_api.rb} +20 -25
- data/test/module_old_api.rb +146 -0
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +3 -3
- data/test/populate_new_api.rb +304 -0
- data/test/{populate_test.rb → populate_old_api.rb} +83 -49
- data/test/populator_skip_test.rb +9 -9
- data/test/prepopulator_test.rb +8 -9
- data/test/read_only_test.rb +12 -1
- data/test/readable_test.rb +7 -7
- data/test/reform_new_api.rb +204 -0
- data/test/{reform_test.rb → reform_old_api.rb} +30 -51
- data/test/save_new_api.rb +101 -0
- data/test/{save_test.rb → save_old_api.rb} +32 -20
- data/test/setup_test.rb +8 -8
- data/test/{skip_if_test.rb → skip_if_new_api.rb} +23 -12
- data/test/skip_if_old_api.rb +92 -0
- data/test/skip_setter_and_getter_test.rb +3 -4
- data/test/test_helper.rb +25 -14
- data/test/validate_new_api.rb +408 -0
- data/test/{validate_test.rb → validate_old_api.rb} +59 -69
- data/test/validation/dry_validation_new_api.rb +836 -0
- data/test/validation/dry_validation_old_api.rb +772 -0
- data/test/validation/result_test.rb +77 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +47 -7
- data/test/writeable_test.rb +35 -6
- metadata +127 -56
- data/gemfiles/Gemfile.disposable-0.3 +0 -6
- data/lib/reform/contract/errors.rb +0 -43
- data/lib/reform/form/mongoid.rb +0 -37
- data/lib/reform/form/orm.rb +0 -26
- data/lib/reform/mongoid.rb +0 -4
- data/test/deprecation_test.rb +0 -27
- data/test/errors_test.rb +0 -165
- data/test/inherit_test.rb +0 -119
- data/test/readonly_test.rb +0 -14
- data/test/validation/dry_test.rb +0 -60
- data/test/validation/dry_validation_test.rb +0 -352
- data/test/validation/errors.yml +0 -4
@@ -1,25 +1,25 @@
|
|
1
|
-
require
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
class ContractValidateTest < MiniTest::Spec
|
4
4
|
Song = Struct.new(:title, :album, :composer)
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestContract
|
9
9
|
property :name
|
10
10
|
validation do
|
11
|
-
|
11
|
+
required(:name).filled
|
12
12
|
end
|
13
13
|
|
14
14
|
collection :songs do
|
15
15
|
property :title
|
16
16
|
validation do
|
17
|
-
|
17
|
+
required(:title).filled
|
18
18
|
end
|
19
19
|
|
20
20
|
property :composer do
|
21
21
|
validation do
|
22
|
-
|
22
|
+
required(:name).filled
|
23
23
|
end
|
24
24
|
property :name
|
25
25
|
end
|
@@ -30,13 +30,13 @@ class ContractValidateTest < MiniTest::Spec
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
let
|
34
|
-
let
|
35
|
-
let
|
36
|
-
let
|
37
|
-
let
|
33
|
+
let(:song) { Song.new("Broken") }
|
34
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
35
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
36
|
+
let(:artist) { Artist.new("Bad Religion") }
|
37
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
38
38
|
|
39
|
-
let
|
39
|
+
let(:form) { AlbumForm.new(album) }
|
40
40
|
|
41
41
|
# valid
|
42
42
|
it do
|
@@ -50,34 +50,32 @@ class ContractValidateTest < MiniTest::Spec
|
|
50
50
|
album.name = nil
|
51
51
|
|
52
52
|
form.validate.must_equal false
|
53
|
-
form.errors.messages.inspect.must_equal "{:name=>[\"
|
53
|
+
form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"]}"
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
|
58
57
|
# no configuration results in "sync" (formerly known as parse_strategy: :sync).
|
59
58
|
class ValidateWithoutConfigurationTest < MiniTest::Spec
|
60
59
|
Song = Struct.new(:title, :album, :composer)
|
61
60
|
Album = Struct.new(:name, :songs, :artist)
|
62
61
|
Artist = Struct.new(:name)
|
63
62
|
|
64
|
-
class AlbumForm <
|
63
|
+
class AlbumForm < TestForm
|
65
64
|
property :name
|
66
65
|
validation do
|
67
|
-
|
66
|
+
required(:name).filled
|
68
67
|
end
|
69
68
|
|
70
69
|
collection :songs do
|
71
|
-
|
72
70
|
property :title
|
73
71
|
validation do
|
74
|
-
|
72
|
+
required(:title).filled
|
75
73
|
end
|
76
74
|
|
77
75
|
property :composer do
|
78
76
|
property :name
|
79
77
|
validation do
|
80
|
-
|
78
|
+
required(:name).filled
|
81
79
|
end
|
82
80
|
end
|
83
81
|
end
|
@@ -87,23 +85,25 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
|
-
let
|
91
|
-
let
|
92
|
-
let
|
93
|
-
let
|
94
|
-
let
|
88
|
+
let(:song) { Song.new("Broken") }
|
89
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
90
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
91
|
+
let(:artist) { Artist.new("Bad Religion") }
|
92
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
95
93
|
|
96
|
-
let
|
94
|
+
let(:form) { AlbumForm.new(album) }
|
97
95
|
|
98
96
|
# valid.
|
99
97
|
it do
|
100
|
-
object_ids = {
|
101
|
-
|
98
|
+
object_ids = {
|
99
|
+
song: form.songs[0].object_id, song_with_composer: form.songs[1].object_id,
|
100
|
+
artist: form.artist.object_id, composer: form.songs[1].composer.object_id
|
101
|
+
}
|
102
102
|
|
103
103
|
form.validate(
|
104
104
|
"name" => "Best Of",
|
105
105
|
"songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
|
106
|
-
"artist" => {"name" => "The Police"}
|
106
|
+
"artist" => {"name" => "The Police"}
|
107
107
|
).must_equal true
|
108
108
|
|
109
109
|
form.errors.messages.inspect.must_equal "{}"
|
@@ -121,7 +121,6 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
|
|
121
121
|
form.songs[1].composer.object_id.must_equal object_ids[:composer]
|
122
122
|
form.artist.object_id.must_equal object_ids[:artist]
|
123
123
|
|
124
|
-
|
125
124
|
# model has not changed, yet.
|
126
125
|
album.name.must_equal "The Dissent Of Man"
|
127
126
|
album.songs[0].title.must_equal "Broken"
|
@@ -135,7 +134,7 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
|
|
135
134
|
form.validate(
|
136
135
|
name: "Best Of",
|
137
136
|
songs: [{title: "The X-Creep"}, {title: "Trudging", composer: {name: "SNFU"}}],
|
138
|
-
artist: {name: "The Police"}
|
137
|
+
artist: {name: "The Police"}
|
139
138
|
).must_equal true
|
140
139
|
|
141
140
|
form.name.must_equal "Best Of"
|
@@ -160,45 +159,45 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
160
159
|
Album = Struct.new(:name, :songs, :artist)
|
161
160
|
Artist = Struct.new(:name)
|
162
161
|
|
163
|
-
class AlbumForm <
|
162
|
+
class AlbumForm < TestForm
|
164
163
|
property :name
|
165
164
|
validation do
|
166
|
-
|
165
|
+
required(:name).filled
|
167
166
|
end
|
168
167
|
|
169
168
|
collection :songs,
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
169
|
+
internal_populator: ->(input, options) {
|
170
|
+
collection = options[:represented].songs
|
171
|
+
(item = collection[options[:index]]) ? item : collection.insert(options[:index], Song.new)
|
172
|
+
} do
|
174
173
|
property :title
|
175
174
|
validation do
|
176
|
-
|
175
|
+
required(:title).filled
|
177
176
|
end
|
178
177
|
|
179
|
-
property :composer, internal_populator:
|
178
|
+
property :composer, internal_populator: ->(input, options) { (item = options[:represented].composer) ? item : Artist.new } do
|
180
179
|
property :name
|
181
180
|
validation do
|
182
|
-
|
181
|
+
required(:name).filled
|
183
182
|
end
|
184
183
|
end
|
185
184
|
end
|
186
185
|
|
187
|
-
property :artist, internal_populator:
|
186
|
+
property :artist, internal_populator: ->(input, options) { (item = options[:represented].artist) ? item : Artist.new } do
|
188
187
|
property :name
|
189
188
|
validation do
|
190
|
-
|
189
|
+
required(:name).filled
|
191
190
|
end
|
192
191
|
end
|
193
192
|
end
|
194
193
|
|
195
|
-
let
|
196
|
-
let
|
197
|
-
let
|
198
|
-
let
|
199
|
-
let
|
194
|
+
let(:song) { Song.new("Broken") }
|
195
|
+
let(:song_with_composer) { Song.new("Resist Stance", nil, composer) }
|
196
|
+
let(:composer) { Artist.new("Greg Graffin") }
|
197
|
+
let(:artist) { Artist.new("Bad Religion") }
|
198
|
+
let(:album) { Album.new("The Dissent Of Man", [song, song_with_composer], artist) }
|
200
199
|
|
201
|
-
let
|
200
|
+
let(:form) { AlbumForm.new(album) }
|
202
201
|
|
203
202
|
# valid.
|
204
203
|
it("xxx") do
|
@@ -207,6 +206,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
207
206
|
"songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => "Sting"}}],
|
208
207
|
"artist" => {"name" => "The Police"},
|
209
208
|
).must_equal true
|
209
|
+
form.valid?.must_equal true
|
210
210
|
|
211
211
|
form.errors.messages.inspect.must_equal "{}"
|
212
212
|
|
@@ -217,7 +217,6 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
217
217
|
form.songs[1].composer.name.must_equal "Sting"
|
218
218
|
form.artist.name.must_equal "The Police"
|
219
219
|
|
220
|
-
|
221
220
|
# model has not changed, yet.
|
222
221
|
album.name.must_equal "The Dissent Of Man"
|
223
222
|
album.songs[0].title.must_equal "Broken"
|
@@ -233,6 +232,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
233
232
|
"songs" => [{"title" => "Fallout"}, {"title" => "Roxanne", "composer" => {"name" => ""}}],
|
234
233
|
"artist" => {"name" => ""},
|
235
234
|
).must_equal false
|
235
|
+
form.valid?.must_equal false
|
236
236
|
|
237
237
|
form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"], :\"songs.composer.name\"=>[\"must be filled\"], :\"artist.name\"=>[\"must be filled\"]}"
|
238
238
|
end
|
@@ -256,7 +256,6 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
256
256
|
form.songs.size.must_equal 3
|
257
257
|
form.artist.name.must_equal "Bad Religion"
|
258
258
|
|
259
|
-
|
260
259
|
# model has not changed, yet.
|
261
260
|
album.name.must_equal "The Dissent Of Man"
|
262
261
|
album.songs[0].title.must_equal "Broken"
|
@@ -266,25 +265,24 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
266
265
|
album.artist.name.must_equal "Bad Religion"
|
267
266
|
end
|
268
267
|
|
269
|
-
|
270
268
|
# allow writeable: false even in the deserializer.
|
271
|
-
class SongForm <
|
269
|
+
class SongForm < TestForm
|
272
270
|
property :title, deserializer: {writeable: false}
|
273
271
|
end
|
274
272
|
|
275
273
|
it do
|
276
274
|
form = SongForm.new(song = Song.new)
|
277
275
|
form.validate("title" => "Ignore me!")
|
278
|
-
form.title
|
276
|
+
assert_nil form.title
|
279
277
|
form.title = "Unopened"
|
280
278
|
form.sync # only the deserializer is marked as not-writeable.
|
281
279
|
song.title.must_equal "Unopened"
|
282
280
|
end
|
283
281
|
end
|
284
282
|
|
285
|
-
# # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/
|
283
|
+
# # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/trailblazer/reform/pull/104
|
286
284
|
# # describe ":populator with :empty" do
|
287
|
-
# # let
|
285
|
+
# # let(:form) {
|
288
286
|
# # Class.new(Reform::Form) do
|
289
287
|
# # collection :songs, :empty => true, :populator => lambda { |fragment, index, args|
|
290
288
|
# # songs[index] = args.binding[:form].new(Song.new)
|
@@ -294,7 +292,7 @@ end
|
|
294
292
|
# # end
|
295
293
|
# # }
|
296
294
|
|
297
|
-
# # let
|
295
|
+
# # let(:params) {
|
298
296
|
# # {
|
299
297
|
# # "songs" => [{"title" => "Fallout"}, {"title" => "Roxanne"}]
|
300
298
|
# # }
|
@@ -308,10 +306,9 @@ end
|
|
308
306
|
# # it { subject.songs[1].title.must_equal "Roxanne" }
|
309
307
|
# # end
|
310
308
|
|
311
|
-
|
312
309
|
# # test cardinalities.
|
313
310
|
# describe "with empty collection and cardinality" do
|
314
|
-
# let
|
311
|
+
# let(:album) { Album.new }
|
315
312
|
|
316
313
|
# subject { Class.new(Reform::Form) do
|
317
314
|
# include Reform::Form::ActiveModel
|
@@ -329,7 +326,6 @@ end
|
|
329
326
|
# validates :hit, :presence => true
|
330
327
|
# end.new(album) }
|
331
328
|
|
332
|
-
|
333
329
|
# describe "invalid" do
|
334
330
|
# before { subject.validate({}).must_equal false }
|
335
331
|
|
@@ -342,9 +338,8 @@ end
|
|
342
338
|
# end
|
343
339
|
# end
|
344
340
|
|
345
|
-
|
346
341
|
# describe "valid" do
|
347
|
-
# let
|
342
|
+
# let(:album) { Album.new(nil, Song.new, [Song.new("Urban Myth")]) }
|
348
343
|
|
349
344
|
# before {
|
350
345
|
# subject.validate({"songs" => [{"title"=>"Daddy, Brother, Lover, Little Boy"}], "hit" => {"title"=>"The Horse"}}).
|
@@ -355,13 +350,9 @@ end
|
|
355
350
|
# end
|
356
351
|
# end
|
357
352
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
353
|
# # providing manual validator method allows accessing form's API.
|
363
354
|
# describe "with ::validate" do
|
364
|
-
# let
|
355
|
+
# let(:form) {
|
365
356
|
# Class.new(Reform::Form) do
|
366
357
|
# property :title
|
367
358
|
|
@@ -373,8 +364,8 @@ end
|
|
373
364
|
# end
|
374
365
|
# }
|
375
366
|
|
376
|
-
# let
|
377
|
-
# let
|
367
|
+
# let(:params) { {"title" => "Fallout"} }
|
368
|
+
# let(:song) { Song.new("Englishman") }
|
378
369
|
|
379
370
|
# subject { form.new(song) }
|
380
371
|
|
@@ -384,10 +375,9 @@ end
|
|
384
375
|
# it { subject.errors.messages.must_equal({:title=>["not lowercase"]}) }
|
385
376
|
# end
|
386
377
|
|
387
|
-
|
388
378
|
# # overriding the reader for a nested form should only be considered when rendering.
|
389
379
|
# describe "with overridden reader for nested form" do
|
390
|
-
# let
|
380
|
+
# let(:form) {
|
391
381
|
# Class.new(Reform::Form) do
|
392
382
|
# property :band, :populate_if_empty => lambda { |*| Band.new } do
|
393
383
|
# property :label
|
@@ -407,7 +397,7 @@ end
|
|
407
397
|
# end.new(album)
|
408
398
|
# }
|
409
399
|
|
410
|
-
# let
|
400
|
+
# let(:album) { Album.new }
|
411
401
|
|
412
402
|
# # don't use #artist when validating!
|
413
403
|
# it do
|
@@ -0,0 +1,836 @@
|
|
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
|
+
SessionSchema = 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 schema: SessionSchema
|
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
|
+
#- validation( schema: MySchema )
|
346
|
+
describe "with custom schema" do
|
347
|
+
Session2 = Struct.new(:username, :email, :password)
|
348
|
+
|
349
|
+
MySchema = 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 schema: MySchema 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
|
+
form.validate(username: "Helloween", email: "yep", password: "extrasafe").must_equal true
|
378
|
+
form.errors.messages.inspect.must_equal "{}"
|
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
|
+
form.validate({}).must_equal false
|
385
|
+
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"])
|
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
|
+
form.validate(email: 1).must_equal false
|
391
|
+
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\"]}"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "MIXED nested validations" do
|
396
|
+
class AlbumForm < TestForm
|
397
|
+
property :title
|
398
|
+
|
399
|
+
property :hit do
|
400
|
+
property :title
|
401
|
+
|
402
|
+
validation do
|
403
|
+
params { required(:title).filled }
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
collection :songs do
|
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
|
418
|
+
end
|
419
|
+
|
420
|
+
property :band do
|
421
|
+
property :name
|
422
|
+
property :label do
|
423
|
+
property :location
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
validation do
|
428
|
+
config.messages.load_paths << "test/fixtures/dry_new_api_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
|
436
|
+
end
|
437
|
+
|
438
|
+
required(:producers).each do
|
439
|
+
hash { required(:name).filled }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
rule(:title) do
|
444
|
+
key.failure(:good_musical_taste?) unless value != "Nickelback"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
let(:album) do
|
450
|
+
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],
|
455
|
+
)
|
456
|
+
end
|
457
|
+
|
458
|
+
let(:form) { AlbumForm.new(album) }
|
459
|
+
|
460
|
+
it "maps errors to form objects correctly" do
|
461
|
+
result = form.validate(
|
462
|
+
"title" => "Nickelback",
|
463
|
+
"songs" => [{"title" => ""}, {"title" => ""}],
|
464
|
+
"band" => {"size" => "", "label" => {"location" => ""}},
|
465
|
+
"producers" => [{"name" => ""}, {"name" => "something lovely"}]
|
466
|
+
)
|
467
|
+
|
468
|
+
result.must_equal false
|
469
|
+
# from nested validation
|
470
|
+
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"])
|
471
|
+
|
472
|
+
# songs have their own validation.
|
473
|
+
form.songs[0].errors.messages.must_equal(title: ["must be filled"])
|
474
|
+
# hit got its own validation group.
|
475
|
+
form.hit.errors.messages.must_equal(title: ["must be filled"])
|
476
|
+
|
477
|
+
form.band.label.errors.messages.must_equal(location: ["must be filled"])
|
478
|
+
form.band.errors.messages.must_equal(name: ["must be filled"], "label.location": ["must be filled"])
|
479
|
+
form.producers[0].errors.messages.must_equal(name: ["must be filled"])
|
480
|
+
|
481
|
+
# TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
|
482
|
+
form.producers[0].to_result.errors.must_equal(name: ["must be filled"])
|
483
|
+
form.producers[0].to_result.messages.must_equal(name: ["must be filled"])
|
484
|
+
form.producers[0].to_result.hints.must_equal({})
|
485
|
+
end
|
486
|
+
|
487
|
+
# FIXME: fix the "must be filled error"
|
488
|
+
|
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
|
+
result.must_equal false
|
498
|
+
form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
|
499
|
+
form.band.label.errors.full_messages.must_equal ["Location must be filled"]
|
500
|
+
form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
|
501
|
+
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"]
|
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
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
validation do
|
515
|
+
config.messages.load_paths << "test/fixtures/dry_new_api_error_messages.yml"
|
516
|
+
|
517
|
+
params do
|
518
|
+
required(:title).filled
|
519
|
+
|
520
|
+
required(:band).schema do
|
521
|
+
required(:name).filled
|
522
|
+
required(:label).schema do
|
523
|
+
required(:location).filled
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
let(:form) { AlbumFormWith1NestedVal.new(album) }
|
531
|
+
|
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
|
+
)
|
539
|
+
|
540
|
+
form.to_result.errors.must_equal(title: ["must be filled"])
|
541
|
+
form.band.to_result.errors.must_equal(name: ["must be filled"])
|
542
|
+
form.band.label.to_result.errors.must_equal(location: ["must be filled"])
|
543
|
+
|
544
|
+
# with locale: "de"
|
545
|
+
form.to_result.errors(locale: :de).must_equal(title: ["muss abgefüllt sein"])
|
546
|
+
form.band.to_result.errors(locale: :de).must_equal(name: ["muss abgefüllt sein"])
|
547
|
+
form.band.label.to_result.errors(locale: :de).must_equal(location: ["muss abgefüllt sein"])
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# describe "same-named group" do
|
553
|
+
# class OverwritingForm < TestForm
|
554
|
+
# include Reform::Form::Dry::Validations
|
555
|
+
|
556
|
+
# property :username
|
557
|
+
# property :email
|
558
|
+
|
559
|
+
# validation :email do # FIX ME: is this working for other validator or just bugging here?
|
560
|
+
# key(:email, &:filled?) # it's not considered, overitten
|
561
|
+
# end
|
562
|
+
|
563
|
+
# validation :email do # just another group.
|
564
|
+
# key(:username, &:filled?)
|
565
|
+
# end
|
566
|
+
# end
|
567
|
+
|
568
|
+
# let(:form) { OverwritingForm.new(Session.new) }
|
569
|
+
|
570
|
+
# # valid.
|
571
|
+
# it do
|
572
|
+
# form.validate({username: "Helloween"}).must_equal true
|
573
|
+
# end
|
574
|
+
|
575
|
+
# # invalid.
|
576
|
+
# it "whoo" do
|
577
|
+
# form.validate({}).must_equal false
|
578
|
+
# form.errors.messages.inspect.must_equal "{:username=>[\"username can't be blank\"]}"
|
579
|
+
# end
|
580
|
+
# end
|
581
|
+
|
582
|
+
describe "inherit: true in same group" do
|
583
|
+
class InheritSameGroupForm < TestForm
|
584
|
+
property :username
|
585
|
+
property :email
|
586
|
+
property :full_name, virtual: true
|
587
|
+
|
588
|
+
validation name: :username do
|
589
|
+
params do
|
590
|
+
required(:username).filled
|
591
|
+
required(:full_name).filled
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
validation name: :username, inherit: true do # extends the above.
|
596
|
+
params do
|
597
|
+
optional(:username).maybe(:string)
|
598
|
+
required(:email).filled
|
599
|
+
end
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
let(:form) { InheritSameGroupForm.new(Session.new) }
|
604
|
+
|
605
|
+
# valid.
|
606
|
+
it do
|
607
|
+
skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
|
608
|
+
form.validate(email: 9).must_equal true
|
609
|
+
end
|
610
|
+
|
611
|
+
# invalid.
|
612
|
+
it do
|
613
|
+
skip "waiting dry-v to add this as feature https://github.com/dry-rb/dry-schema/issues/33"
|
614
|
+
form.validate({}).must_equal false
|
615
|
+
form.errors.messages.must_equal email: ["must be filled"], full_name: ["must be filled"]
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
describe "if: with lambda" do
|
620
|
+
class IfWithLambdaForm < TestForm
|
621
|
+
property :username
|
622
|
+
property :email
|
623
|
+
property :password
|
624
|
+
|
625
|
+
validation name: :email do
|
626
|
+
params { required(:email).filled }
|
627
|
+
end
|
628
|
+
|
629
|
+
# run this is :email group is true.
|
630
|
+
validation name: :after_email, if: ->(results) { results[:email].success? } do # extends the above.
|
631
|
+
params { required(:username).filled }
|
632
|
+
end
|
633
|
+
|
634
|
+
# block gets evaled in form instance context.
|
635
|
+
validation name: :password, if: ->(results) { email == "john@trb.org" } do
|
636
|
+
params { required(:password).filled }
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
let(:form) { IfWithLambdaForm.new(Session.new) }
|
641
|
+
|
642
|
+
# valid.
|
643
|
+
it do
|
644
|
+
form.validate(username: "Strung Out", email: 9).must_equal true
|
645
|
+
end
|
646
|
+
|
647
|
+
# invalid.
|
648
|
+
it do
|
649
|
+
form.validate(email: 9).must_equal false
|
650
|
+
form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
|
651
|
+
end
|
652
|
+
end
|
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
|
+
form.validate(delivery_address: {company: "not int"}).must_equal false
|
675
|
+
form.errors.messages.must_equal(:"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
|
+
form.validate(delivery_address: {company: "not int"}).must_equal false
|
698
|
+
form.errors.messages.must_equal(:"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_new_api_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
|
+
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
|
+
).must_equal false
|
752
|
+
form.songs.size.must_equal 3
|
753
|
+
|
754
|
+
form.errors.messages.must_equal(
|
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
|
+
form.validate(age: "99").must_equal true
|
789
|
+
form.sync
|
790
|
+
model.age.must_equal "99"
|
791
|
+
|
792
|
+
form = ParamsForm.new(Foo.new)
|
793
|
+
form.validate(age: "1000").must_equal false
|
794
|
+
form.errors.messages.must_equal age: ["value exceeded"]
|
795
|
+
end
|
796
|
+
|
797
|
+
it "using schema" do
|
798
|
+
model = Foo.new
|
799
|
+
form = SchemaForm.new(model)
|
800
|
+
form.validate(age: "99").must_equal false
|
801
|
+
form.validate(age: 99).must_equal true
|
802
|
+
form.sync
|
803
|
+
model.age.must_equal 99
|
804
|
+
|
805
|
+
form = SchemaForm.new(Foo.new)
|
806
|
+
form.validate(age: 1000).must_equal false
|
807
|
+
form.errors.messages.must_equal age: ["value exceeded"]
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
# Currenty dry-v don't support that option, it doesn't make sense
|
812
|
+
# I've talked to @solnic and he plans to add a "hint" feature to show
|
813
|
+
# more errors messages than only those that have failed.
|
814
|
+
#
|
815
|
+
# describe "multiple errors for property" do
|
816
|
+
# class MultipleErrorsForPropertyForm < TestForm
|
817
|
+
# include Reform::Form::Dry::Validations
|
818
|
+
|
819
|
+
# property :username
|
820
|
+
|
821
|
+
# validation :default do
|
822
|
+
# key(:username) do |username|
|
823
|
+
# username.filled? | (username.min_size?(2) & username.max_size?(3))
|
824
|
+
# end
|
825
|
+
# end
|
826
|
+
# end
|
827
|
+
|
828
|
+
# let(:form) { MultipleErrorsForPropertyForm.new(Session.new) }
|
829
|
+
|
830
|
+
# # valid.
|
831
|
+
# it do
|
832
|
+
# form.validate({username: ""}).must_equal false
|
833
|
+
# form.errors.messages.inspect.must_equal "{:username=>[\"username must be filled\", \"username is not proper size\"]}"
|
834
|
+
# end
|
835
|
+
# end
|
836
|
+
end
|