dry-validation 0.5.0 → 0.6.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.
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