dry-validation 0.2.0 → 0.3.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -2
  3. data/Gemfile +4 -0
  4. data/README.md +131 -42
  5. data/config/errors.yml +36 -27
  6. data/examples/basic.rb +2 -4
  7. data/examples/each.rb +2 -2
  8. data/examples/form.rb +1 -2
  9. data/examples/nested.rb +2 -4
  10. data/examples/rule_ast.rb +0 -8
  11. data/lib/dry/validation.rb +0 -5
  12. data/lib/dry/validation/error.rb +2 -6
  13. data/lib/dry/validation/error_compiler.rb +19 -5
  14. data/lib/dry/validation/input_type_compiler.rb +2 -1
  15. data/lib/dry/validation/messages.rb +7 -58
  16. data/lib/dry/validation/messages/abstract.rb +75 -0
  17. data/lib/dry/validation/messages/i18n.rb +24 -0
  18. data/lib/dry/validation/messages/namespaced.rb +27 -0
  19. data/lib/dry/validation/messages/yaml.rb +50 -0
  20. data/lib/dry/validation/result.rb +19 -49
  21. data/lib/dry/validation/rule.rb +2 -2
  22. data/lib/dry/validation/rule/group.rb +21 -0
  23. data/lib/dry/validation/rule/result.rb +73 -0
  24. data/lib/dry/validation/rule_compiler.rb +5 -0
  25. data/lib/dry/validation/schema.rb +33 -14
  26. data/lib/dry/validation/schema/definition.rb +16 -0
  27. data/lib/dry/validation/schema/result.rb +21 -3
  28. data/lib/dry/validation/schema/rule.rb +1 -1
  29. data/lib/dry/validation/schema/value.rb +2 -1
  30. data/lib/dry/validation/version.rb +1 -1
  31. data/spec/fixtures/locales/en.yml +5 -0
  32. data/spec/fixtures/locales/pl.yml +14 -0
  33. data/spec/integration/custom_error_messages_spec.rb +4 -16
  34. data/spec/{unit → integration}/error_compiler_spec.rb +81 -39
  35. data/spec/integration/localized_error_messages_spec.rb +52 -0
  36. data/spec/integration/messages/i18n_spec.rb +71 -0
  37. data/spec/integration/rule_groups_spec.rb +35 -0
  38. data/spec/integration/schema_form_spec.rb +9 -9
  39. data/spec/integration/schema_spec.rb +2 -2
  40. data/spec/shared/predicates.rb +2 -0
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/unit/rule/group_spec.rb +12 -0
  43. data/spec/unit/schema_spec.rb +35 -0
  44. metadata +24 -6
  45. data/spec/fixtures/errors.yml +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee26d9a3fbca2ae3faf18827ad28f02bd323239b
4
- data.tar.gz: a007eccefbc3456f8a481549512896074dcf3f86
3
+ metadata.gz: a4c5757cf4c0d97231cb5077aa617a731ae8b77c
4
+ data.tar.gz: bb77ddf24b5108592e5ab92961bff80caefb20e6
5
5
  SHA512:
