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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -7
  3. data/CHANGES.md +26 -4
  4. data/CONTRIBUTING.md +31 -0
  5. data/Gemfile +1 -12
  6. data/ISSUE_TEMPLATE.md +25 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +3 -3
  9. data/lib/reform.rb +1 -0
  10. data/lib/reform/contract.rb +1 -11
  11. data/lib/reform/contract/validate.rb +49 -23
  12. data/lib/reform/errors.rb +49 -0
  13. data/lib/reform/form.rb +20 -5
  14. data/lib/reform/form/dry.rb +57 -29
  15. data/lib/reform/form/populator.rb +2 -16
  16. data/lib/reform/form/prepopulate.rb +1 -1
  17. data/lib/reform/form/validate.rb +10 -2
  18. data/lib/reform/result.rb +63 -0
  19. data/lib/reform/validation.rb +19 -13
  20. data/lib/reform/validation/groups.rb +11 -25
  21. data/lib/reform/version.rb +1 -1
  22. data/reform.gemspec +7 -6
  23. data/test/benchmarking.rb +39 -5
  24. data/test/call_test.rb +1 -1
  25. data/test/changed_test.rb +1 -1
  26. data/test/coercion_test.rb +2 -2
  27. data/test/composition_test.rb +47 -9
  28. data/test/contract_test.rb +5 -5
  29. data/test/default_test.rb +1 -1
  30. data/test/deserialize_test.rb +3 -3
  31. data/test/errors_test.rb +36 -21
  32. data/test/feature_test.rb +1 -1
  33. data/test/fixtures/dry_error_messages.yml +70 -23
  34. data/test/form_option_test.rb +3 -3
  35. data/test/form_test.rb +3 -3
  36. data/test/from_test.rb +2 -2
  37. data/test/inherit_test.rb +44 -51
  38. data/test/module_test.rb +12 -12
  39. data/test/parse_option_test.rb +40 -0
  40. data/test/parse_pipeline_test.rb +2 -2
  41. data/test/populate_test.rb +59 -19
  42. data/test/populator_skip_test.rb +9 -8
  43. data/test/prepopulator_test.rb +3 -3
  44. data/test/readable_test.rb +2 -2
  45. data/test/readonly_test.rb +1 -1
  46. data/test/reform_test.rb +16 -31
  47. data/test/save_test.rb +23 -8
  48. data/test/setup_test.rb +2 -2
  49. data/test/skip_if_test.rb +4 -4
  50. data/test/skip_setter_and_getter_test.rb +1 -1
  51. data/test/test_helper.rb +13 -10
  52. data/test/validate_test.rb +18 -18
  53. data/test/validation/dry_validation_test.rb +430 -117
  54. data/test/validation/result_test.rb +79 -0
  55. data/test/validation_library_provided_test.rb +16 -0
  56. data/test/virtual_test.rb +1 -1
  57. data/test/writeable_test.rb +31 -2
  58. metadata +42 -23
  59. data/gemfiles/Gemfile.disposable-0.3 +0 -6
  60. data/lib/reform/contract/errors.rb +0 -43
  61. data/lib/reform/form/mongoid.rb +0 -37
  62. data/lib/reform/form/orm.rb +0 -26
  63. data/lib/reform/mongoid.rb +0 -4
  64. data/test/deprecation_test.rb +0 -27
  65. data/test/validation/dry_test.rb +0 -60
  66. data/test/validation/errors.yml +0 -4
@@ -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 < Reform::Form
8
+ class AlbumForm < TestForm
9
9
  property :name
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs do
15
15
  property :title
16
16
  validation do
17
- key(:title).required
17
+ required(:title).filled
18
18
  end
19
19
 
20
20
  property :composer do
21
21
  property :name
22
22
  validation do
23
- key(:name).required
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?.must_equal nil
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 < Reform::Form
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.must_equal nil
101
+ # assert_nil song.length
87
102
  # song.id.must_equal "10: 120"
88
103
  # end
89
104
  # end
@@ -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 < Reform::Form
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.must_equal nil
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"
@@ -2,13 +2,13 @@ require 'test_helper'
2
2
 
3
3
  class SkipIfTest < BaseTest
4
4
 
5
- class AlbumForm < Reform::Form
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
- key(:title).required
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.must_equal nil # hit hasn't been deserialised.
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 < Reform::Form
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
@@ -7,7 +7,7 @@ class SetupSkipSetterAndGetterTest < MiniTest::Spec
7
7
  Album = Struct.new(:title, :artist)
8
8
  Artist = Struct.new(:name)
9
9
 
10
- class AlbumForm < Reform::Form
10
+ class AlbumForm < TestForm
11
11
  property :title
12
12
 
13
13
  def title
