dry-logic 0.2.3 → 0.3.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: 95bd134452791d94fdaf05fc2afc746040800e47
4
- data.tar.gz: c1a47707cec851ac223ba9a7ce5dcbe46d0f2e9e
3
+ metadata.gz: 0ea86d568127098e2777868066c2026dbc380eb9
4
+ data.tar.gz: 5553735b89ab2ef6a1c5bdd3f24874bab46c2581
5
5
  SHA512:
6
- metadata.gz: b8d2c782c82ee06c6f434c3aa768c85fc6ba4d35ec2196d2e4318d8870ff84368284a6774ebe1ee6db82681cd8c5551be971d86e59a56251f6547750ed43f520
7
- data.tar.gz: 03d4dad7d29603a97541db67089f27b3d44a36114592273e5e6d6a102785186021bdc3070da72a11d675a0e5b2d10b1e0b85ac287db24d59d1745e5250783d0c
6
+ metadata.gz: c943b1c67b55618b3e3fa1e982e371614a3e34c0ec2ca7457c775e755335a9579f52434d50545b411bf053f3c8e5a72209bdc86a58d7f13abef521df92b1fde5
7
+ data.tar.gz: 87598a0a72b1196d966464b1a731746922613fa8eb72cfa3d002ecfe3dee917502126bcfb2e506ae67c958335a6343b05db131e7012d1a0c64d74320d4e3d2d6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # v0.3.0 2016-07-01
2
+
3
+ ### Added
4
+
5
+ * `:type?` predicate imported from dry-types (solnic)
6
+ * `Rule#curry` interface (solnic)
7
+
8
+ ### Changed
9
+
10
+ * Predicates AST now includes information about args (names & possible values) (fran-worley + solnic)
11
+ * Predicates raise errors when they are called with invalid arity (fran-worley + solnic)
12
+ * Rules no longer evaluate input twice when building result objects (solnic)
13
+
14
+ [Compare v0.2.3...v0.3.0](https://github.com/dryrb/dry-logic/compare/v0.2.3...v0.3.0)
15
+
1
16
  # v0.2.3 2016-05-11
2
17
 
3
18
  ### Added
@@ -11,7 +26,6 @@
11
26
 
12
27
  [Compare v0.2.2...v0.2.3](https://github.com/dryrb/dry-logic/compare/v0.2.2...v0.2.3)
13
28
 
14
-
15
29
  # v0.2.2 2016-03-30
16
30
 
17
31
  ### Added
data/README.md CHANGED
@@ -28,20 +28,27 @@ require 'dry/logic/predicates'
28
28
 
29
29
  include Dry::Logic
30
30
 
31
- user_present = Rule::Key.new(Predicates[:key?], name: :user)
31
+ user_present = Rule::Key.new(Predicates[:filled?], name: :user)
32
32
 
33
- has_min_age = Rule::Key.new(
34
- Predicates[:key?]) & Rule::Value.new(:age, Predicates[:gt?].curry(18),
35
- name: :age
36
- )
33
+ has_min_age = Rule::Key.new(Predicates[:int?], name: [:user, :age])
34
+ & Rule::Key.new(Predicates[:gt?].curry(18), name: [:user, :age])
37
35
 
38
36
  user_rule = user_present & has_min_age
39
37
 
40
38
  user_rule.(user: { age: 19 })
41
- # #<Dry::Logic::Result::Value success?=true input=19 rule=#<Dry::Logic::Rule::Value name=:age predicate=#<Dry::Logic::Predicate id=:gt?>>>
39
+ # #<Dry::Logic::Result::Named success?=true input={:user=>{:age=>19}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:gt? args=[18, 19]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
42
40
 
43
41
  user_rule.(user: { age: 18 })
44
- # #<Dry::Logic::Result::Value success?=false input=18 rule=#<Dry::Logic::Rule::Value name=:age predicate=#<Dry::Logic::Predicate id=:gt?>>>
42
+ # #<Dry::Logic::Result::Named success?=false input={:user=>{:age=>18}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:gt? args=[18, 18]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
43
+
44
+ user_rule.(user: { age: 'seventeen' }).inspect
45
+ #<Dry::Logic::Result::Named success?=false input={:user=>{:age=>"seventeen"}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:int? args=["seventeen"]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
46
+
47
+ user_rule.(user: { }).inspect
48
+ #<Dry::Logic::Result::Named success?=false input={:user=>{}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:filled? args=[{}]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user]>, :name=>:user}>>
49
+
50
+ puts user_rule.({}).inspect
51
+ #<Dry::Logic::Result::Named success?=false input={} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:filled? args=[nil]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user]>, :name=>:user}>>
45
52
  ```
46
53
 
47
54
  ## License
data/examples/basic.rb CHANGED
@@ -3,11 +3,17 @@ require 'dry/logic/predicates'
3
3
 
4
4
  include Dry::Logic
5
5
 
6
- user_present = Rule::Key.new(:user, Predicates[:key?])
7
- has_min_age = Rule::Key.new(:age, Predicates[:key?]) & Rule::Value.new(:age, Predicates[:gt?].curry(18))
6
+ user_present = Rule::Key.new(Predicates[:filled?], name: :user)
7
+ has_min_age = Rule::Key.new(Predicates[:int?], name: [:user, :age]) & Rule::Key.new(Predicates[:gt?].curry(18), name: [:user, :age])
8
8
 
9
9
  user_rule = user_present & has_min_age
10
10
 
11
11
  puts user_rule.(user: { age: 19 }).inspect
12
12
 
13
13
  puts user_rule.(user: { age: 18 }).inspect
14
+
15
+ puts user_rule.(user: { age: 'seventeen' }).inspect
16
+
17
+ puts user_rule.(user: { }).inspect
18
+
19
+ puts user_rule.({}).inspect
@@ -3,33 +3,98 @@ module Dry
3
3
  def self.Predicate(block)
4
4
  case block
5
5
  when Method then Predicate.new(block.name, &block)
6
+ when UnboundMethod then Predicate.new(block.name, fn: block)
6
7
  else raise ArgumentError, 'predicate needs an :id'
7
8
  end
8
9
  end
9
10
 
10
11
  class Predicate
12
+ Undefined = Class.new {
13
+ def inspect
14
+ "undefined"
15
+ end
16
+ alias_method :to_s, :inspect
17
+ }.new.freeze
18
+
11
19
  include Dry::Equalizer(:id, :args)
12
20
 
13
- attr_reader :id, :args, :fn
21
+ attr_reader :id, :args, :fn, :arity
22
+
23
+ class Curried < Predicate
24
+ def call(*args)
25
+ all_args = @args + args
14
26
 
15
- def initialize(id, *args, &block)
27
+ if all_args.size == arity
28
+ super(*args)
29
+ else
30
+ curry(*args)
31
+ end
32
+ end
33
+ alias_method :[], :call
34
+ end
35
+
36
+ def initialize(id, args: [], fn: nil, arity: nil, &block)
16
37
  @id = id
17
- @fn = block
18
38
  @args = args
39
+ @fn = fn || block
40
+ @arity = arity || @fn.arity
19
41
  end
20
42
 
43
+ #as long as we keep track of the args, we don't actually need to curry the proc...
44
+ #if we never curry the proc then fn.arity & fn.parameters stay intact
45
+ def curry(*args)
46
+ if args.size > 0
47
+ all_args = @args + args
48
+ size = all_args.size
49
+
50
+ if size <= arity
51
+ Curried.new(id, args: all_args, fn: fn, arity: arity)
52
+ else
53
+ raise_arity_error(all_args.size)
54
+ end
55
+ else
56
+ self
57
+ end
58
+ end
59
+
60
+ # @api public
61
+ def bind(object)
62
+ self.class.new(id, *args, &fn.bind(object))
63
+ end
64
+
65
+ #enables a rule to call with its params & have them ignored if the
66
+ #predicate doesn't need them.
67
+ #if @args.size == arity then we should ignore called args
21
68
  def call(*args)
22
- fn.(*args)
69
+ all_args = @args + args
70
+ size = all_args.size
71
+
72
+ if size == arity
73
+ fn.(*all_args)
74
+ else
75
+ raise_arity_error(size)
76
+ end
23
77
  end
78
+ alias_method :[], :call
24
79
 
25
- def curry(*args)
26
- self.class.new(id, *args, &fn.curry.(*args))
80
+ def parameters
81
+ fn.parameters
27
82
  end
28
83
 
29
84
  def to_ast
30
- [:predicate, [id, args]]
85
+ [:predicate, [id, args_with_names]]
31
86
  end
32
87
  alias_method :to_a, :to_ast
88
+
89
+ private
90
+
91
+ def args_with_names
92
+ parameters.map(&:last).zip(args + Array.new(arity - args.size, Undefined))
93
+ end
94
+
95
+ def raise_arity_error(args_size)
96
+ raise ArgumentError, "wrong number of arguments (#{args_size} for #{arity})"
97
+ end
33
98
  end
34
99
  end
35
100
  end
@@ -15,6 +15,10 @@ module Dry
15
15
  other.import(self)
16
16
  end
17
17
 
18
+ predicate(:type?) do |type, input|
19
+ input.kind_of?(type)
20
+ end
21
+
18
22
  predicate(:none?) do |input|
19
23
  input.nil?
20
24
  end
@@ -3,7 +3,7 @@ module Dry
3
3
  class Result::Each < Result::Multi
4
4
  def to_ast
5
5
  failed_rules = failures.map { |idx, el| [:el, [idx, el.to_ast]] }
6
- [:result, [rule.evaluate(input), [:each, failed_rules]]]
6
+ [:result, [input, [:each, failed_rules]]]
7
7
  end
8
8
 
9
9
  def success?
@@ -3,7 +3,7 @@ module Dry
3
3
  class Result::Set < Result::Multi
4
4
  def to_ast
5
5
  failed_rules = failures.map { |el| el.to_ast }
6
- [:result, [rule.evaluate(input), [:set, failed_rules]]]
6
+ [:result, [input, [:set, failed_rules]]]
7
7
  end
8
8
  end
9
9
  end
@@ -5,9 +5,13 @@ module Dry
5
5
  if response.respond_to?(:to_ast)
6
6
  response.to_ast
7
7
  else
8
- [:result, [rule.evaluate(input), rule.to_ast]]
8
+ [:result, [input, rule.to_ast]]
9
9
  end
10
10
  end
11
+
12
+ def input
13
+ rule.input != Predicate::Undefined ? rule.input : super
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -7,6 +7,13 @@ module Dry
7
7
 
8
8
  attr_reader :options
9
9
 
10
+ def self.method_added(meth)
11
+ super
12
+ if meth == :call
13
+ alias_method :[], :call
14
+ end
15
+ end
16
+
10
17
  def initialize(predicate, options = {})
11
18
  @predicate = predicate
12
19
  @options = options
@@ -49,7 +56,11 @@ module Dry
49
56
  end
50
57
 
51
58
  def curry(*args)
52
- self.class.new(predicate.curry(*args), options)
59
+ if arity > 0
60
+ new(predicate.curry(*args))
61
+ else
62
+ self
63
+ end
53
64
  end
54
65
 
55
66
  def each?
@@ -1,8 +1,8 @@
1
1
  module Dry
2
2
  module Logic
3
3
  class Rule::Attr < Rule::Key
4
- def self.evaluator(options)
5
- Evaluator::Attr.new(options.fetch(:name))
4
+ def self.evaluator(name)
5
+ Evaluator::Attr.new(name)
6
6
  end
7
7
 
8
8
  def type
@@ -21,7 +21,7 @@ module Dry
21
21
  def call(input)
22
22
  args = evaluator[input].reverse
23
23
  *head, tail = args
24
- Logic.Result(predicate.curry(*head).(tail), head.size > 0 ? curry(*head) : self, input)
24
+ Logic.Result(predicate.curry(*head).(tail), curry(*args), input)
25
25
  end
26
26
 
27
27
  def evaluate(input)
@@ -10,6 +10,18 @@ module Dry
10
10
  @right = right
11
11
  end
12
12
 
13
+ def arity
14
+ -1
15
+ end
16
+
17
+ def input
18
+ Predicate::Undefined
19
+ end
20
+
21
+ def curry(*args)
22
+ self.class.new(left.curry(*args), right.curry(*args))
23
+ end
24
+
13
25
  def name
14
26
  :"#{left.name}_#{type}_#{right.name}"
15
27
  end
@@ -71,10 +83,6 @@ module Dry
71
83
  Logic.Result(left.(input).success? ^ right.(input).success?, self, input)
72
84
  end
73
85
 
74
- def evaluate(input)
75
- [left.evaluate(input), right.evaluate(input)]
76
- end
77
-
78
86
  def type
79
87
  :xor
80
88
  end
@@ -7,11 +7,12 @@ module Dry
7
7
 
8
8
  def self.new(predicate, options)
9
9
  name = options.fetch(:name)
10
- super(predicate, evaluator: evaluator(options), name: name)
10
+ eval = options.fetch(:evaluator, evaluator(name))
11
+ super(predicate, evaluator: eval, name: name)
11
12
  end
12
13
 
13
- def self.evaluator(options)
14
- Evaluator::Key.new(options.fetch(:name))
14
+ def self.evaluator(name)
15
+ Evaluator::Key.new(name)
15
16
  end
16
17
 
17
18
  def initialize(predicate, options)
@@ -7,10 +7,18 @@ module Dry
7
7
  :set
8
8
  end
9
9
 
10
+ def arity
11
+ -1
12
+ end
13
+
10
14
  def apply(input)
11
15
  rules.map { |rule| rule.(input) }
12
16
  end
13
17
 
18
+ def curry(*args)
19
+ new(rules.map { |r| r.curry(*args) })
20
+ end
21
+
14
22
  def at(*args)
15
23
  new(rules.values_at(*args))
16
24
  end
@@ -5,12 +5,35 @@ module Dry
5
5
  :val
6
6
  end
7
7
 
8
+ def nulary?
9
+ arity == 0
10
+ end
11
+
12
+ def arity
13
+ @arity ||= predicate.arity
14
+ end
15
+
16
+ def args
17
+ @args ||= predicate.args
18
+ end
19
+
20
+ def input
21
+ predicate.args.last
22
+ end
23
+
8
24
  def call(input)
9
- Logic.Result(apply(input), self, input)
25
+ if nulary?
26
+ Logic.Result(predicate.(), self, input)
27
+ else
28
+ evaled = evaluate(input)
29
+ result = apply(evaled)
30
+ rule = result == true ? self : curry(evaled)
31
+ Logic.Result(result, rule, input)
32
+ end
10
33
  end
11
34
 
12
35
  def apply(input)
13
- predicate.(evaluate(input))
36
+ predicate.(input)
14
37
  end
15
38
 
16
39
  def evaluate(input)
@@ -50,8 +50,15 @@ module Dry
50
50
  end
51
51
 
52
52
  def visit_predicate(node)
53
- name, args = node
54
- predicates[name].curry(*args)
53
+ name, params = node
54
+ predicate = predicates[name]
55
+
56
+ if params.size > 1
57
+ args = params.map(&:last).reject { |val| val == Predicate::Undefined }
58
+ predicate.curry(*args)
59
+ else
60
+ predicate
61
+ end
55
62
  end
56
63
 
57
64
  def visit_and(node)
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Logic
3
- VERSION = '0.2.3'.freeze
3
+ VERSION = '0.3.0'.freeze
4
4
  end
5
5
  end
@@ -30,8 +30,8 @@ RSpec.shared_examples 'a passing predicate' do
30
30
  let(:predicate) { Dry::Logic::Predicates[predicate_name] }
31
31
 
32
32
  it do
33
- arguments_list.each do |(left, right)|
34
- expect(predicate.call(left, right)).to be(true)
33
+ arguments_list.each do |args|
34
+ expect(predicate.call(*args)).to be(true)
35
35
  end
36
36
  end
37
37
  end
@@ -40,8 +40,8 @@ RSpec.shared_examples 'a failing predicate' do
40
40
  let(:predicate) { Dry::Logic::Predicates[predicate_name] }
41
41
 
42
42
  it do
43
- arguments_list.each do |(left, right)|
44
- expect(predicate.call(left, right)).to be(false)
43
+ arguments_list.each do |args|
44
+ expect(predicate.call(*args)).to be(false)
45
45
  end
46
46
  end
47
47
  end
@@ -1,6 +1,35 @@
1
1
  require 'dry/logic/predicate'
2
2
 
3
- RSpec.describe Dry::Logic::Predicate do
3
+ RSpec.describe Predicate do
4
+ describe '.new' do
5
+ it 'can be initialized with empty args' do
6
+ predicate = Predicate.new(:id) { |v| v.is_a?(Integer) }
7
+
8
+ expect(predicate.to_ast).to eql([:predicate, [:id, [[:v, Predicate::Undefined]]]])
9
+ end
10
+
11
+ it 'can be initialized with args' do
12
+ predicate = Predicate.new(:id, args: [1]) { |v| v.is_a?(Integer) }
13
+
14
+ expect(predicate.to_ast).to eql([:predicate, [:id, [[:v, 1]]]])
15
+ end
16
+
17
+ it 'can be initialized with fn as the last arg' do
18
+ predicate = Predicate.new(:id, args: [1], fn: -> v { v.is_a?(Integer) })
19
+
20
+ expect(predicate.to_ast).to eql([:predicate, [:id, [[:v, 1]]]])
21
+ end
22
+ end
23
+
24
+ describe '#bind' do
25
+ it 'returns a predicate bound to a specific object' do
26
+ fn = String.instance_method(:empty?)
27
+
28
+ expect(Dry::Logic.Predicate(fn).bind("").()).to be(true)
29
+ expect(Dry::Logic.Predicate(fn).bind("foo").()).to be(false)
30
+ end
31
+ end
32
+
4
33
  describe '#call' do
5
34
  it 'returns result of the predicate function' do
6
35
  is_empty = Dry::Logic::Predicate.new(:is_empty) { |str| str.empty? }
@@ -9,6 +38,52 @@ RSpec.describe Dry::Logic::Predicate do
9
38
 
10
39
  expect(is_empty.('filled')).to be(false)
11
40
  end
41
+
42
+ it "raises argument error when incorrect number of args provided" do
43
+ min_age = Dry::Logic::Predicate.new(:min_age) { |age, input| input >= age }
44
+
45
+ expect { min_age.curry(10, 12, 14) }.to raise_error(ArgumentError)
46
+ expect { min_age.(18, 19, 20, 30) }.to raise_error(ArgumentError)
47
+ expect { min_age.curry(18).(19, 20) }.to raise_error(ArgumentError)
48
+ end
49
+
50
+ it "predicates should work without any args" do
51
+ is_empty = Dry::Logic::Predicate.new(:is_empty) { true }
52
+
53
+ expect(is_empty.()).to be(true)
54
+ end
55
+ end
56
+
57
+ describe '#arity' do
58
+ it 'returns arity of the predicate function' do
59
+ is_equal = Dry::Logic::Predicate.new(:is_equal) { |left, right| left == right }
60
+
61
+ expect(is_equal.arity).to eql(2)
62
+ end
63
+ end
64
+
65
+ describe '#parameters' do
66
+ it 'returns arity of the predicate function' do
67
+ is_equal = Dry::Logic::Predicate.new(:is_equal) { |left, right| left == right }
68
+
69
+ expect(is_equal.parameters).to eql([[:opt, :left], [:opt, :right]])
70
+ end
71
+ end
72
+
73
+ describe '#arity' do
74
+ it 'returns arity of the predicate function' do
75
+ is_equal = Dry::Logic::Predicate.new(:is_equal) { |left, right| left == right }
76
+
77
+ expect(is_equal.arity).to eql(2)
78
+ end
79
+ end
80
+
81
+ describe '#parameters' do
82
+ it 'returns arity of the predicate function' do
83
+ is_equal = Dry::Logic::Predicate.new(:is_equal) { |left, right| left == right }
84
+
85
+ expect(is_equal.parameters).to eql([[:opt, :left], [:opt, :right]])
86
+ end
12
87
  end
13
88
 
14
89
  describe '#curry' do
@@ -23,5 +98,18 @@ RSpec.describe Dry::Logic::Predicate do
23
98
  expect(min_age_18.(19)).to be(true)
24
99
  expect(min_age_18.(17)).to be(false)
25
100
  end
101
+
102
+ it 'can curry again & again' do
103
+ min_age = Dry::Logic::Predicate.new(:min_age) { |age, input| input >= age }
104
+
105
+ min_age_18 = min_age.curry(18)
106
+
107
+ expect(min_age_18.args).to eql([18])
108
+
109
+ actual_age_19 = min_age_18.curry(19)
110
+
111
+ expect(actual_age_19.()).to be(true)
112
+ expect(actual_age_19.args).to eql([18,19])
113
+ end
26
114
  end
27
115
  end
@@ -0,0 +1,41 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#array?' do
5
+ let(:predicate_name) { :array? }
6
+
7
+ context 'when value is an array' do
8
+ let(:arguments_list) do
9
+ [
10
+ [ [] ],
11
+ [ ['other', 'array'] ],
12
+ [ [123, 'really', :blah] ],
13
+ [ Array.new ],
14
+ [ [nil] ],
15
+ [ [false] ],
16
+ [ [true] ]
17
+ ]
18
+ end
19
+
20
+ it_behaves_like 'a passing predicate'
21
+ end
22
+
23
+ context 'when value is not an array' do
24
+ let(:arguments_list) do
25
+ [
26
+ [''],
27
+ [{}],
28
+ [nil],
29
+ [:symbol],
30
+ [String],
31
+ [1],
32
+ [1.0],
33
+ [true],
34
+ [Hash.new]
35
+ ]
36
+ end
37
+
38
+ it_behaves_like 'a failing predicate'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#type?' do
5
+ let(:predicate_name) { :type? }
6
+
7
+ context 'when value has a correct type' do
8
+ let(:arguments_list) do
9
+ [[TrueClass, true]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not true' do
16
+ let(:arguments_list) do
17
+ [
18
+ [TrueClass, false],
19
+ [TrueClass, ''],
20
+ [TrueClass, []],
21
+ [TrueClass, {}],
22
+ [TrueClass, nil],
23
+ [TrueClass, :symbol],
24
+ [TrueClass, String],
25
+ [TrueClass, 1],
26
+ [TrueClass, 0],
27
+ [TrueClass, 'true'],
28
+ [TrueClass, 'false']
29
+ ]
30
+ end
31
+
32
+ it_behaves_like 'a failing predicate'
33
+ end
34
+ end
35
+ end
@@ -13,7 +13,7 @@ RSpec.describe Rule::Check do
13
13
 
14
14
  expect(rule.(num: 1).to_ast).to eql(
15
15
  [:input, [:compare, [
16
- :result, [1, [:check, [:compare, [:predicate, [:eql?, [1]]]]]]]]
16
+ :result, [1, [:check, [:compare, [:predicate, [:eql?, [[:left, 1], [:right, 1]]]]]]]]]
17
17
  ]
18
18
  )
19
19
  end
@@ -29,12 +29,13 @@ RSpec.describe Rule::Check do
29
29
  expect(rule.(nums: { left: 1, right: 2 })).to be_failure
30
30
  end
31
31
 
32
+ #check rules reverse the order of params to enable cases like `left.gt(right)` to work
32
33
  it 'curries args properly' do
33
34
  result = rule.(nums: { left: 1, right: 2 })
34
35
 
35
36
  expect(result.to_ast).to eql([
36
37
  :input, [:compare, [
37
- :result, [1, [:check, [:compare, [:predicate, [:eql?, [2]]]]]]]
38
+ :result, [1, [:check, [:compare, [:predicate, [:eql?, [[:left, 2], [:right, 1]]]]]]]]
38
39
  ]
39
40
  ])
40
41
  end
@@ -21,8 +21,8 @@ RSpec.describe Dry::Logic::Rule::Each do
21
21
  expect(address_rule.([nil, nil]).to_ast).to eql([
22
22
  :result, [[nil, nil], [
23
23
  :each, [
24
- [:el, [0, [:result, [nil, [:val, [:predicate, [:str?, []]]]]]]],
25
- [:el, [1, [:result, [nil, [:val, [:predicate, [:str?, []]]]]]]]
24
+ [:el, [0, [:result, [nil, [:val, [:predicate, [:str?, [[:input, nil]]]]]]]]],
25
+ [:el, [1, [:result, [nil, [:val, [:predicate, [:str?, [[:input, nil]]]]]]]]]
26
26
  ]
27
27
  ]]
28
28
  ])
@@ -56,7 +56,7 @@ RSpec.describe Rule::Key do
56
56
  :address,
57
57
  [:result, [
58
58
  { city: "NYC" },
59
- [:set, [[:result, [{ city: 'NYC' }, [:val, [:predicate, [:key?, [:zipcode]]]]]]]]
59
+ [:set, [[:result, [{ city: 'NYC' }, [:val, [:predicate, [:key?, [[:name, :zipcode], [:input, {:city=>"NYC"}]]]]]]]]]
60
60
  ]]
61
61
  ]
62
62
  ])
@@ -93,8 +93,8 @@ RSpec.describe Rule::Key do
93
93
  :result, [
94
94
  [1, '3', 3],
95
95
  [:each, [
96
- [:el, [0, [:result, [1, [:val, [:predicate, [:str?, []]]]]]]],
97
- [:el, [2, [:result, [3, [:val, [:predicate, [:str?, []]]]]]]]
96
+ [:el, [0, [:result, [1, [:val, [:predicate, [:str?, [[:input, 1]]]]]]]]],
97
+ [:el, [2, [:result, [3, [:val, [:predicate, [:str?, [[:input, 3]]]]]]]]]
98
98
  ]]
99
99
  ]
100
100
  ]
@@ -21,8 +21,8 @@ RSpec.describe Dry::Logic::Rule::Set do
21
21
  it 'returns an array representation' do
22
22
  expect(rule.to_ast).to eql([
23
23
  :set, [
24
- [:val, [:predicate, [:str?, []]]],
25
- [:val, [:predicate, [:min_size?, [6]]]]
24
+ [:val, [:predicate, [:str?, [[:input, Predicate::Undefined]]]]],
25
+ [:val, [:predicate, [:min_size?, [[:num, 6], [:input, Predicate::Undefined]]]]]
26
26
  ]
27
27
  ])
28
28
  end
@@ -15,11 +15,31 @@ RSpec.describe Dry::Logic::Rule::Value do
15
15
  expect(is_string.('1')).to be_success
16
16
  end
17
17
 
18
+ context 'with a composite rule' do
19
+ subject(:rule) { Dry::Logic::Rule::Value.new(is_nil | is_string) }
20
+
21
+ it 'returns a success for valid input' do
22
+ expect(rule.(nil)).to be_success
23
+ expect(rule.('foo')).to be_success
24
+ end
25
+
26
+ it 'returns a failure for invalid input' do
27
+ expect(rule.(312)).to be_failure
28
+ end
29
+
30
+ it 'returns a failure result with curried args' do
31
+ expect(rule.(312).to_ast).to eql(
32
+ [:result, [312, [:val, [:predicate, [:str?, [[:input, 312]]]]]]]
33
+ )
34
+ end
35
+ end
36
+
18
37
  context 'with a custom predicate' do
19
38
  subject(:rule) { Dry::Logic::Rule::Value.new(predicate) }
20
39
 
21
- let(:response) { double(success?: true) }
22
- let(:predicate) { -> input { Result.new(response, double, input) } }
40
+ let(:response) { double("response", success?: true) }
41
+ let(:predicate) { double("predicate", arity: 1, curry: curried, call: Result.new(response, double("rule"), test: true)) }
42
+ let(:curried) { double("curried", arity: 1, call: Result.new(response, double("rule"), test: true)) }
23
43
 
24
44
  let(:result) { rule.(test: true) }
25
45
 
@@ -39,6 +59,17 @@ RSpec.describe Dry::Logic::Rule::Value do
39
59
  it 'has no name by default' do
40
60
  expect(result.name).to be(nil)
41
61
  end
62
+
63
+ context "works with predicates.arity == 0" do
64
+ subject(:rule) { Dry::Logic::Rule::Value.new(predicate) }
65
+
66
+ let(:predicate) { Dry::Logic::Predicate.new(:without_args) { true } }
67
+ let(:result) { rule.('sutin') }
68
+
69
+ it "calls its predicate without any args" do
70
+ expect(result).to be_success
71
+ end
72
+ end
42
73
  end
43
74
  end
44
75
 
@@ -25,7 +25,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
25
25
  let(:each_rule) { Rule::Each.new(val_rule) }
26
26
 
27
27
  it 'compiles key rules' do
28
- ast = [[:key, [:email, [:predicate, [:filled?, []]]]]]
28
+ ast = [[:key, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
29
29
 
30
30
  rules = compiler.(ast)
31
31
 
@@ -33,7 +33,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
33
33
  end
34
34
 
35
35
  it 'compiles attr rules' do
36
- ast = [[:attr, [:email, [:predicate, [:filled?, []]]]]]
36
+ ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
37
37
 
38
38
  rules = compiler.(ast)
39
39
 
@@ -41,7 +41,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
41
41
  end
42
42
 
43
43
  it 'compiles check rules' do
44
- ast = [[:check, [:email, [:predicate, [:filled?, []]]]]]
44
+ ast = [[:check, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
45
45
 
46
46
  rules = compiler.(ast)
47
47
 
@@ -49,7 +49,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
49
49
  end
50
50
 
51
51
  it 'compiles attr rules' do
52
- ast = [[:attr, [:email, [:predicate, [:attr?, predicate]]]]]
52
+ ast = [[:attr, [:email, [:predicate, [:attr?, [[:name, :email]]]]]]]
53
53
 
54
54
  rules = compiler.(ast)
55
55
 
@@ -57,7 +57,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
57
57
  end
58
58
 
59
59
  it 'compiles negated rules' do
60
- ast = [[:not, [:key, [:email, [:predicate, [:filled?, []]]]]]]
60
+ ast = [[:not, [:key, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]]
61
61
 
62
62
  rules = compiler.(ast)
63
63
 
@@ -68,8 +68,8 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
68
68
  ast = [
69
69
  [
70
70
  :and, [
71
- [:key, [:email, [:predicate, [:key?, []]]]],
72
- [:val, [:predicate, [:filled?, []]]]
71
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
72
+ [:val, [:predicate, [:filled?, [[:input, nil]]]]]
73
73
  ]
74
74
  ]
75
75
  ]
@@ -83,8 +83,8 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
83
83
  ast = [
84
84
  [
85
85
  :or, [
86
- [:key, [:email, [:predicate, [:key?, []]]]],
87
- [:val, [:predicate, [:filled?, []]]]
86
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
87
+ [:val, [:predicate, [:filled?, [[:input, nil]]]]]
88
88
  ]
89
89
  ]
90
90
  ]
@@ -98,8 +98,8 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
98
98
  ast = [
99
99
  [
100
100
  :xor, [
101
- [:key, [:email, [:predicate, [:key?, []]]]],
102
- [:val, [:predicate, [:filled?, []]]]
101
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
102
+ [:val, [:predicate, [:filled?, [[:input, nil]]]]]
103
103
  ]
104
104
  ]
105
105
  ]
@@ -110,7 +110,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
110
110
  end
111
111
 
112
112
  it 'compiles set rules' do
113
- ast = [[:set, [[:val, [:predicate, [:filled?, []]]]]]]
113
+ ast = [[:set, [[:val, [:predicate, [:filled?, [[:input, nil]]]]]]]]
114
114
 
115
115
  rules = compiler.(ast)
116
116
 
@@ -118,7 +118,7 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
118
118
  end
119
119
 
120
120
  it 'compiles each rules' do
121
- ast = [[:each, [:val, [:predicate, [:filled?, []]]]]]
121
+ ast = [[:each, [:val, [:predicate, [:filled?, [[:input, nil]]]]]]]
122
122
 
123
123
  rules = compiler.(ast)
124
124
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-logic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-11 00:00:00.000000000 Z
11
+ date: 2016-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-container
@@ -131,6 +131,7 @@ files:
131
131
  - spec/shared/predicates.rb
132
132
  - spec/spec_helper.rb
133
133
  - spec/unit/predicate_spec.rb
134
+ - spec/unit/predicates/array_spec.rb
134
135
  - spec/unit/predicates/attr_spec.rb
135
136
  - spec/unit/predicates/bool_spec.rb
136
137
  - spec/unit/predicates/date_spec.rb
@@ -163,6 +164,7 @@ files:
163
164
  - spec/unit/predicates/str_spec.rb
164
165
  - spec/unit/predicates/time_spec.rb
165
166
  - spec/unit/predicates/true_spec.rb
167
+ - spec/unit/predicates/type_spec.rb
166
168
  - spec/unit/rule/attr_spec.rb
167
169
  - spec/unit/rule/check_spec.rb
168
170
  - spec/unit/rule/conjunction_spec.rb
@@ -194,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
196
  version: '0'
195
197
  requirements: []
196
198
  rubyforge_project:
197
- rubygems_version: 2.4.8
199
+ rubygems_version: 2.5.1
198
200
  signing_key:
199
201
  specification_version: 4
200
202
  summary: Predicate logic with rule composition
@@ -202,6 +204,7 @@ test_files:
202
204
  - spec/shared/predicates.rb
203
205
  - spec/spec_helper.rb
204
206
  - spec/unit/predicate_spec.rb
207
+ - spec/unit/predicates/array_spec.rb
205
208
  - spec/unit/predicates/attr_spec.rb
206
209
  - spec/unit/predicates/bool_spec.rb
207
210
  - spec/unit/predicates/date_spec.rb
@@ -234,6 +237,7 @@ test_files:
234
237
  - spec/unit/predicates/str_spec.rb
235
238
  - spec/unit/predicates/time_spec.rb
236
239
  - spec/unit/predicates/true_spec.rb
240
+ - spec/unit/predicates/type_spec.rb
237
241
  - spec/unit/rule/attr_spec.rb
238
242
  - spec/unit/rule/check_spec.rb
239
243
  - spec/unit/rule/conjunction_spec.rb
@@ -245,3 +249,4 @@ test_files:
245
249
  - spec/unit/rule/set_spec.rb
246
250
  - spec/unit/rule/value_spec.rb
247
251
  - spec/unit/rule_compiler_spec.rb
252
+ has_rdoc: