reform 2.3.0.rc1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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