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