reform 2.2.4 → 2.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +13 -7
- data/CHANGES.md +26 -4
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +1 -12
- data/ISSUE_TEMPLATE.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -3
- data/lib/reform.rb +1 -0
- data/lib/reform/contract.rb +1 -11
- data/lib/reform/contract/validate.rb +49 -23
- data/lib/reform/errors.rb +49 -0
- data/lib/reform/form.rb +20 -5
- data/lib/reform/form/dry.rb +57 -29
- data/lib/reform/form/populator.rb +2 -16
- data/lib/reform/form/prepopulate.rb +1 -1
- data/lib/reform/form/validate.rb +10 -2
- data/lib/reform/result.rb +63 -0
- data/lib/reform/validation.rb +19 -13
- data/lib/reform/validation/groups.rb +11 -25
- data/lib/reform/version.rb +1 -1
- data/reform.gemspec +7 -6
- data/test/benchmarking.rb +39 -5
- data/test/call_test.rb +1 -1
- data/test/changed_test.rb +1 -1
- data/test/coercion_test.rb +2 -2
- data/test/composition_test.rb +47 -9
- data/test/contract_test.rb +5 -5
- data/test/default_test.rb +1 -1
- data/test/deserialize_test.rb +3 -3
- data/test/errors_test.rb +36 -21
- data/test/feature_test.rb +1 -1
- data/test/fixtures/dry_error_messages.yml +70 -23
- data/test/form_option_test.rb +3 -3
- data/test/form_test.rb +3 -3
- data/test/from_test.rb +2 -2
- data/test/inherit_test.rb +44 -51
- data/test/module_test.rb +12 -12
- data/test/parse_option_test.rb +40 -0
- data/test/parse_pipeline_test.rb +2 -2
- data/test/populate_test.rb +59 -19
- data/test/populator_skip_test.rb +9 -8
- data/test/prepopulator_test.rb +3 -3
- data/test/readable_test.rb +2 -2
- data/test/readonly_test.rb +1 -1
- data/test/reform_test.rb +16 -31
- data/test/save_test.rb +23 -8
- data/test/setup_test.rb +2 -2
- data/test/skip_if_test.rb +4 -4
- data/test/skip_setter_and_getter_test.rb +1 -1
- data/test/test_helper.rb +13 -10
- data/test/validate_test.rb +18 -18
- data/test/validation/dry_validation_test.rb +430 -117
- data/test/validation/result_test.rb +79 -0
- data/test/validation_library_provided_test.rb +16 -0
- data/test/virtual_test.rb +1 -1
- data/test/writeable_test.rb +31 -2
- metadata +42 -23
- 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/validation/dry_test.rb +0 -60
- data/test/validation/errors.yml +0 -4
data/test/save_test.rb
CHANGED
@@ -5,22 +5,22 @@ class SaveTest < BaseTest
|
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestForm
|
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
|
property :name
|
22
22
|
validation do
|
23
|
-
|
23
|
+
required(:name).filled
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -36,7 +36,7 @@ class SaveTest < BaseTest
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def saved?
|
39
|
-
@saved
|
39
|
+
defined?(@saved) && @saved
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -58,7 +58,22 @@ class SaveTest < BaseTest
|
|
58
58
|
album.saved?.must_equal true
|
59
59
|
album.songs[0].title.must_equal "Fixed"
|
60
60
|
album.songs[0].saved?.must_equal true
|
61
|
-
album.artist.saved
|
61
|
+
assert_nil album.artist.saved?
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#sync with block" do
|
65
|
+
it do
|
66
|
+
form = AlbumForm.new(Album.new("Greatest Hits"))
|
67
|
+
|
68
|
+
form.validate(name: nil) # nil-out the title.
|
69
|
+
|
70
|
+
nested_hash = nil
|
71
|
+
form.sync do |hash|
|
72
|
+
nested_hash = hash
|
73
|
+
end
|
74
|
+
|
75
|
+
nested_hash.must_equal({"name"=>nil, "artist"=>nil})
|
76
|
+
end
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
@@ -68,7 +83,7 @@ end
|
|
68
83
|
# include Saveable
|
69
84
|
# end
|
70
85
|
|
71
|
-
# class SongForm <
|
86
|
+
# class SongForm < TestForm
|
72
87
|
# property :title#, save: false
|
73
88
|
# property :length, virtual: true
|
74
89
|
# end
|
@@ -83,7 +98,7 @@ end
|
|
83
98
|
# form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
|
84
99
|
|
85
100
|
# song.title.must_equal "A Poor Man's Memory"
|
86
|
-
# song.length
|
101
|
+
# assert_nil song.length
|
87
102
|
# song.id.must_equal "10: 120"
|
88
103
|
# end
|
89
104
|
# end
|
data/test/setup_test.rb
CHANGED
@@ -5,7 +5,7 @@ class SetupTest < MiniTest::Spec
|
|
5
5
|
Album = Struct.new(:name, :songs, :artist)
|
6
6
|
Artist = Struct.new(:name)
|
7
7
|
|
8
|
-
class AlbumForm <
|
8
|
+
class AlbumForm < TestForm
|
9
9
|
property :name
|
10
10
|
collection :songs do
|
11
11
|
property :title
|
@@ -33,7 +33,7 @@ class SetupTest < MiniTest::Spec
|
|
33
33
|
|
34
34
|
form.name.must_equal "The Dissent Of Man"
|
35
35
|
form.songs[0].title.must_equal "Broken"
|
36
|
-
form.songs[0].composer
|
36
|
+
assert_nil form.songs[0].composer
|
37
37
|
form.songs[1].title.must_equal "Resist Stance"
|
38
38
|
form.songs[1].composer.name.must_equal "Greg Graffin"
|
39
39
|
form.artist.name.must_equal "Bad Religion"
|
data/test/skip_if_test.rb
CHANGED
@@ -2,13 +2,13 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class SkipIfTest < BaseTest
|
4
4
|
|
5
|
-
class AlbumForm <
|
5
|
+
class AlbumForm < TestForm
|
6
6
|
property :title
|
7
7
|
|
8
8
|
property :hit, skip_if: lambda { |options| options[:fragment]["title"]=="" } do
|
9
9
|
property :title
|
10
10
|
validation do
|
11
|
-
|
11
|
+
required(:title).filled
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -36,7 +36,7 @@ class SkipIfTest < BaseTest
|
|
36
36
|
it do
|
37
37
|
form = AlbumForm.new(Album.new)
|
38
38
|
form.validate("hit" => {"title" => ""}).must_equal true
|
39
|
-
form.hit
|
39
|
+
assert_nil form.hit # hit hasn't been deserialised.
|
40
40
|
end
|
41
41
|
|
42
42
|
# skips deserialization when not present.
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
|
51
51
|
class SkipIfAllBlankTest < BaseTest
|
52
52
|
# skip_if: :all_blank"
|
53
|
-
class AlbumForm <
|
53
|
+
class AlbumForm < TestForm
|
54
54
|
collection :songs, skip_if: :all_blank, populate_if_empty: BaseTest::Song do
|
55
55
|
property :title
|
56
56
|
property :length
|
data/test/test_helper.rb
CHANGED
@@ -3,9 +3,20 @@ require 'minitest/autorun'
|
|
3
3
|
require "representable/debug"
|
4
4
|
require "declarative/testing"
|
5
5
|
require "pp"
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
require "reform/form/dry"
|
9
|
+
# setup test classes so we can test without dry being included
|
10
|
+
class TestForm < Reform::Form
|
11
|
+
feature Reform::Form::Dry
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestContract < Reform::Contract
|
15
|
+
feature Reform::Form::Dry
|
16
|
+
end
|
6
17
|
|
7
18
|
class BaseTest < MiniTest::Spec
|
8
|
-
class AlbumForm <
|
19
|
+
class AlbumForm < TestForm
|
9
20
|
property :title
|
10
21
|
|
11
22
|
property :hit do
|
@@ -17,7 +28,7 @@ class BaseTest < MiniTest::Spec
|
|
17
28
|
end
|
18
29
|
end
|
19
30
|
|
20
|
-
Song = Struct.new(:title, :length)
|
31
|
+
Song = Struct.new(:title, :length, :rating)
|
21
32
|
Album = Struct.new(:title, :hit, :songs, :band)
|
22
33
|
Band = Struct.new(:label)
|
23
34
|
Label = Struct.new(:name)
|
@@ -39,11 +50,3 @@ MiniTest::Spec.class_eval do
|
|
39
50
|
end
|
40
51
|
end
|
41
52
|
|
42
|
-
require "reform/form/dry"
|
43
|
-
Reform::Contract.class_eval do
|
44
|
-
feature Reform::Form::Dry
|
45
|
-
end
|
46
|
-
# FIXME!
|
47
|
-
Reform::Form.class_eval do
|
48
|
-
feature Reform::Form::Dry
|
49
|
-
end
|
data/test/validate_test.rb
CHANGED
@@ -5,21 +5,21 @@ class ContractValidateTest < MiniTest::Spec
|
|
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
|
@@ -50,7 +50,7 @@ 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
|
|
@@ -61,23 +61,23 @@ class ValidateWithoutConfigurationTest < MiniTest::Spec
|
|
61
61
|
Album = Struct.new(:name, :songs, :artist)
|
62
62
|
Artist = Struct.new(:name)
|
63
63
|
|
64
|
-
class AlbumForm <
|
64
|
+
class AlbumForm < TestForm
|
65
65
|
property :name
|
66
66
|
validation do
|
67
|
-
|
67
|
+
required(:name).filled
|
68
68
|
end
|
69
69
|
|
70
70
|
collection :songs do
|
71
71
|
|
72
72
|
property :title
|
73
73
|
validation do
|
74
|
-
|
74
|
+
required(:title).filled
|
75
75
|
end
|
76
76
|
|
77
77
|
property :composer do
|
78
78
|
property :name
|
79
79
|
validation do
|
80
|
-
|
80
|
+
required(:name).filled
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
@@ -160,10 +160,10 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
160
160
|
Album = Struct.new(:name, :songs, :artist)
|
161
161
|
Artist = Struct.new(:name)
|
162
162
|
|
163
|
-
class AlbumForm <
|
163
|
+
class AlbumForm < TestForm
|
164
164
|
property :name
|
165
165
|
validation do
|
166
|
-
|
166
|
+
required(:name).filled
|
167
167
|
end
|
168
168
|
|
169
169
|
collection :songs,
|
@@ -173,13 +173,13 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
173
173
|
|
174
174
|
property :title
|
175
175
|
validation do
|
176
|
-
|
176
|
+
required(:title).filled
|
177
177
|
end
|
178
178
|
|
179
179
|
property :composer, internal_populator: lambda { |input, options| (item = options[:represented].composer) ? item : Artist.new } do
|
180
180
|
property :name
|
181
181
|
validation do
|
182
|
-
|
182
|
+
required(:name).filled
|
183
183
|
end
|
184
184
|
end
|
185
185
|
end
|
@@ -187,7 +187,7 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
187
187
|
property :artist, internal_populator: lambda { |input, options| (item = options[:represented].artist) ? item : Artist.new } do
|
188
188
|
property :name
|
189
189
|
validation do
|
190
|
-
|
190
|
+
required(:name).filled
|
191
191
|
end
|
192
192
|
end
|
193
193
|
end
|
@@ -268,21 +268,21 @@ class ValidateWithInternalPopulatorOptionTest < MiniTest::Spec
|
|
268
268
|
|
269
269
|
|
270
270
|
# allow writeable: false even in the deserializer.
|
271
|
-
class SongForm <
|
272
|
-
property :title, deserializer: {writeable: false}
|
271
|
+
class SongForm < TestForm
|
272
|
+
property :title, deserializer: { writeable: false }
|
273
273
|
end
|
274
274
|
|
275
275
|
it do
|
276
276
|
form = SongForm.new(song = Song.new)
|
277
277
|
form.validate("title" => "Ignore me!")
|
278
|
-
form.title
|
278
|
+
assert_nil form.title
|
279
279
|
form.title = "Unopened"
|
280
280
|
form.sync # only the deserializer is marked as not-writeable.
|
281
281
|
song.title.must_equal "Unopened"
|
282
282
|
end
|
283
283
|
end
|
284
284
|
|
285
|
-
# # not sure if we should catch that in Reform or rather do that in disposable. this is https://github.com/
|
285
|
+
# # 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
286
|
# # describe ":populator with :empty" do
|
287
287
|
# # let (:form) {
|
288
288
|
# # Class.new(Reform::Form) do
|
@@ -1,20 +1,208 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require "test_helper"
|
2
3
|
require "reform/form/dry"
|
4
|
+
require "reform/form/coercion"
|
5
|
+
|
6
|
+
#---
|
7
|
+
# one "nested" Schema per form.
|
8
|
+
class DryValidationErrorsAPITest < Minitest::Spec
|
9
|
+
Album = Struct.new(:title, :artist, :songs)
|
10
|
+
Song = Struct.new(:title)
|
11
|
+
Artist = Struct.new(:email, :label)
|
12
|
+
Label = Struct.new(:location)
|
13
|
+
|
14
|
+
class AlbumForm < TestForm
|
15
|
+
property :title
|
16
|
+
|
17
|
+
validation do
|
18
|
+
# required(:title).filled
|
19
|
+
required(:title).filled(min_size?: 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
property :artist do
|
23
|
+
property :email
|
24
|
+
|
25
|
+
validation do
|
26
|
+
required(:email).filled
|
27
|
+
end
|
28
|
+
|
29
|
+
property :label do
|
30
|
+
property :location
|
31
|
+
|
32
|
+
validation do
|
33
|
+
required(:location).filled
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# note the validation block is *in* the collection block, per item, so to speak.
|
39
|
+
collection :songs do
|
40
|
+
property :title
|
41
|
+
|
42
|
+
validation do
|
43
|
+
configure do
|
44
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
45
|
+
end
|
46
|
+
|
47
|
+
required(:title).filled
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
let (:form) { AlbumForm.new(Album.new(nil, Artist.new(nil, Label.new), [Song.new(nil), Song.new(nil)])) }
|
54
|
+
|
55
|
+
it "everything wrong" do
|
56
|
+
result = form.({ title: nil, artist: { email: "" }, songs: [{ title: "Clams have feelings too" }, { title: "" }] })
|
57
|
+
|
58
|
+
result.success?.must_equal false
|
59
|
+
|
60
|
+
# errors.messages
|
61
|
+
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"]})
|
62
|
+
form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
|
63
|
+
form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
|
64
|
+
form.songs[0].errors.messages.must_equal({})
|
65
|
+
form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
|
66
|
+
|
67
|
+
# #errors[]
|
68
|
+
form.errors[:nonsense].must_be_nil
|
69
|
+
form.errors[:title].must_equal ["must be filled", "size cannot be less than 2"]
|
70
|
+
form.artist.errors[:email].must_equal ["must be filled"]
|
71
|
+
form.artist.label.errors[:location].must_equal ["must be filled"]
|
72
|
+
form.songs[0].errors[:title].must_be_nil
|
73
|
+
form.songs[1].errors[:title].must_equal ["must be filled"]
|
74
|
+
|
75
|
+
# #to_result
|
76
|
+
form.to_result.errors.must_equal({:title=>["must be filled"]})
|
77
|
+
form.to_result.messages.must_equal({:title=>["must be filled", "size cannot be less than 2"]})
|
78
|
+
form.to_result.hints.must_equal({:title=>["size cannot be less than 2"]})
|
79
|
+
form.artist.to_result.errors.must_equal({:email=>["must be filled"]})
|
80
|
+
form.artist.to_result.messages.must_equal({:email=>["must be filled"]})
|
81
|
+
form.artist.to_result.hints.must_equal({:email=>[]})
|
82
|
+
form.artist.label.to_result.errors.must_equal({:location=>["must be filled"]})
|
83
|
+
form.artist.label.to_result.messages.must_equal({:location=>["must be filled"]})
|
84
|
+
form.artist.label.to_result.hints.must_equal({:location=>[]})
|
85
|
+
form.songs[0].to_result.errors.must_equal({})
|
86
|
+
form.songs[0].to_result.messages.must_equal({})
|
87
|
+
form.songs[0].to_result.hints.must_equal({})
|
88
|
+
form.songs[1].to_result.errors.must_equal({:title=>["must be filled"]})
|
89
|
+
form.songs[1].to_result.messages.must_equal({:title=>["must be filled"]})
|
90
|
+
form.songs[1].to_result.hints.must_equal({:title=>[]})
|
91
|
+
form.songs[1].to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
92
|
+
form.songs[1].to_result.messages(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
93
|
+
form.songs[1].to_result.hints(locale: :de).must_equal({:title=>[]})
|
94
|
+
end
|
95
|
+
|
96
|
+
it "only nested property is invalid." do
|
97
|
+
result = form.({ title: "Black Star", artist: { email: "" } })
|
98
|
+
|
99
|
+
result.success?.must_equal false
|
100
|
+
|
101
|
+
# errors.messages
|
102
|
+
form.errors.messages.must_equal({:"artist.email"=>["must be filled"], :"artist.label.location"=>["must be filled"], :"songs.title"=>["must be filled"]})
|
103
|
+
form.artist.errors.messages.must_equal({:email=>["must be filled"], :"label.location"=>["must be filled"]})
|
104
|
+
form.artist.label.errors.messages.must_equal({:location=>["must be filled"]})
|
105
|
+
end
|
106
|
+
|
107
|
+
it "nested collection invalid" do
|
108
|
+
result = form.({ title: "Black Star", artist: { email: "uhm", label: { location: "Hannover" } }, songs: [ { title: "" } ] })
|
109
|
+
|
110
|
+
result.success?.must_equal false
|
111
|
+
form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
|
112
|
+
end
|
113
|
+
|
114
|
+
#---
|
115
|
+
#- validation .each
|
116
|
+
class CollectionExternalValidationsForm < TestForm
|
117
|
+
collection :songs do
|
118
|
+
property :title
|
119
|
+
end
|
120
|
+
|
121
|
+
validation do
|
122
|
+
required(:songs).each do
|
123
|
+
schema do
|
124
|
+
required(:title).filled
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it do
|
131
|
+
form = CollectionExternalValidationsForm.new(Album.new(nil, nil, [Song.new, Song.new]))
|
132
|
+
form.validate(songs: [ { title: "Liar"}, { title: ""} ])
|
133
|
+
|
134
|
+
form.errors.messages.must_equal({:"songs.title"=>["must be filled"]})
|
135
|
+
form.songs[0].errors.messages.must_equal({})
|
136
|
+
form.songs[1].errors.messages.must_equal({:title=>["must be filled"]})
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class DryValidationExplicitSchemaTest < Minitest::Spec
|
141
|
+
Session = Struct.new(:name, :email)
|
142
|
+
SessionSchema = Dry::Validation.Schema do
|
143
|
+
required(:name).filled
|
144
|
+
required(:email).filled
|
145
|
+
end
|
146
|
+
|
147
|
+
class SessionForm < TestForm
|
148
|
+
include Coercion
|
149
|
+
|
150
|
+
property :name
|
151
|
+
property :email
|
152
|
+
|
153
|
+
validation schema: SessionSchema
|
154
|
+
end
|
155
|
+
|
156
|
+
let (:form) { SessionForm.new(Session.new) }
|
157
|
+
|
158
|
+
# valid.
|
159
|
+
it do
|
160
|
+
form.validate(name: "Helloween", email: "yep").must_equal true
|
161
|
+
form.errors.messages.inspect.must_equal "{}"
|
162
|
+
end
|
163
|
+
|
164
|
+
it "invalid" do
|
165
|
+
form.validate(name: "", email: "yep").must_equal false
|
166
|
+
form.errors.messages.inspect.must_equal "{:name=>[\"must be filled\"]}"
|
167
|
+
end
|
168
|
+
end
|
3
169
|
|
4
170
|
class DryValidationDefaultGroupTest < Minitest::Spec
|
5
|
-
Session = Struct.new(:username, :email, :password, :confirm_password)
|
171
|
+
Session = Struct.new(:username, :email, :password, :confirm_password, :starts_at, :active, :color)
|
6
172
|
|
7
|
-
class SessionForm <
|
8
|
-
include
|
173
|
+
class SessionForm < TestForm
|
174
|
+
include Coercion
|
9
175
|
|
10
176
|
property :username
|
11
177
|
property :email
|
12
178
|
property :password
|
13
179
|
property :confirm_password
|
180
|
+
property :starts_at, type: Types::Form::DateTime
|
181
|
+
property :active, type: Types::Form::Bool
|
182
|
+
property :color
|
14
183
|
|
15
184
|
validation do
|
16
|
-
|
17
|
-
|
185
|
+
required(:username).filled
|
186
|
+
required(:email).filled
|
187
|
+
required(:starts_at).filled(:date_time?)
|
188
|
+
required(:active).filled(:bool?)
|
189
|
+
end
|
190
|
+
|
191
|
+
validation name: :another_block do
|
192
|
+
required(:confirm_password).filled
|
193
|
+
end
|
194
|
+
|
195
|
+
validation name: :dynamic_args, with: { form: true } do
|
196
|
+
configure do
|
197
|
+
def colors
|
198
|
+
form.colors
|
199
|
+
end
|
200
|
+
end
|
201
|
+
required(:color).maybe(included_in?: colors)
|
202
|
+
end
|
203
|
+
|
204
|
+
def colors
|
205
|
+
%(red orange green)
|
18
206
|
end
|
19
207
|
end
|
20
208
|
|
@@ -23,38 +211,52 @@ class DryValidationDefaultGroupTest < Minitest::Spec
|
|
23
211
|
# valid.
|
24
212
|
it do
|
25
213
|
form.validate(username: "Helloween",
|
26
|
-
email: "yep"
|
214
|
+
email: "yep",
|
215
|
+
starts_at: "01/01/2000 - 11:00",
|
216
|
+
active: "true",
|
217
|
+
confirm_password: 'pA55w0rd').must_equal true
|
27
218
|
form.errors.messages.inspect.must_equal "{}"
|
28
219
|
end
|
220
|
+
|
221
|
+
it "invalid" do
|
222
|
+
form.validate(username: "Helloween",
|
223
|
+
email: "yep",
|
224
|
+
active: 'hello',
|
225
|
+
starts_at: "01/01/2000 - 11:00",
|
226
|
+
color: 'purple').must_equal false
|
227
|
+
form.errors.messages.inspect.must_equal "{:active=>[\"must be boolean\"], :confirm_password=>[\"must be filled\"], :color=>[\"must be one of: red orange green\"]}"
|
228
|
+
end
|
29
229
|
end
|
30
230
|
|
31
231
|
class ValidationGroupsTest < MiniTest::Spec
|
32
232
|
describe "basic validations" do
|
33
|
-
Session = Struct.new(:username, :email, :password, :confirm_password)
|
233
|
+
Session = Struct.new(:username, :email, :password, :confirm_password, :special_class)
|
234
|
+
SomeClass= Struct.new(:id)
|
34
235
|
|
35
|
-
class SessionForm <
|
36
|
-
include Reform::Form::Dry::Validations
|
236
|
+
class SessionForm < TestForm
|
37
237
|
|
38
238
|
property :username
|
39
239
|
property :email
|
40
240
|
property :password
|
41
241
|
property :confirm_password
|
242
|
+
property :special_class
|
42
243
|
|
43
|
-
validation
|
44
|
-
|
45
|
-
|
244
|
+
validation do
|
245
|
+
required(:username).filled
|
246
|
+
required(:email).filled
|
247
|
+
required(:special_class).filled(type?: SomeClass)
|
46
248
|
end
|
47
249
|
|
48
|
-
validation :email, if: :default do
|
49
|
-
|
250
|
+
validation name: :email, if: :default do
|
251
|
+
required(:email).filled(min_size?: 3)
|
50
252
|
end
|
51
253
|
|
52
|
-
validation :nested, if: :default do
|
53
|
-
|
254
|
+
validation name: :nested, if: :default do
|
255
|
+
required(:password).filled(min_size?: 2)
|
54
256
|
end
|
55
257
|
|
56
|
-
validation :confirm, if: :default, after: :email do
|
57
|
-
|
258
|
+
validation name: :confirm, if: :default, after: :email do
|
259
|
+
required(:confirm_password).filled(min_size?: 2)
|
58
260
|
end
|
59
261
|
end
|
60
262
|
|
@@ -63,6 +265,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
63
265
|
# valid.
|
64
266
|
it do
|
65
267
|
form.validate({ username: "Helloween",
|
268
|
+
special_class: SomeClass.new(id: 15),
|
66
269
|
email: "yep",
|
67
270
|
password: "99",
|
68
271
|
confirm_password: "99" }).must_equal true
|
@@ -72,160 +275,274 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
72
275
|
# invalid.
|
73
276
|
it do
|
74
277
|
form.validate({}).must_equal false
|
75
|
-
form.errors.messages.
|
278
|
+
form.errors.messages.must_equal({:username=>["must be filled"], :email=>["must be filled"], :special_class=>["must be filled", "must be ValidationGroupsTest::SomeClass"]})
|
76
279
|
end
|
77
280
|
|
78
281
|
# partially invalid.
|
79
282
|
# 2nd group fails.
|
80
283
|
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=>[\"
|
284
|
+
form.validate(username: "Helloween", email: "yo", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
|
285
|
+
form.errors.messages.inspect.must_equal "{:email=>[\"size cannot be less than 3\"], :confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
|
83
286
|
end
|
84
287
|
# 3rd group fails.
|
85
288
|
it do
|
86
|
-
form.validate(username: "Helloween", email: "yo!", confirm_password:"9").must_equal false
|
289
|
+
form.validate(username: "Helloween", email: "yo!", confirm_password:"9", special_class: SomeClass.new(id: 15)).must_equal false
|
87
290
|
form.errors.messages.inspect
|
88
|
-
.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"
|
291
|
+
.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"must be filled\", \"size cannot be less than 2\"]}"
|
89
292
|
end
|
90
293
|
# 4th group with after: fails.
|
91
294
|
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=>[\"
|
295
|
+
form.validate(username: "Helloween", email: "yo!", password: "1", confirm_password: "9", special_class: SomeClass.new(id: 15)).must_equal false
|
296
|
+
form.errors.messages.inspect.must_equal "{:confirm_password=>[\"size cannot be less than 2\"], :password=>[\"size cannot be less than 2\"]}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class ValidationWithOptionsTest < MiniTest::Spec
|
301
|
+
describe "basic validations" do
|
302
|
+
Session = Struct.new(:username)
|
303
|
+
class SessionForm < TestForm
|
304
|
+
property :username
|
305
|
+
|
306
|
+
validation name: :default, with: { user: OpenStruct.new(name: "Nick") } do
|
307
|
+
configure do
|
308
|
+
def users_name
|
309
|
+
user.name
|
310
|
+
end
|
311
|
+
end
|
312
|
+
required(:username).filled(eql?: users_name)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
let (:form) { SessionForm.new(Session.new) }
|
317
|
+
|
318
|
+
# valid.
|
319
|
+
it do
|
320
|
+
form.validate({ username: "Nick" }).must_equal true
|
321
|
+
form.errors.messages.inspect.must_equal "{}"
|
322
|
+
end
|
323
|
+
|
324
|
+
# invalid.
|
325
|
+
it do
|
326
|
+
form.validate({ username: 'Fred'}).must_equal false
|
327
|
+
form.errors.messages.inspect.must_equal "{:username=>[\"must be equal to Nick\"]}"
|
328
|
+
end
|
94
329
|
end
|
95
330
|
end
|
96
331
|
|
97
|
-
|
98
|
-
|
99
|
-
|
332
|
+
#---
|
333
|
+
#- validation( schema: MySchema )
|
334
|
+
describe "with custom schema" do
|
335
|
+
Session2 = Struct.new(:username, :email, :password)
|
336
|
+
|
337
|
+
MySchema = Dry::Validation.Schema do
|
338
|
+
configure do
|
339
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
340
|
+
|
341
|
+
def good_musical_taste?(val)
|
342
|
+
val.is_a? String
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
required(:password).filled(:min_size? => 6)
|
348
|
+
end
|
349
|
+
|
350
|
+
class Session2Form < TestForm
|
351
|
+
property :username
|
352
|
+
property :email
|
353
|
+
property :password
|
100
354
|
|
355
|
+
validation schema: MySchema do
|
356
|
+
required(:username).filled
|
357
|
+
required(:email).filled(:good_musical_taste?)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
let (:form) { Session2Form.new(Session2.new) }
|
362
|
+
|
363
|
+
# valid.
|
364
|
+
it do
|
365
|
+
form.validate({ username: "Helloween", email: "yep", password: "extrasafe" }).must_equal true
|
366
|
+
form.errors.messages.inspect.must_equal "{}"
|
367
|
+
end
|
368
|
+
|
369
|
+
# invalid.
|
370
|
+
it do
|
371
|
+
form.validate({}).must_equal false
|
372
|
+
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"]})
|
373
|
+
end
|
374
|
+
|
375
|
+
it do
|
376
|
+
form.validate({email: 1}).must_equal false
|
377
|
+
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\"]}"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
describe "MIXED nested validations" do
|
382
|
+
class AlbumForm < TestForm
|
101
383
|
property :title
|
102
384
|
|
103
385
|
property :hit do
|
104
386
|
property :title
|
105
387
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
# key(:title, &:filled?)
|
110
|
-
# end
|
388
|
+
validation do
|
389
|
+
required(:title).filled
|
390
|
+
end
|
111
391
|
end
|
112
392
|
|
113
393
|
collection :songs do
|
114
394
|
property :title
|
395
|
+
|
396
|
+
validation do
|
397
|
+
required(:title).filled
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# we test this one by running an each / schema dry-v check on the main block
|
402
|
+
collection :producers do
|
403
|
+
property :name
|
115
404
|
end
|
116
405
|
|
117
406
|
property :band do
|
118
407
|
property :name
|
119
408
|
property :label do
|
120
|
-
property :
|
409
|
+
property :location
|
121
410
|
end
|
122
411
|
end
|
123
412
|
|
124
|
-
validation
|
125
|
-
|
126
|
-
key(:title).required
|
127
|
-
|
128
|
-
key(:band).schema do
|
129
|
-
key(:name).required
|
130
|
-
key(:label).schema do
|
131
|
-
key(:name).required
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
413
|
+
validation do
|
135
414
|
configure do
|
136
|
-
|
415
|
+
config.messages_file = "test/fixtures/dry_error_messages.yml"
|
137
416
|
# message need to be defined on fixtures/dry_error_messages
|
138
417
|
# d-v expects you to define your custome messages on the .yml file
|
139
418
|
def good_musical_taste?(value)
|
140
419
|
value != 'Nickelback'
|
141
420
|
end
|
421
|
+
end
|
422
|
+
|
423
|
+
required(:title).filled(:good_musical_taste?)
|
142
424
|
|
143
|
-
|
144
|
-
|
425
|
+
required(:band).schema do
|
426
|
+
required(:name).filled
|
427
|
+
required(:label).schema do
|
428
|
+
required(:location).filled
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
required(:producers).each do
|
433
|
+
schema do
|
434
|
+
required(:name).filled
|
145
435
|
end
|
146
436
|
end
|
147
437
|
|
148
|
-
key(:title).required(:good_musical_taste?)
|
149
|
-
key(:title).required(:form_access_validation?)
|
150
438
|
end
|
151
439
|
end
|
152
440
|
|
153
441
|
let (:album) do
|
154
442
|
OpenStruct.new(
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
443
|
+
:hit => OpenStruct.new,
|
444
|
+
:songs => [OpenStruct.new, OpenStruct.new],
|
445
|
+
:band => Struct.new(:name, :label).new("", OpenStruct.new),
|
446
|
+
:producers => [OpenStruct.new, OpenStruct.new, OpenStruct.new],
|
159
447
|
)
|
160
448
|
end
|
161
|
-
|
162
|
-
let (:songs) { [ song = OpenStruct.new(:title => "Calling"), song ] }
|
449
|
+
|
163
450
|
let (:form) { AlbumForm.new(album) }
|
164
451
|
|
165
|
-
|
166
|
-
it do
|
452
|
+
it "maps errors to form objects correctly" do
|
167
453
|
result = form.validate(
|
168
|
-
"title" => "
|
169
|
-
"songs" => [
|
170
|
-
|
171
|
-
|
172
|
-
],
|
173
|
-
"band" => {"label" => {"name" => "Epitaph"}},
|
454
|
+
"title" => "Nickelback",
|
455
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
456
|
+
"band" => {"size" => "", "label" => {"location" => ""}},
|
457
|
+
"producers" => [{"name" => ''}, {"name" => 'something lovely'}]
|
174
458
|
)
|
175
459
|
|
176
|
-
result.must_equal
|
177
|
-
|
178
|
-
|
179
|
-
|
460
|
+
result.must_equal false
|
461
|
+
# from nested validation
|
462
|
+
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"]})
|
463
|
+
|
464
|
+
# songs have their own validation.
|
465
|
+
form.songs[0].errors.messages.must_equal({:title=>["must be filled"]})
|
466
|
+
# hit got its own validation group.
|
467
|
+
form.hit.errors.messages.must_equal({:title=>["must be filled"]})
|
180
468
|
|
469
|
+
form.band.label.errors.messages.must_equal({:location=>["must be filled"]})
|
470
|
+
form.band.errors.messages.must_equal({:name=>["must be filled"], :"label.location"=>["must be filled"]})
|
471
|
+
form.producers[0].errors.messages.must_equal({:name=>["must be filled"]})
|
472
|
+
|
473
|
+
# TODO: use the same form structure as the top one and do the same test against messages, errors and hints.
|
474
|
+
form.producers[0].to_result.errors.must_equal({:name=>["must be filled"]})
|
475
|
+
form.producers[0].to_result.messages.must_equal({:name=>["must be filled"]})
|
476
|
+
form.producers[0].to_result.hints.must_equal({:name=>[]})
|
477
|
+
end
|
181
478
|
|
182
|
-
|
479
|
+
# FIXME: fix the "must be filled error"
|
183
480
|
|
184
|
-
it "
|
185
|
-
|
186
|
-
|
187
|
-
|
481
|
+
it "renders full messages correctly" do
|
482
|
+
result = form.validate(
|
483
|
+
"title" => "",
|
484
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
485
|
+
"band" => {"size" => "", "label" => {"name" => ""}},
|
486
|
+
"producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
|
487
|
+
)
|
188
488
|
|
189
|
-
|
489
|
+
result.must_equal false
|
490
|
+
form.band.errors.full_messages.must_equal ["Name must be filled", "Label Location must be filled"]
|
491
|
+
form.band.label.errors.full_messages.must_equal ["Location must be filled"]
|
492
|
+
form.producers.first.errors.full_messages.must_equal ["Name must be filled"]
|
493
|
+
form.errors.full_messages.must_equal ["Title must be filled", "Title you're a bad person", "Hit Title must be filled", "Songs Title must be filled", "Producers Name must be filled", "Band Name must be filled", "Band Label Location must be filled"]
|
494
|
+
end
|
190
495
|
|
191
|
-
|
192
|
-
|
496
|
+
describe "only 1 nested validation" do
|
497
|
+
class AlbumFormWith1NestedVal < TestForm
|
498
|
+
property :title
|
499
|
+
property :band do
|
500
|
+
property :name
|
501
|
+
property :label do
|
502
|
+
property :location
|
193
503
|
end
|
194
504
|
end
|
195
|
-
end.must_raise(NoMethodError)
|
196
|
-
# e.message.must_equal 'validates() is not supported by Dry Validation backend.'
|
197
505
|
|
198
|
-
|
199
|
-
|
200
|
-
|
506
|
+
validation do
|
507
|
+
configure do
|
508
|
+
config.messages_file = 'test/fixtures/dry_error_messages.yml'
|
509
|
+
end
|
201
510
|
|
202
|
-
|
511
|
+
required(:title).filled
|
203
512
|
|
204
|
-
|
205
|
-
|
513
|
+
required(:band).schema do
|
514
|
+
required(:name).filled
|
515
|
+
required(:label).schema do
|
516
|
+
required(:location).filled
|
517
|
+
end
|
206
518
|
end
|
207
519
|
end
|
208
|
-
end
|
209
|
-
# e.message.must_equal 'validate() is not supported by Dry Validation backend.'
|
520
|
+
end
|
210
521
|
|
211
|
-
|
212
|
-
class FailingForm < Reform::Form
|
213
|
-
include Reform::Form::Dry::Validations
|
522
|
+
let (:form) { AlbumFormWith1NestedVal.new(album) }
|
214
523
|
|
215
|
-
|
524
|
+
it "allows to access dry's result semantics per nested form" do
|
525
|
+
result = form.validate(
|
526
|
+
"title" => "",
|
527
|
+
"songs" => [ {"title" => ""}, {"title" => ""} ],
|
528
|
+
"band" => {"size" => "", "label" => {"name" => ""}},
|
529
|
+
"producers" => [{"name" => ''}, {"name" => ''}, {"name" => 'something lovely'}]
|
530
|
+
)
|
216
531
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
532
|
+
form.to_result.errors.must_equal({:title=>["must be filled"]})
|
533
|
+
form.band.to_result.errors.must_equal({:name=>["must be filled"]})
|
534
|
+
form.band.label.to_result.errors.must_equal({:location=>["must be filled"]})
|
535
|
+
|
536
|
+
# with locale: "de"
|
537
|
+
form.to_result.errors(locale: :de).must_equal({:title=>["muss abgefüllt sein"]})
|
538
|
+
form.band.to_result.errors(locale: :de).must_equal({:name=>["muss abgefüllt sein"]})
|
539
|
+
form.band.label.to_result.errors(locale: :de).must_equal({:location=>["muss abgefüllt sein"]})
|
540
|
+
end
|
223
541
|
end
|
224
542
|
end
|
225
543
|
|
226
|
-
|
227
544
|
# describe "same-named group" do
|
228
|
-
# class OverwritingForm <
|
545
|
+
# class OverwritingForm < TestForm
|
229
546
|
# include Reform::Form::Dry::Validations
|
230
547
|
|
231
548
|
# property :username
|
@@ -256,18 +573,16 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
256
573
|
|
257
574
|
|
258
575
|
describe "inherit: true in same group" do
|
259
|
-
class InheritSameGroupForm <
|
260
|
-
include Reform::Form::Dry::Validations
|
261
|
-
|
576
|
+
class InheritSameGroupForm < TestForm
|
262
577
|
property :username
|
263
578
|
property :email
|
264
579
|
|
265
|
-
validation :email do
|
266
|
-
|
580
|
+
validation name: :email do
|
581
|
+
required(:email).filled
|
267
582
|
end
|
268
583
|
|
269
|
-
validation :email, inherit: true do # extends the above.
|
270
|
-
|
584
|
+
validation name: :email, inherit: true do # extends the above.
|
585
|
+
required(:username).filled
|
271
586
|
end
|
272
587
|
end
|
273
588
|
|
@@ -281,31 +596,29 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
281
596
|
# invalid.
|
282
597
|
it do
|
283
598
|
form.validate({}).must_equal false
|
284
|
-
form.errors.messages.inspect.must_equal "{:email=>[\"
|
599
|
+
form.errors.messages.inspect.must_equal "{:email=>[\"must be filled\"], :username=>[\"must be filled\"]}"
|
285
600
|
end
|
286
601
|
end
|
287
602
|
|
288
603
|
|
289
604
|
describe "if: with lambda" do
|
290
|
-
class IfWithLambdaForm <
|
291
|
-
include Reform::Form::Dry::Validations # ::build_errors.
|
292
|
-
|
605
|
+
class IfWithLambdaForm < TestForm
|
293
606
|
property :username
|
294
607
|
property :email
|
295
608
|
property :password
|
296
609
|
|
297
|
-
validation :email do
|
298
|
-
|
610
|
+
validation name: :email do
|
611
|
+
required(:email).filled
|
299
612
|
end
|
300
613
|
|
301
614
|
# run this is :email group is true.
|
302
|
-
validation :after_email, if: lambda { |results| results[:email]
|
303
|
-
|
615
|
+
validation name: :after_email, if: lambda { |results| results[:email].success? } do # extends the above.
|
616
|
+
required(:username).filled
|
304
617
|
end
|
305
618
|
|
306
619
|
# block gets evaled in form instance context.
|
307
|
-
validation :password, if: lambda { |results| email == "john@trb.org" } do
|
308
|
-
|
620
|
+
validation name: :password, if: lambda { |results| email == "john@trb.org" } do
|
621
|
+
required(:password).filled
|
309
622
|
end
|
310
623
|
end
|
311
624
|
|
@@ -319,7 +632,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
319
632
|
# invalid.
|
320
633
|
it do
|
321
634
|
form.validate({email: 9}).must_equal false
|
322
|
-
form.errors.messages.inspect.must_equal "{:username=>[\"
|
635
|
+
form.errors.messages.inspect.must_equal "{:username=>[\"must be filled\"]}"
|
323
636
|
end
|
324
637
|
end
|
325
638
|
|
@@ -329,7 +642,7 @@ class ValidationGroupsTest < MiniTest::Spec
|
|
329
642
|
# more errors messages than only those that have failed.
|
330
643
|
#
|
331
644
|
# describe "multiple errors for property" do
|
332
|
-
# class MultipleErrorsForPropertyForm <
|
645
|
+
# class MultipleErrorsForPropertyForm < TestForm
|
333
646
|
# include Reform::Form::Dry::Validations
|
334
647
|
|
335
648
|
# property :username
|