dry-validation 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d2494cc932a3cd69938b609aa9ad9b19477097a
4
- data.tar.gz: 1f8ea70284ddb90ef3c84f05f78684e66cfd040d
3
+ metadata.gz: 341f5b1e89f76e09d7a1d8d2d20cdb68a50a88c1
4
+ data.tar.gz: 6dada3ab87730f7fe97e600bb73868c2afba511f
5
5
  SHA512:
6
- metadata.gz: a1acce326a10195fe64fd864aa784b5c975122636e194b658572ff1bde82fd7eb5f31fb13bd785447243446f4cd6c31c7e2594feb6082270feb1e43a9fbe2395
7
- data.tar.gz: 61f64697186fd3ad6ca35b5a1ee5d9b9725fe705a45a72fc5fd0fb1b1c3f4180f9564d0bb85ed1302195d3c5a2b3fb27ea548412ad57167b0d9a50928bb461d5
6
+ metadata.gz: 331d2cd4ea7c0f2b3b96046ad46faf7de8746b1538aaaba8f0fdf07a71546088962653d96cc8b71a3ecba0b04850367b19841f0418d083702666e154890bf29c
7
+ data.tar.gz: ec3ec948092ab30ad982498ca234b385c66e136ec351cc0b8754f041a46a23cc59049b34fae76aec31d2cc46a2f11d91b9f469c862cc991c4605531600d31d39
@@ -0,0 +1,16 @@
1
+ engines:
2
+ rubocop:
3
+ enabled: true
4
+ checks:
5
+ Rubocop/Style/LambdaCall:
6
+ enabled: false
7
+ Rubocop/Style/StabbyLambdaParentheses:
8
+ enabled: false
9
+ Rubocop/Metrics/CyclomaticComplexity:
10
+ Max: 10
11
+ ratings:
12
+ paths:
13
+ - lib/**/*.rb
14
+ exclude_paths:
15
+ - spec/**/*
16
+ - examples/**/*
@@ -1,15 +1,36 @@
1
+ # v0.6.0 2016-01-20
2
+
3
+ ### Added
4
+
5
+ * Support for validating objects with attr readers via `attr` (SunnyMagadan)
6
+ * Support for `value` interface in the DSL for composing high-level rules based on values (solnic)
7
+ * Support for `rule(name: :something)` syntax for grouping high-level rules under
8
+ the same name (solnic)
9
+ * Support for `confirmation(:foo, some_predicate: some_argument)` shortcut syntax (solnic)
10
+ * Support for error messages for grouped rules (like `confirmation`) (solnic)
11
+ * Schemas support injecting rules from the outside (solnic)
12
+
13
+ ## Changed
14
+
15
+ * `rule` uses objects that inherit from `BasicObject` to avoid conflicts with
16
+ predicate names and built-in `Object` methods (solnic)
17
+ * In `Schema::Form` both `key` and `optional` will apply `filled?` predicate by
18
+ default when no block is passed (solnic)
19
+
20
+ [Compare v0.5.0...v0.6.0](https://github.com/dryrb/dry-validation/compare/v0.5.0...v0.6.0)
21
+
1
22
  # v0.5.0 2016-01-11
2
23
 
3
24
  ### Changed
4
25
 
5
- * Now depends on `[dry-logic](https://github.com/dryrb/dry-logic` for predicates and rules (solnic)
26
+ * Now depends on [dry-logic](https://github.com/dryrb/dry-logic) for predicates and rules (solnic)
6
27
  * `dry/validation/schema/form` is now required by default (solnic)
7
28
 
8
29
  ### Fixed
9
30
 
10
31
  * `Schema::Form` uses safe `form.array` and `form.hash` types which fixes #42 (solnic)
11
32
 
12
- [Compare v0.5.1...v0.5.0](https://github.com/dryrb/dry-validation/compare/v0.4.1...v0.5.0)
33
+ [Compare v0.4.1...v0.5.0](https://github.com/dryrb/dry-validation/compare/v0.4.1...v0.5.0)
13
34
 
14
35
  # v0.4.1 2015-12-27
15
36
 
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ gemspec
4
4
 
5
5
  group :test do
6
6
  gem 'i18n'
7
+ gem 'codeclimate-test-reporter', platform: :rbx
7
8
  end
8
9
 
9
10
  group :tools do
@@ -36,6 +36,8 @@ en:
36
36
 
37
37
  key?: "%{name} is missing"
38
38
 
39
+ attr?: "%{name} is missing"
40
+
39
41
  lt?: "%{name} must be less than %{num} (%{value} was given)"
40
42
 
41
43
  lteq?: "%{name} must be less than or equal to %{num}"
@@ -15,10 +15,10 @@ Gem::Specification.new do |spec|
15
15
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
16
  spec.require_paths = ['lib']
17
17
 
18
- spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
19
- spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.6'
18
+ spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3'
19
+ spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.8'
20
20
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
21
- spec.add_runtime_dependency 'dry-logic', '~> 0.1'
21
+ spec.add_runtime_dependency 'dry-logic', '~> 0.1', '>= 0.1.2'
22
22
  spec.add_runtime_dependency 'dry-data', '~> 0.5', '>= 0.5.0'
23
23
 
24
24
  spec.add_development_dependency 'bundler'
@@ -4,6 +4,7 @@ module Dry
4
4
  attr_reader :messages, :options
5
5
 
6
6
  DEFAULT_RESULT = {}.freeze
7
+ KEY_SEPARATOR = '.'.freeze
7
8
 
8
9
  def initialize(messages, options = {})
9
10
  @messages = messages
@@ -11,7 +12,7 @@ module Dry
11
12
  end
12
13
 
13
14
  def call(ast)
14
- ast.map { |node| visit(node) }.reduce(:merge) || DEFAULT_RESULT
15
+ merge(ast.map { |node| visit(node) }) || DEFAULT_RESULT
15
16
  end
16
17
 
17
18
  def with(new_options)
@@ -26,13 +27,25 @@ module Dry
26
27
  visit(error)
27
28
  end
28
29
 
29
- def visit_input(input, *args)
30
- name, value, rules = input
31
- { name => [rules.map { |rule| visit(rule, name, value) }, value] }
30
+ def visit_input(input, *)
31
+ name = normalize_name(input[0])
32
+ _, value, rules = input
33
+ errors = [rules.map { |rule| visit(rule, name, value) }, value]
34
+
35
+ if input[0].is_a?(Hash)
36
+ root, sub = input[0].to_a.flatten
37
+ { root => { sub => errors } }
38
+ else
39
+ { input[0] => errors }
40
+ end
41
+ end
42
+
43
+ def visit_group(_, name, _)
44
+ messages[name, rule: name]
32
45
  end
33
46
 
34
- def visit_check(node, *args)
35
- name, _ = node
47
+ def visit_check(node, *)
48
+ name = normalize_name(node[0])
36
49
  messages[name, rule: name]
37
50
  end
38
51
 
@@ -41,6 +54,11 @@ module Dry
41
54
  visit(predicate, value, name)
42
55
  end
43
56
 
57
+ def visit_attr(rule, name, value)
58
+ _, predicate = rule
59
+ visit(predicate, value, name)
60
+ end
61
+
44
62
  def visit_val(rule, name, value)
45
63
  name, predicate = rule
46
64
  visit(predicate, value, name)
@@ -59,15 +77,19 @@ module Dry
59
77
  template % tokens
60
78
  end
61
79
 
62
- def visit_key?(*args, value)
80
+ def visit_key?(*args, _value)
63
81
  { name: args[0][0] }
64
82
  end
65
83
 
66
- def visit_exclusion?(*args, value)
84
+ def visit_attr?(*args, _value)
85
+ { name: args[0][0] }
86
+ end
87
+
88
+ def visit_exclusion?(*args, _value)
67
89
  { list: args[0][0].join(', ') }
68
90
  end
69
91
 
70
- def visit_inclusion?(*args, value)
92
+ def visit_inclusion?(*args, _value)
71
93
  { list: args[0][0].join(', ') }
72
94
  end
73
95
 
@@ -113,7 +135,21 @@ module Dry
113
135
  end
114
136
  end
115
137
 
116
- def method_missing(meth, *args)
138
+ private
139
+
140
+ def normalize_name(name)
141
+ Array(name).join(KEY_SEPARATOR).to_sym
142
+ end
143
+
144
+ def merge(result)
145
+ result.reduce do |a, e|
146
+ e.merge(a) do |_, l, r|
147
+ l.is_a?(Hash) ? l.merge(r) : l + r
148
+ end
149
+ end
150
+ end
151
+
152
+ def method_missing(_meth, *args)
117
153
  { value: args[1] }
118
154
  end
119
155
  end
@@ -61,6 +61,11 @@ module Dry
61
61
  name
62
62
  end
63
63
 
64
+ def visit_attr(node)
65
+ name, _ = node
66
+ name
67
+ end
68
+
64
69
  def method_missing(name, *args)
65
70
  nil
66
71
  end
@@ -13,8 +13,12 @@ module Dry
13
13
  rule_results.each(&block)
14
14
  end
15
15
 
16
+ def [](name)
17
+ to_h[name]
18
+ end
19
+
16
20
  def to_h
17
- each_with_object({}) { |result, hash| hash[result.name] = result }
21
+ @to_h ||= each_with_object({}) { |result, hash| hash[result.name] = result }
18
22
  end
19
23
 
20
24
  def merge!(other)
@@ -38,7 +38,7 @@ module Dry
38
38
  when :yaml then Messages.default
39
39
  when :i18n then Messages::I18n.new
40
40
  else
41
- raise RuntimeError, "+#{config.messages}+ is not a valid messages identifier"
41
+ fail "+#{config.messages}+ is not a valid messages identifier"
42
42
  end
43
43
 
44
44
  if config.messages_file && config.namespace
@@ -70,18 +70,20 @@ module Dry
70
70
 
71
71
  attr_reader :rules, :schemas, :groups, :checks
72
72
 
73
+ attr_reader :rule_compiler
74
+
73
75
  attr_reader :error_compiler
74
76
 
75
77
  attr_reader :hint_compiler
76
78
 
77
- def initialize(error_compiler = self.class.error_compiler, hint_compiler = self.class.hint_compiler)
78
- compiler = Logic::RuleCompiler.new(self)
79
- @rules = compiler.(self.class.rules.map(&:to_ary))
80
- @checks = self.class.checks
81
- @groups = compiler.(self.class.groups.map(&:to_ary))
79
+ def initialize(rules = [])
80
+ @rule_compiler = Logic::RuleCompiler.new(self)
81
+ @rules = rule_compiler.(self.class.rules.map(&:to_ary) + rules.map(&:to_ary))
82
+ @checks = self.class.checks.map(&:to_ary)
83
+ @groups = rule_compiler.(self.class.groups.map(&:to_ary))
82
84
  @schemas = self.class.schemas.map(&:new)
83
- @error_compiler = error_compiler
84
- @hint_compiler = hint_compiler
85
+ @error_compiler = self.class.error_compiler
86
+ @hint_compiler = self.class.hint_compiler
85
87
  end
86
88
 
87
89
  def call(input)
@@ -92,10 +94,11 @@ module Dry
92
94
  end
93
95
 
94
96
  if checks.size > 0
95
- compiled_checks = Logic::RuleCompiler.new(result.to_h).(checks)
97
+ resolver = -> name { result[name] || self[name] }
98
+ compiled_checks = Logic::RuleCompiler.new(resolver).(checks)
96
99
 
97
100
  compiled_checks.each do |rule|
98
- result << rule.()
101
+ result << rule.(result)
99
102
  end
100
103
  end
101
104
 
@@ -116,7 +119,7 @@ module Dry
116
119
  elsif respond_to?(name)
117
120
  Logic::Predicate.new(name, &method(name))
118
121
  else
119
- raise ArgumentError, "+#{name}+ is not a valid predicate name"
122
+ fail ArgumentError, "+#{name}+ is not a valid predicate name"
120
123
  end
121
124
  end
122
125
 
@@ -0,0 +1,37 @@
1
+ module Dry
2
+ module Validation
3
+ class Schema
4
+ class Attr < BasicObject
5
+ attr_reader :name, :rules
6
+
7
+ def initialize(name, rules, &block)
8
+ @name = name
9
+ @rules = rules
10
+ end
11
+
12
+ private
13
+
14
+ def method_missing(meth, *args, &block)
15
+ attr_rule = [:attr, [name, [:predicate, [meth, args]]]]
16
+
17
+ if block
18
+ val_rule = yield(Value.new(name))
19
+
20
+ rules <<
21
+ if val_rule.is_a?(::Array)
22
+ Schema::Rule.new(name, [:and, [attr_rule, [:set, [name, val_rule.map(&:to_ary)]]]])
23
+ else
24
+ Schema::Rule.new(name, [:and, [attr_rule, val_rule.to_ary]])
25
+ end
26
+ else
27
+ Schema::Rule.new(name, attr_rule)
28
+ end
29
+ end
30
+
31
+ def respond_to_missing?(*)
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -13,10 +13,18 @@ module Dry
13
13
  Key.new(name, rules).key?(&block)
14
14
  end
15
15
 
16
+ def attr(name, &block)
17
+ Attr.new(name, rules).attr?(&block)
18
+ end
19
+
16
20
  def optional(name, &block)
17
21
  Key.new(name, rules).optional(&block)
18
22
  end
19
23
 
24
+ def value(name)
25
+ Schema::Rule::Result.new(name, [])
26
+ end
27
+
20
28
  def rule(name, **options, &block)
21
29
  if options.any?
22
30
  predicate, rule_names = options.to_a.first
@@ -32,13 +40,22 @@ module Dry
32
40
  end
33
41
  end
34
42
 
35
- def confirmation(name)
36
- identifier = :"#{name}_confirmation"
43
+ def confirmation(name, options = {})
44
+ conf_name = :"#{name}_confirmation"
45
+
46
+ unless rule_by_name(name)
47
+ if options.any?
48
+ key(name) do |value|
49
+ options.map { |p, args| value.__send__(:"#{p}?", *args) }.reduce(:&)
50
+ end
51
+ else
52
+ key(name, &:filled?)
53
+ end
54
+ end
37
55
 
38
- key(name, &:filled?)
39
- key(identifier, &:filled?)
56
+ key(conf_name, &:filled?)
40
57
 
41
- rule(identifier, eql?: [name, identifier])
58
+ rule(conf_name, eql?: [name, conf_name])
42
59
  end
43
60
 
44
61
  private
@@ -54,3 +71,4 @@ end
54
71
  require 'dry/validation/schema/rule'
55
72
  require 'dry/validation/schema/value'
56
73
  require 'dry/validation/schema/key'
74
+ require 'dry/validation/schema/attr'
@@ -6,6 +6,22 @@ module Dry
6
6
  class Schema::Form < Schema
7
7
  attr_reader :input_type
8
8
 
9
+ def self.key(name, &block)
10
+ if block
11
+ super
12
+ else
13
+ super(name, &:filled?)
14
+ end
15
+ end
16
+
17
+ def self.optional(name, &block)
18
+ if block
19
+ super
20
+ else
21
+ super(name, &:filled?)
22
+ end
23
+ end
24
+
9
25
  def initialize
10
26
  super
11
27
  @input_type = InputTypeCompiler.new.(self.class.rules.map(&:to_ary))
@@ -41,7 +41,7 @@ module Dry
41
41
  end
42
42
  end
43
43
 
44
- def respond_to_missing?(meth, _include_private = false)
44
+ def respond_to_missing?(*)
45
45
  true
46
46
  end
47
47
  end
@@ -40,11 +40,19 @@ module Dry
40
40
  all_msgs = error_compiler.with(options).(errors.map(&:to_ary))
41
41
  hints = hint_compiler.with(options).call
42
42
 
43
- all_msgs.each do |(name, data)|
44
- data[0].concat(hints[name]).uniq!
43
+ msgs_data = all_msgs.map do |(name, data)|
44
+ msgs, input =
45
+ if data.is_a?(Hash)
46
+ values = data.values
47
+ [values.map(&:first).flatten.concat(hints[name]).uniq, values[0][1]]
48
+ else
49
+ [data[0].concat(hints[name]).uniq.flatten, data[1]]
50
+ end
51
+
52
+ [name, [msgs, input]]
45
53
  end
46
54
 
47
- all_msgs
55
+ Hash[msgs_data]
48
56
  end
49
57
  end
50
58
 
@@ -1,20 +1,38 @@
1
1
  module Dry
2
2
  module Validation
3
3
  class Schema
4
- class Rule
4
+ class Rule < BasicObject
5
5
  attr_reader :name, :node
6
6
 
7
7
  class Check < Rule
8
+ def class
9
+ Schema::Rule::Check
10
+ end
11
+
8
12
  def method_missing(meth, *)
9
13
  self.class.new(name, [:check, [name, [:predicate, [name, [meth]]]]])
10
14
  end
11
15
  end
12
16
 
17
+ class Result < Rule
18
+ def class
19
+ Schema::Rule::Result
20
+ end
21
+
22
+ def method_missing(meth, *args)
23
+ self.class.new(name, [:res, [name, [:predicate, [meth, args]]]])
24
+ end
25
+ end
26
+
13
27
  def initialize(name, node)
14
28
  @name = name
15
29
  @node = node
16
30
  end
17
31
 
32
+ def class
33
+ Schema::Rule
34
+ end
35
+
18
36
  def to_ary
19
37
  node
20
38
  end
@@ -24,6 +42,10 @@ module Dry
24
42
  Rule::Check.new(name, [:check, [name, [:predicate, [name, []]]]])
25
43
  end
26
44
 
45
+ def is_a?(other)
46
+ self.class == other
47
+ end
48
+
27
49
  def not
28
50
  self.class.new(:"not_#{name}", [:not, node])
29
51
  end