@@ -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 < Reform::Form
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
@@ -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 < Reform::Contract
8
+ class AlbumForm < TestContract
9
9
  property :name
10
10
  validation do
11
- key(:name).required
11
+ required(:name).filled
12
12
  end
13
13
 
14
14
  collection :songs do
15
15
  property :title
16
16
  validation do
17
- key(:title).required
17
+ required(:title).filled
18
18
  end
19
19
 
20
20
  property :composer do
21
21
  validation do
22
- key(:name).required
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=>[\"is missing\"], :\"songs.composer.name\"=>[\"is missing\"]}"
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 < Reform::Form
64
+ class AlbumForm < TestForm
65
65
  property :name
66
66
  validation do
67
- key(:name).required
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
- key(:title).required
74
+ required(:title).filled
75
75
  end
76
76
 
77
77
  property :composer do
78
78
  property :name
79
79
  validation do
80
- key(:name).required
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 < Reform::Form
163
+ class AlbumForm < TestForm
164
164
  property :name
165
165
  validation do
166
- key(:name).required
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
- key(:title).required
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
- key(:name).required
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
- key(:name).required
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 < Reform::Form
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.must_equal nil
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/apotonick/reform/pull/104
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 < Reform::Form
8
- include Reform::Form::Dry
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
- key(:username).required
17
- key(:email).required
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").must_equal true
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 < Reform::Form
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 :default do
44
- key(:username).required
45
- key(:email).required
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
- key(:email).required(min_size?: 3)
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
- key(:password).required(min_size?: 2)
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
- key(:confirm_password).required(min_size?: 2)
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.inspect.must_equal "{:username=>[\"is missing\"], :email=>[\"is missing\"]}"
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=>[\"is missing\", \"size cannot be less than 2\"]}"
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=>[\"is missing\", \"size cannot be less than 2\"]}"
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=>[\"must be filled\", \"size cannot be less than 2\"]}"
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
- describe "Nested validations" do
98
- class AlbumForm < Reform::Form
99
- include Reform::Form::Dry::Validations
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
- # 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
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 :name
409
+ property :location
121
410
  end
122
411
  end
123
412
 
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
132
- end
133
- end
134
-
413
+ validation do
135
414
  configure do
136
- option :form
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
- def form_access_validation?(value)
144
- form.title == 'Reform'
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
- :title => "Blackhawks Over Los Angeles",
156
- :hit => song,
157
- :songs => songs,
158
- :band => Struct.new(:name, :label).new("Epitaph", OpenStruct.new),
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
- let (:song) { OpenStruct.new(:title => "Downtown") }
162
- let (:songs) { [ song = OpenStruct.new(:title => "Calling"), song ] }
449
+
163
450
  let (:form) { AlbumForm.new(album) }
164
451
 
165
- # correct #validate.
166
- it do
452
+ it "maps errors to form objects correctly" do
167
453
  result = form.validate(
168
- "title" => "Reform",
169
- "songs" => [
170
- {"title" => "Fallout"},
171
- {"title" => "Roxanne", "composer" => {"name" => "Sting"}}
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 true
177
- form.errors.messages.inspect.must_equal "{}"
178
- end
179
- end
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
- describe "fails with :validate, :validates and :validates_with" do
479
+ # FIXME: fix the "must be filled error"
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
+ 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
- property :username
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
- validation :email do
192
- validates(:email, &:filled?)
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
- e = proc do
199
- class FailingForm < Reform::Form
200
- include Reform::Form::Dry::Validations
506
+ validation do
507
+ configure do
508
+ config.messages_file = 'test/fixtures/dry_error_messages.yml'
509
+ end
201
510
 
202
- property :username
511
+ required(:title).filled
203
512
 
204
- validation :email do
205
- validate(:email, &:filled?)
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.must_raise(NoMethodError)
209
- # e.message.must_equal 'validate() is not supported by Dry Validation backend.'
520
+ end
210
521
 
211
- e = proc do
212
- class FailingForm < Reform::Form
213
- include Reform::Form::Dry::Validations
522
+ let (:form) { AlbumFormWith1NestedVal.new(album) }
214
523
 
215
- property :username
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
- 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.'
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 < Reform::Form
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 < Reform::Form
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
- key(:email).required
580
+ validation name: :email do
581
+ required(:email).filled
267
582
  end
268
583
 
269
- validation :email, inherit: true do # extends the above.
270
- key(:username).required
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=>[\"is missing\"], :username=>[\"is missing\"]}"
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 < Reform::Form
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
- key(:email).required
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]==true } do # extends the above.
303
- key(:username).required
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
- key(:password).required
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=>[\"is missing\"]}"
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 < Reform::Form
645
+ # class MultipleErrorsForPropertyForm < TestForm
333
646
  # include Reform::Form::Dry::Validations
334
647
 
335
648
  # property :username