dry-validation 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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