dry-logic 0.2.3 → 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.
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: