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