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 +4 -4
- data/CHANGELOG.md +15 -1
- data/README.md +14 -7
- data/examples/basic.rb +8 -2
- data/lib/dry/logic/predicate.rb +72 -7
- data/lib/dry/logic/predicates.rb +4 -0
- data/lib/dry/logic/result/each.rb +1 -1
- data/lib/dry/logic/result/set.rb +1 -1
- data/lib/dry/logic/result/value.rb +5 -1
- data/lib/dry/logic/rule.rb +12 -1
- data/lib/dry/logic/rule/attr.rb +2 -2
- data/lib/dry/logic/rule/check.rb +1 -1
- data/lib/dry/logic/rule/composite.rb +12 -4
- data/lib/dry/logic/rule/key.rb +4 -3
- data/lib/dry/logic/rule/set.rb +8 -0
- data/lib/dry/logic/rule/value.rb +25 -2
- data/lib/dry/logic/rule_compiler.rb +9 -2
- data/lib/dry/logic/version.rb +1 -1
- data/spec/shared/predicates.rb +4 -4
- data/spec/unit/predicate_spec.rb +89 -1
- data/spec/unit/predicates/array_spec.rb +41 -0
- data/spec/unit/predicates/type_spec.rb +35 -0
- data/spec/unit/rule/check_spec.rb +3 -2
- data/spec/unit/rule/each_spec.rb +2 -2
- data/spec/unit/rule/key_spec.rb +3 -3
- data/spec/unit/rule/set_spec.rb +2 -2
- data/spec/unit/rule/value_spec.rb +33 -2
- data/spec/unit/rule_compiler_spec.rb +13 -13
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ea86d568127098e2777868066c2026dbc380eb9
|
4
|
+
data.tar.gz: 5553735b89ab2ef6a1c5bdd3f24874bab46c2581
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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[:
|
31
|
+
user_present = Rule::Key.new(Predicates[:filled?], name: :user)
|
32
32
|
|
33
|
-
has_min_age = Rule::Key.new(
|
34
|
-
|
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::
|
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::
|
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(
|
7
|
-
has_min_age = Rule::Key.new(:
|
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
|
data/lib/dry/logic/predicate.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
26
|
-
|
80
|
+
def parameters
|
81
|
+
fn.parameters
|
27
82
|
end
|
28
83
|
|
29
84
|
def to_ast
|
30
|
-
[:predicate, [id,
|
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
|
data/lib/dry/logic/predicates.rb
CHANGED
data/lib/dry/logic/result/set.rb
CHANGED
@@ -5,9 +5,13 @@ module Dry
|
|
5
5
|
if response.respond_to?(:to_ast)
|
6
6
|
response.to_ast
|
7
7
|
else
|
8
|
-
[:result, [
|
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
|
data/lib/dry/logic/rule.rb
CHANGED
@@ -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
|
-
|
59
|
+
if arity > 0
|
60
|
+
new(predicate.curry(*args))
|
61
|
+
else
|
62
|
+
self
|
63
|
+
end
|
53
64
|
end
|
54
65
|
|
55
66
|
def each?
|
data/lib/dry/logic/rule/attr.rb
CHANGED
data/lib/dry/logic/rule/check.rb
CHANGED
@@ -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),
|
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
|
data/lib/dry/logic/rule/key.rb
CHANGED
@@ -7,11 +7,12 @@ module Dry
|
|
7
7
|
|
8
8
|
def self.new(predicate, options)
|
9
9
|
name = options.fetch(:name)
|
10
|
-
|
10
|
+
eval = options.fetch(:evaluator, evaluator(name))
|
11
|
+
super(predicate, evaluator: eval, name: name)
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.evaluator(
|
14
|
-
Evaluator::Key.new(
|
14
|
+
def self.evaluator(name)
|
15
|
+
Evaluator::Key.new(name)
|
15
16
|
end
|
16
17
|
|
17
18
|
def initialize(predicate, options)
|
data/lib/dry/logic/rule/set.rb
CHANGED
@@ -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
|
data/lib/dry/logic/rule/value.rb
CHANGED
@@ -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
|
-
|
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.(
|
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,
|
54
|
-
predicates[name]
|
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)
|
data/lib/dry/logic/version.rb
CHANGED
data/spec/shared/predicates.rb
CHANGED
@@ -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 |
|
34
|
-
expect(predicate.call(
|
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 |
|
44
|
-
expect(predicate.call(
|
43
|
+
arguments_list.each do |args|
|
44
|
+
expect(predicate.call(*args)).to be(false)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/spec/unit/predicate_spec.rb
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
require 'dry/logic/predicate'
|
2
2
|
|
3
|
-
RSpec.describe
|
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
|
data/spec/unit/rule/each_spec.rb
CHANGED
@@ -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
|
])
|
data/spec/unit/rule/key_spec.rb
CHANGED
@@ -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
|
]
|
data/spec/unit/rule/set_spec.rb
CHANGED
@@ -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) {
|
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?,
|
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.
|
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-
|
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.
|
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:
|