reform 2.3.0.rc1 → 2.5.0

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