6
- metadata.gz: fe7c8c42376862ea8fbe14d3563ad65f7e3bc3f8d9b8bbbb9bff2fc239b7e16bfb9a11fd6cba5f2d0957650cd365470beaacb158a5a078998a589a15d92ca571
7
- data.tar.gz: 8d337dd7bbf3fef8ca79f41ea962bc692ac6dd25b6fd93fca7c55559f0f3723f37e1bdd5fea804d3fcccbda1358f0360c8576268ecf766eed589208ac4771a31
6
+ metadata.gz: 78c62e2a2585215397e12cb0621497e1b4332750b61501534b0e1f643f7d5ea7f82e05f3c64b9977a313acf87466c8031ec160e3706794b34ce90c1fe8a7300a
7
+ data.tar.gz: 1a9685102ac167001c910e617dee87cea45f6413d105f5231e93790bc3dad0c7199e67a0d838b294185520e26a5475ab56f9b13df08be92e5880a630b129ca20
@@ -1,10 +1,34 @@
1
- # v0.2.0 to-be-released
1
+ # v0.3.0 to-be-released
2
+
3
+ ### Added
4
+
5
+ * I18n messages support (solnic)
6
+ * Ability to configure `messages` via `configure { config.messages = :i18n }` (solnic)
7
+ * `rule` interface in DSL for defining rules that depend on other rules (solnic)
8
+ * `confirmation` interface as a shortcut for defining "confirmation of" rule (solnic)
9
+ * Error messages can be now matched by input value type too (solnic)
10
+
11
+ ### Fixed
12
+
13
+ * `optional` rule with coercions work correctly with `|` + multiple `&`s (solnic)
14
+ * `Schema#[]` checks registered predicates first before defaulting to its own predicates (solnic)
15
+
16
+ ### Changed
17
+
18
+ * `Schema#messages(input)` => `Schema#call(input).messages` (solnic)
19
+ * `Schema#call` returns `Schema::Result` which has access to all rule results,
20
+ errors and messages
21
+ * `Schema::Result#messages` returns a hash with rule names, messages and input values (solnic)
22
+
23
+ [Compare v0.2.0...HEAD](https://github.com/dryrb/dry-validation/compare/v0.2.0...HEAD)
24
+
25
+ # v0.2.0 2015-11-30
2
26
 
3
27
  ### Added
4
28
 
5
29
  * `Schema::Form` with a built-in coercer inferred from type-check predicates (solnic)
6
30
  * Ability to pass a block to predicate check in the DSL ie `value.hash? { ... }` (solnic)
7
- * Optional keys using `option(:key_name) { ... }` interface in the DSL (solnic)
31
+ * Optional keys using `optional(:key_name) { ... }` interface in the DSL (solnic)
8
32
  * New predicates:
9
33
  - `bool?`
10
34
  - `date?`
data/Gemfile CHANGED
@@ -2,6 +2,10 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ group :test do
6
+ gem 'i18n'
7
+ end
8
+
5
9
  group :tools do
6
10
  gem 'rubocop'
7
11
  gem 'guard'
data/README.md CHANGED
@@ -75,15 +75,15 @@ end
75
75
 
76
76
  schema = Schema.new
77
77
 
78
- errors = schema.messages(email: 'jane@doe.org', age: 19)
78
+ errors = schema.call(email: 'jane@doe.org', age: 19).messages
79
79
 
80
80
  puts errors.inspect
81
81
  # []
82
82
 
83
- errors = schema.messages(email: nil, age: 19)
83
+ errors = schema.call(email: nil, age: 19).messages
84
84
 
85
85
  puts errors.inspect
86
- # [[:email, ["email must be filled"]]]
86
+ # { :email => [["email must be filled", nil]] }
87
87
  ```
88
88
 
89
89
  A couple of remarks:
@@ -112,15 +112,15 @@ end
112
112
 
113
113
  schema = Schema.new
114
114
 
115
- errors = schema.messages(email: 'jane@doe.org')
115
+ errors = schema.call(email: 'jane@doe.org').messages
116
116
 
117
117
  puts errors.inspect
118
118
  # []
119
119
 
120
- errors = schema.messages(email: 'jane@doe.org', age: 17)
120
+ errors = schema.call(email: 'jane@doe.org', age: 17).messages
121
121
 
122
122
  puts errors.inspect
123
- # [[:age, ["age must be greater than 18 (17 was given)"]]]
123
+ # { :age => [["age must be greater than 18"], 17] }
124
124
  ```
125
125
 
126
126
  ### Optional Values
@@ -140,20 +140,20 @@ end
140
140
 
141
141
  schema = Schema.new
142
142
 
143
- errors = schema.messages(email: 'jane@doe.org', age: nil)
143
+ errors = schema.call(email: 'jane@doe.org', age: nil).messages
144
144
 
145
145
  puts errors.inspect
146
146
  # []
147
147
 
148
- errors = schema.messages(email: 'jane@doe.org', age: 19)
148
+ errors = schema.call(email: 'jane@doe.org', age: 19).messages
149
149
 
150
150
  puts errors.inspect
151
151
  # []
152
152
 
153
- errors = schema.messages(email: 'jane@doe.org', age: 17)
153
+ errors = schema.call(email: 'jane@doe.org', age: 17).messages
154
154
 
155
155
  puts errors.inspect
156
- # [[:age, ["age must be greater than 18 (17 was given)"]]]
156
+ # { :age => [["age must be greater than 18"], 17] }
157
157
  ```
158
158
 
159
159
  ### Optional Key vs Value
@@ -201,15 +201,20 @@ end
201
201
 
202
202
  schema = Schema.new
203
203
 
204
- errors = schema.messages({})
204
+ errors = schema.call({}).messages
205
205
 
206
206
  puts errors.inspect
207
- # [[:address, ["address is missing"]]]
207
+ # { :address => ["address is missing"] }
208
208
 
209
- errors = schema.messages(address: { city: 'NYC' })
209
+ errors = schema.call(address: { city: 'NYC' }).messages
210
210
 
211
- puts errors.inspect
212
- # [[:address, [[:street, ["street is missing"]], [:country, ["country is missing"]]]]]
211
+ puts errors.to_h.inspect
212
+ # {
213
+ # :address => [
214
+ # { :street => ["street is missing"] },
215
+ # { :country => ["country is missing"] }
216
+ # ]
217
+ # }
213
218
  ```
214
219
 
215
220
  ### Array Elements
@@ -227,17 +232,50 @@ end
227
232
 
228
233
  schema = Schema.new
229
234
 
230
- errors = schema.messages(phone_numbers: '')
235
+ errors = schema.call(phone_numbers: '').messages
231
236
 
232
237
  puts errors.inspect
233
- # [[:phone_numbers, ["phone_numbers must be an array"]]]
238
+ # { :phone_numbers => [["phone_numbers must be an array", ""]] }
234
239
 
235
- errors = schema.messages(phone_numbers: ['123456789', 123456789])
240
+ errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
236
241
 
237
242
  puts errors.inspect
238
- # [[:phone_numbers, [[:phone_numbers, ["phone_numbers must be a string"]]]]]
243
+ # {
244
+ # :phone_numbers => [
245
+ # {
246
+ # :phone_numbers => [
247
+ # ["phone_numbers must be a string", 123456789]
248
+ # ]
249
+ # }
250
+ # ]
251
+ # }
252
+ ```
253
+
254
+ ### Rules Depending On Other Rules
255
+
256
+ When a rule needs input from other rules and depends on their results you can
257
+ define it using `rule` DSL. A common example of this is "confirmation validation":
258
+
259
+ ``` ruby
260
+ class Schema < Dry::Validation::Schema
261
+ key(:password, &:filled?)
262
+ key(:password_confirmation, &:filled?)
263
+
264
+ rule(:password_confirmation, eql?: [:password, :password_confirmation])
265
+ end
239
266
  ```
240
267
 
268
+ A short version of the same thing:
269
+
270
+ ``` ruby
271
+ class Schema < Dry::Validation::Schema
272
+ confirmation(:password)
273
+ end
274
+ ```
275
+
276
+ Notice that you must add `:password_confirmation` error message configuration if
277
+ you want to have the error converted to a message.
278
+
241
279
  ### Form Validation With Coercions
242
280
 
243
281
  Probably the most common use case is to validate form params. This is a special
@@ -262,11 +300,13 @@ end
262
300
 
263
301
  schema = UserFormSchema.new
264
302
 
265
- errors = schema.messages('email' => '', 'age' => '18')
303
+ errors = schema.call('email' => '', 'age' => '18').messages
266
304
 
267
305
  puts errors.inspect
268
-
269
- # [[:email, ["email must be filled"]], [:age, ["age must be greater than 18 (18 was given)"]]]
306
+ # {
307
+ # :email => [["email must be filled", nil]],
308
+ # :age => [["age must be greater than 18 (18 was given)", 18]]
309
+ # }
270
310
  ```
271
311
 
272
312
  There are few major differences between how it works here and in `ActiveModel`:
@@ -350,7 +390,8 @@ You can learn how to do that in the [Error Messages](https://github.com/dryrb/dr
350
390
  * `lteq?`
351
391
  * `max_size?`
352
392
  * `min_size?`
353
- * `size?`
393
+ * `size?(int)`
394
+ * `size?(range)`
354
395
  * `format?`
355
396
  * `inclusion?`
356
397
  * `exclusion?`
@@ -359,7 +400,7 @@ You can learn how to do that in the [Error Messages](https://github.com/dryrb/dr
359
400
 
360
401
  By default `dry-validation` comes with a set of pre-defined error messages for
361
402
  every built-in predicate. They are defined in [a yaml file](https://github.com/dryrb/dry-validation/blob/master/config/errors.yml)
362
- which is shipped with the gem.
403
+ which is shipped with the gem. This file is compatible with `I18n` format.
363
404
 
364
405
  You can provide your own messages and configure your schemas to use it like that:
365
406
 
@@ -380,18 +421,31 @@ end
380
421
  Lookup rules:
381
422
 
382
423
  ``` yaml
383
- filled?: "%{name} must be filled"
384
-
385
- attributes:
386
- email:
387
- filled?: "the email is missing"
388
-
389
- user:
390
- filled?: "%{name} name cannot be blank"
391
-
392
- attributes:
393
- address:
394
- filled?: "You gotta tell us where you live"
424
+ en:
425
+ errors:
426
+ size?:
427
+ arg:
428
+ default: "%{name} size must be %{num}"
429
+ range: "%{name} size must be within %{left} - %{right}"
430
+
431
+ value:
432
+ string:
433
+ arg:
434
+ default: "%{name} length must be %{num}"
435
+ range: "%{name} length must be within %{left} - %{right}"
436
+
437
+ filled?: "%{name} must be filled"
438
+
439
+ rules:
440
+ email:
441
+ filled?: "the email is missing"
442
+
443
+ user:
444
+ filled?: "%{name} name cannot be blank"
445
+
446
+ rules:
447
+ address:
448
+ filled?: "You gotta tell us where you live"
395
449
  ```
396
450
 
397
451
  Given the yaml file above, messages lookup works as follows:
@@ -399,15 +453,25 @@ Given the yaml file above, messages lookup works as follows:
399
453
  ``` ruby
400
454
  messages = Dry::Validation::Messages.load('/path/to/our/errors.yml')
401
455
 
402
- messages.lookup(:filled?, :age) # => "age must be filled"
403
- messages.lookup(:filled?, :address) # => "address must be filled"
404
- messages.lookup(:filled?, :email) # => "the email is missing"
456
+ # matching arg type for size? predicate
457
+ messages[:size?, rule: :name, arg_type: Fixnum] # => "%{name} size must be %{num}"
458
+ messages[:size?, rule: :name, arg_type: Range] # => "%{name} size must within %{left} - %{right}"
459
+
460
+ # matching val type for size? predicate
461
+ messages[:size?, rule: :name, val_type: String] # => "%{name} length must be %{num}"
462
+
463
+ # matching predicate
464
+ messages[:filled?, rule: :age] # => "%{name} must be filled"
465
+ messages[:filled?, rule: :address] # => "%{name} must be filled"
466
+
467
+ # matching predicate for a specific rule
468
+ messages[:filled?, rule: :email] # => "the email is missing"
405
469
 
406
470
  # with namespaced messages
407
471
  user_messages = messages.namespaced(:user)
408
472
 
409
- user_messages.lookup(:filled?, :age) # "age cannot be blank"
410
- user_messages.lookup(:filled?, :address) # "You gotta tell us where you live"
473
+ user_messages[:filled?, rule: :age] # "%{name} cannot be blank"
474
+ user_messages[:filled?, rule: :address] # "You gotta tell us where you live"
411
475
  ```
412
476
 
413
477
  By configuring `messages_file` and/or `namespace` in a schema, default messages
@@ -415,7 +479,32 @@ are going to be automatically merged with your overrides and/or namespaced.
415
479
 
416
480
  ## I18n Integration
417
481
 
418
- Coming (very) soon...
482
+ If you are using `i18n` gem and load it before `dry-validation` then you'll be
483
+ able to configure a schema to use `i18n` messages:
484
+
485
+ ``` ruby
486
+ require 'i18n'
487
+ require 'dry-validation'
488
+
489
+ class Schema < Dry::Validation::Schema
490
+ configure { config.messages = :i18n }
491
+
492
+ key(:email, &:filled?)
493
+ end
494
+
495
+ schema = Schema.new
496
+
497
+ # return default translations
498
+ puts schema.call(email: '').messages
499
+ { :email => ["email must be filled"] }
500
+
501
+ # return other translations (assuming you have it :))
502
+ puts schema.call(email: '').messages(locale: :pl)
503
+ { :email => ["email musi być wypełniony"] }
504
+ ```
505
+
506
+ Important: I18n must be initialized before using schema, `dry-validation` does
507
+ not try to do it for you, it only sets its default error translations automatically.
419
508
 
420
509
  ## Rule AST
421
510
 
@@ -1,51 +1,60 @@
1
- array?: "%{name} must be an array"
1
+ en:
2
+ errors:
3
+ array?: "%{name} must be an array"
2
4
 
3
- empty?: "%{name} cannot be empty"
5
+ empty?: "%{name} cannot be empty"
4
6
 
5
- exclusion?: "%{name} must not be one of: %{list}"
7
+ exclusion?: "%{name} must not be one of: %{list}"
6
8
 
7
- eql?: "%{name} must be equal to %{eql_value}"
9
+ eql?: "%{name} must be equal to %{eql_value}"
8
10
 
9
- filled?: "%{name} must be filled"
11
+ filled?: "%{name} must be filled"
10
12
 
11
- format?: "%{name} is in invalid format"
13
+ format?: "%{name} is in invalid format"
12
14
 
13
- gt?: "%{name} must be greater than %{num} (%{value} was given)"
15
+ gt?: "%{name} must be greater than %{num}"
14
16
 
15
- gteq?: "%{name} must be greater than or equal to %{num}"
17
+ gteq?: "%{name} must be greater than or equal to %{num}"
16
18
 
17
- hash?: "%{name} must be a hash"
19
+ hash?: "%{name} must be a hash"
18
20
 
19
- inclusion?: "%{name} must be one of: %{list}"
21
+ inclusion?: "%{name} must be one of: %{list}"
20
22
 
21
- bool?: "%{name} must be boolean"
23
+ bool?: "%{name} must be boolean"
22
24
 
23
- int?: "%{name} must be an integer"
25
+ int?: "%{name} must be an integer"
24
26
 
25
- float?: "%{name} must be a float"
27
+ float?: "%{name} must be a float"
26
28
 
27
- decimal?: "%{name} must be a decimal"
29
+ decimal?: "%{name} must be a decimal"
28
30
 
29
- date?: "%{name} must be a date"
31
+ date?: "%{name} must be a date"
30
32
 
31
- date_time?: "%{name} must be a date time"
33
+ date_time?: "%{name} must be a date time"
32
34
 
33
- time?: "%{name} must be a time"
35
+ time?: "%{name} must be a time"
34
36
 
35
- key?: "%{name} is missing"
37
+ key?: "%{name} is missing"
36
38
 
37
- lt?: "%{name} must be less than %{num} (%{value} was given)"
39
+ lt?: "%{name} must be less than %{num} (%{value} was given)"
38
40
 
39
- lteq?: "%{name} must be less than or equal to %{num}"
41
+ lteq?: "%{name} must be less than or equal to %{num}"
40
42
 
41
- max_size?: "%{name} size cannot be greater than %{num}"
43
+ max_size?: "%{name} size cannot be greater than %{num}"
42
44
 
43
- min_size?: "%{name} size cannot be less than %{num}"
45
+ min_size?: "%{name} size cannot be less than %{num}"
44
46
 
45
- nil?: "%{name} cannot be nil"
47
+ none?: "%{name} cannot be defined"
46
48
 
47
- size?:
48
- range: "%{name} size must be within %{left} - %{right}"
49
- default: "%{name} size must be %{num}"
49
+ str?: "%{name} must be a string"
50
50
 
51
- str?: "%{name} must be a string"
51
+ size?:
52
+ arg:
53
+ default: "%{name} size must be %{num}"
54
+ range: "%{name} size must be within %{left} - %{right}"
55
+
56
+ value:
57
+ string:
58
+ arg:
59
+ default: "%{name} length must be %{num}"
60
+ range: "%{name} length must be within %{left} - %{right}"
@@ -10,12 +10,10 @@ end
10
10
 
11
11
  schema = Schema.new
12
12
 
13
- errors = schema.messages(email: 'jane@doe.org', age: 19)
13
+ errors = schema.call(email: 'jane@doe.org', age: 19).messages
14
14
 
15
15
  puts errors.inspect
16
- # []
17
16
 
18
- errors = schema.messages(email: nil, age: 19)
17
+ errors = schema.call(email: nil, age: 19).messages
19
18
 
20
19
  puts errors.inspect
21
- # [[:email, ["email must be filled"]]]
@@ -10,10 +10,10 @@ end
10
10
 
11
11
  schema = Schema.new
12
12
 
13
- errors = schema.messages(phone_numbers: '')
13
+ errors = schema.call(phone_numbers: '').messages
14
14
 
15
15
  puts errors.inspect
16
16
 
17
- errors = schema.messages(phone_numbers: ['123456789', 123456789])
17
+ errors = schema.call(phone_numbers: ['123456789', 123456789]).messages
18
18
 
19
19
  puts errors.inspect