dry-logic 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -5
  3. data/CHANGELOG.md +28 -0
  4. data/Gemfile +7 -4
  5. data/dry-logic.gemspec +1 -0
  6. data/lib/dry/logic.rb +2 -3
  7. data/lib/dry/logic/appliable.rb +33 -0
  8. data/lib/dry/logic/evaluator.rb +2 -0
  9. data/lib/dry/logic/operations.rb +13 -0
  10. data/lib/dry/logic/operations/abstract.rb +44 -0
  11. data/lib/dry/logic/operations/and.rb +35 -0
  12. data/lib/dry/logic/operations/attr.rb +17 -0
  13. data/lib/dry/logic/operations/binary.rb +26 -0
  14. data/lib/dry/logic/operations/check.rb +52 -0
  15. data/lib/dry/logic/operations/each.rb +32 -0
  16. data/lib/dry/logic/operations/implication.rb +37 -0
  17. data/lib/dry/logic/operations/key.rb +66 -0
  18. data/lib/dry/logic/operations/negation.rb +18 -0
  19. data/lib/dry/logic/operations/or.rb +35 -0
  20. data/lib/dry/logic/operations/set.rb +35 -0
  21. data/lib/dry/logic/operations/unary.rb +24 -0
  22. data/lib/dry/logic/operations/xor.rb +27 -0
  23. data/lib/dry/logic/operators.rb +25 -0
  24. data/lib/dry/logic/predicates.rb +143 -136
  25. data/lib/dry/logic/result.rb +76 -33
  26. data/lib/dry/logic/rule.rb +62 -46
  27. data/lib/dry/logic/rule/predicate.rb +28 -0
  28. data/lib/dry/logic/rule_compiler.rb +16 -17
  29. data/lib/dry/logic/version.rb +1 -1
  30. data/spec/integration/result_spec.rb +59 -0
  31. data/spec/integration/rule_spec.rb +53 -0
  32. data/spec/shared/predicates.rb +6 -0
  33. data/spec/shared/rule.rb +67 -0
  34. data/spec/spec_helper.rb +10 -3
  35. data/spec/support/mutant.rb +9 -0
  36. data/spec/unit/operations/and_spec.rb +64 -0
  37. data/spec/unit/operations/attr_spec.rb +27 -0
  38. data/spec/unit/operations/check_spec.rb +49 -0
  39. data/spec/unit/operations/each_spec.rb +47 -0
  40. data/spec/unit/operations/implication_spec.rb +30 -0
  41. data/spec/unit/operations/key_spec.rb +119 -0
  42. data/spec/unit/operations/negation_spec.rb +40 -0
  43. data/spec/unit/operations/or_spec.rb +73 -0
  44. data/spec/unit/operations/set_spec.rb +41 -0
  45. data/spec/unit/operations/xor_spec.rb +61 -0
  46. data/spec/unit/predicates_spec.rb +23 -0
  47. data/spec/unit/rule/predicate_spec.rb +53 -0
  48. data/spec/unit/rule_compiler_spec.rb +38 -38
  49. data/spec/unit/rule_spec.rb +94 -0
  50. metadata +67 -40
  51. data/lib/dry/logic/predicate.rb +0 -100
  52. data/lib/dry/logic/predicate_set.rb +0 -23
  53. data/lib/dry/logic/result/each.rb +0 -20
  54. data/lib/dry/logic/result/multi.rb +0 -14
  55. data/lib/dry/logic/result/named.rb +0 -17
  56. data/lib/dry/logic/result/set.rb +0 -10
  57. data/lib/dry/logic/result/value.rb +0 -17
  58. data/lib/dry/logic/rule/attr.rb +0 -13
  59. data/lib/dry/logic/rule/check.rb +0 -40
  60. data/lib/dry/logic/rule/composite.rb +0 -91
  61. data/lib/dry/logic/rule/each.rb +0 -13
  62. data/lib/dry/logic/rule/key.rb +0 -37
  63. data/lib/dry/logic/rule/negation.rb +0 -15
  64. data/lib/dry/logic/rule/set.rb +0 -31
  65. data/lib/dry/logic/rule/value.rb +0 -48
  66. data/spec/unit/predicate_spec.rb +0 -115
  67. data/spec/unit/rule/attr_spec.rb +0 -29
  68. data/spec/unit/rule/check_spec.rb +0 -44
  69. data/spec/unit/rule/conjunction_spec.rb +0 -30
  70. data/spec/unit/rule/disjunction_spec.rb +0 -38
  71. data/spec/unit/rule/each_spec.rb +0 -31
  72. data/spec/unit/rule/exclusive_disjunction_spec.rb +0 -19
  73. data/spec/unit/rule/implication_spec.rb +0 -16
  74. data/spec/unit/rule/key_spec.rb +0 -121
  75. data/spec/unit/rule/set_spec.rb +0 -30
  76. data/spec/unit/rule/value_spec.rb +0 -99
@@ -0,0 +1,40 @@
1
+ RSpec.describe Operations::Negation do
2
+ subject(:operation) { Operations::Negation.new(is_int) }
3
+
4
+ include_context 'predicates'
5
+
6
+ let(:is_int) { Rule::Predicate.new(int?) }
7
+
8
+ describe '#call' do
9
+ it 'negates its rule' do
10
+ expect(operation.('19')).to be_success
11
+ expect(operation.(17)).to be_failure
12
+ end
13
+ end
14
+
15
+ describe '#to_ast' do
16
+ it 'returns ast' do
17
+ expect(operation.to_ast).to eql(
18
+ [:not, [:predicate, [:int?, [[:input, Undefined]]]]]
19
+ )
20
+ end
21
+
22
+ it 'returns result ast' do
23
+ expect(operation.(17).to_ast).to eql(
24
+ [:not, [:predicate, [:int?, [[:input, 17]]]]]
25
+ )
26
+ end
27
+
28
+ it 'returns result ast with an :id' do
29
+ expect(operation.with(id: :age).(17).to_ast).to eql(
30
+ [:failure, [:age, [:not, [:predicate, [:int?, [[:input, 17]]]]]]]
31
+ )
32
+ end
33
+ end
34
+
35
+ describe '#to_s' do
36
+ it 'returns string representation' do
37
+ expect(operation.to_s).to eql('not(int?)')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,73 @@
1
+ RSpec.describe Operations::Or do
2
+ subject(:operation) { Operations::Or.new(left, right) }
3
+
4
+ include_context 'predicates'
5
+
6
+ let(:left) { Rule::Predicate.new(none?) }
7
+ let(:right) { Rule::Predicate.new(gt?).curry(18) }
8
+
9
+ let(:other) do
10
+ Rule::Predicate.new(int?) & Rule::Predicate.new(lt?).curry(14)
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'calls left and right' do
15
+ expect(operation.(nil)).to be_success
16
+ expect(operation.(19)).to be_success
17
+ expect(operation.(18)).to be_failure
18
+ end
19
+ end
20
+
21
+ describe '#to_ast' do
22
+ it 'returns ast' do
23
+ expect(operation.to_ast).to eql(
24
+ [:or, [
25
+ [:predicate, [:none?, [[:input, Undefined]]]],
26
+ [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]
27
+ ]
28
+ )
29
+ end
30
+
31
+ it 'returns result ast' do
32
+ expect(operation.(17).to_ast).to eql(
33
+ [:or, [
34
+ [:predicate, [:none?, [[:input, 17]]]],
35
+ [:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]
36
+ ]
37
+ )
38
+ end
39
+
40
+ it 'returns failure result ast' do
41
+ expect(operation.with(id: :age).(17).to_ast).to eql(
42
+ [:failure, [:age, [:or, [
43
+ [:predicate, [:none?, [[:input, 17]]]],
44
+ [:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]
45
+ ]]]
46
+ )
47
+ end
48
+ end
49
+
50
+ describe '#and' do
51
+ it 'creates and with the other' do
52
+ expect(operation.and(other).(nil)).to be_failure
53
+ expect(operation.and(other).(19)).to be_failure
54
+ expect(operation.and(other).(13)).to be_failure
55
+ expect(operation.and(other).(14)).to be_failure
56
+ end
57
+ end
58
+
59
+ describe '#or' do
60
+ it 'creates or with the other' do
61
+ expect(operation.or(other).(nil)).to be_success
62
+ expect(operation.or(other).(19)).to be_success
63
+ expect(operation.or(other).(13)).to be_success
64
+ expect(operation.or(other).(14)).to be_failure
65
+ end
66
+ end
67
+
68
+ describe '#to_s' do
69
+ it 'returns string representation' do
70
+ expect(operation.to_s).to eql('none? OR gt?(18)')
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ RSpec.describe Operations::Set do
2
+ subject(:operation) { Operations::Set.new(is_int, gt_18) }
3
+
4
+ include_context 'predicates'
5
+
6
+ let(:is_int) { Rule::Predicate.new(int?) }
7
+ let(:gt_18) { Rule::Predicate.new(gt?, args: [18]) }
8
+
9
+ describe '#call' do
10
+ it 'applies all its rules to the input' do
11
+ expect(operation.(19)).to be_success
12
+ expect(operation.(17)).to be_failure
13
+ end
14
+ end
15
+
16
+ describe '#to_ast' do
17
+ it 'returns ast' do
18
+ expect(operation.to_ast).to eql(
19
+ [:set, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]]
20
+ )
21
+ end
22
+
23
+ it 'returns result ast' do
24
+ expect(operation.(17).to_ast).to eql(
25
+ [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]]
26
+ )
27
+ end
28
+
29
+ it 'returns result ast with an :id' do
30
+ expect(operation.with(id: :age).(17).to_ast).to eql(
31
+ [:failure, [:age, [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]]]]
32
+ )
33
+ end
34
+ end
35
+
36
+ describe '#to_s' do
37
+ it 'returns string representation' do
38
+ expect(operation.to_s).to eql('set(int?, gt?(18))')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ RSpec.describe Operations::Xor do
2
+ subject(:operation) { Operations::Xor.new(left, right) }
3
+
4
+ include_context 'predicates'
5
+
6
+ let(:left) { Rule::Predicate.new(array?) }
7
+ let(:right) { Rule::Predicate.new(empty?) }
8
+
9
+ let(:other) do
10
+ Rule::Predicate.new(str?)
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'calls left and right' do
15
+ expect(operation.(nil)).to be_success
16
+ expect(operation.('')).to be_success
17
+ expect(operation.([])).to be_failure
18
+ end
19
+ end
20
+
21
+ describe '#to_ast' do
22
+ it 'returns ast' do
23
+ expect(operation.to_ast).to eql(
24
+ [:xor, [[:predicate, [:array?, [[:input, Undefined]]]], [:predicate, [:empty?, [[:input, Undefined]]]]]]
25
+ )
26
+ end
27
+
28
+ it 'returns result ast' do
29
+ expect(operation.([]).to_ast).to eql(
30
+ [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]]
31
+ )
32
+ end
33
+
34
+ it 'returns failure result ast' do
35
+ expect(operation.with(id: :name).([]).to_ast).to eql(
36
+ [:failure, [:name, [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]]]]
37
+ )
38
+ end
39
+ end
40
+
41
+ describe '#and' do
42
+ it 'creates conjunction with the other' do
43
+ expect(operation.and(other).(nil)).to be_failure
44
+ expect(operation.and(other).(19)).to be_failure
45
+ expect(operation.and(other).('')).to be_success
46
+ end
47
+ end
48
+
49
+ describe '#or' do
50
+ it 'creates disjunction with the other' do
51
+ expect(operation.or(other).([])).to be_failure
52
+ expect(operation.or(other).('')).to be_success
53
+ end
54
+ end
55
+
56
+ describe '#to_s' do
57
+ it 'returns string representation' do
58
+ expect(operation.to_s).to eql('array? XOR empty?')
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Predicates do
4
+ it 'can be included in another module' do
5
+ mod = Module.new { include Predicates }
6
+
7
+ expect(mod[:key?]).to be_a(Method)
8
+ end
9
+
10
+ describe '.predicate' do
11
+ it 'defines a predicate method' do
12
+ mod = Module.new {
13
+ include Predicates
14
+
15
+ predicate(:test?) do |foo|
16
+ true
17
+ end
18
+ }
19
+
20
+ expect(mod.test?('arg')).to be(true)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,53 @@
1
+ RSpec.describe Rule::Predicate do
2
+ subject(:rule) { Rule::Predicate.new(predicate) }
3
+
4
+ let(:predicate) { str? }
5
+
6
+ include_context 'predicates'
7
+
8
+ it_behaves_like Rule
9
+
10
+ describe '#name' do
11
+ it 'returns predicate identifier' do
12
+ expect(rule.name).to be(:str?)
13
+ end
14
+ end
15
+
16
+ describe '#to_ast' do
17
+ context 'without a result' do
18
+ it 'returns rule ast' do
19
+ expect(rule.to_ast).to eql([:predicate, [:str?, [[:input, Undefined]]]])
20
+ end
21
+
22
+ it 'returns :failure with an id' do
23
+ email = rule.with(id: :email)
24
+
25
+ expect(email.(11).to_ast).to eql([:failure, [:email, [:predicate, [:str?, [[:input, 11]]]]]])
26
+ end
27
+ end
28
+
29
+ context 'with a result' do
30
+ it 'returns success ast' do
31
+ expect(rule.('foo').to_ast).to eql([:predicate, [:str?, [[:input, 'foo']]]])
32
+ end
33
+
34
+ it 'returns ast' do
35
+ expect(rule.(5).to_ast).to eql([:predicate, [:str?, [[:input, 5]]]])
36
+ end
37
+ end
38
+
39
+ context 'with a zero-arity predicate' do
40
+ let(:predicate) { Module.new { def self.test?; true; end }.method(:test?) }
41
+
42
+ it 'returns ast' do
43
+ expect(rule.to_ast).to eql([:predicate, [:test?, []]])
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#to_s' do
49
+ it 'returns string representation' do
50
+ expect(rule.curry('foo').to_s).to eql('str?("foo")')
51
+ end
52
+ end
53
+ end
@@ -11,117 +11,117 @@ RSpec.describe Dry::Logic::RuleCompiler, '#call' do
11
11
  one: predicate }
12
12
  }
13
13
 
14
- let(:predicate) { double(:predicate).as_null_object }
15
-
16
- let(:val_rule) { Rule::Value.new(predicate) }
17
- let(:key_rule) { Rule::Key.new(predicate, name: :email) }
18
- let(:attr_rule) { Rule::Attr.new(predicate, name: :email) }
19
- let(:not_key_rule) { Rule::Key.new(predicate, name: :email).negation }
20
- let(:check_rule) { Rule::Check.new(predicate, name: :email, keys: [:email]) }
21
- let(:and_rule) { key_rule & val_rule }
22
- let(:or_rule) { key_rule | val_rule }
23
- let(:xor_rule) { key_rule ^ val_rule }
24
- let(:set_rule) { Rule::Set.new([val_rule]) }
25
- let(:each_rule) { Rule::Each.new(val_rule) }
14
+ let(:predicate) { double(:predicate, name: :test?, arity: 2).as_null_object }
15
+
16
+ let(:rule) { Rule::Predicate.new(predicate) }
17
+ let(:key_op) { Operations::Key.new(rule, name: :email) }
18
+ let(:attr_op) { Operations::Attr.new(rule, name: :email) }
19
+ let(:check_op) { Operations::Check.new(rule, keys: [:email]) }
20
+ let(:not_key_op) { Operations::Negation.new(key_op) }
21
+ let(:and_op) { key_op.curry(:email) & rule }
22
+ let(:or_op) { key_op.curry(:email) | rule }
23
+ let(:xor_op) { key_op.curry(:email) ^ rule }
24
+ let(:set_op) { Operations::Set.new(rule) }
25
+ let(:each_op) { Operations::Each.new(rule) }
26
26
 
27
27
  it 'compiles key rules' do
28
- ast = [[:key, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
28
+ ast = [[:key, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]]
29
29
 
30
30
  rules = compiler.(ast)
31
31
 
32
- expect(rules).to eql([key_rule])
32
+ expect(rules).to eql([key_op])
33
33
  end
34
34
 
35
35
  it 'compiles attr rules' do
36
- ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
36
+ ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]]
37
37
 
38
38
  rules = compiler.(ast)
39
39
 
40
- expect(rules).to eql([attr_rule])
40
+ expect(rules).to eql([attr_op])
41
41
  end
42
42
 
43
43
  it 'compiles check rules' do
44
- ast = [[:check, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]
44
+ ast = [[:check, [[:email], [:predicate, [:filled?, [[:input, Undefined]]]]]]]
45
45
 
46
46
  rules = compiler.(ast)
47
47
 
48
- expect(rules).to eql([check_rule])
48
+ expect(rules).to eql([check_op])
49
49
  end
50
50
 
51
51
  it 'compiles attr rules' do
52
- ast = [[:attr, [:email, [:predicate, [:attr?, [[:name, :email]]]]]]]
52
+ ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]]
53
53
 
54
54
  rules = compiler.(ast)
55
55
 
56
- expect(rules).to eql([attr_rule])
56
+ expect(rules).to eql([attr_op])
57
57
  end
58
58
 
59
59
  it 'compiles negated rules' do
60
- ast = [[:not, [:key, [:email, [:predicate, [:filled?, [[:input, nil]]]]]]]]
60
+ ast = [[:not, [:key, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]]]
61
61
 
62
62
  rules = compiler.(ast)
63
63
 
64
- expect(rules).to eql([not_key_rule])
64
+ expect(rules).to eql([not_key_op])
65
65
  end
66
66
 
67
- it 'compiles conjunction rules' do
67
+ it 'compiles and rules' do
68
68
  ast = [
69
69
  [
70
70
  :and, [
71
- [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
72
- [:val, [:predicate, [:filled?, [[:input, nil]]]]]
71
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]],
72
+ [:predicate, [:filled?, [[:input, Undefined]]]]
73
73
  ]
74
74
  ]
75
75
  ]
76
76
 
77
77
  rules = compiler.(ast)
78
78
 
79
- expect(rules).to eql([and_rule])
79
+ expect(rules).to eql([and_op])
80
80
  end
81
81
 
82
- it 'compiles disjunction rules' do
82
+ it 'compiles or rules' do
83
83
  ast = [
84
84
  [
85
85
  :or, [
86
- [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
87
- [:val, [:predicate, [:filled?, [[:input, nil]]]]]
86
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]],
87
+ [:predicate, [:filled?, [[:input, Undefined]]]]
88
88
  ]
89
89
  ]
90
90
  ]
91
91
 
92
92
  rules = compiler.(ast)
93
93
 
94
- expect(rules).to eql([or_rule])
94
+ expect(rules).to eql([or_op])
95
95
  end
96
96
 
97
- it 'compiles exclusive disjunction rules' do
97
+ it 'compiles exclusive or rules' do
98
98
  ast = [
99
99
  [
100
100
  :xor, [
101
- [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, nil]]]]]],
102
- [:val, [:predicate, [:filled?, [[:input, nil]]]]]
101
+ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]],
102
+ [:predicate, [:filled?, [[:input, Undefined]]]]
103
103
  ]
104
104
  ]
105
105
  ]
106
106
 
107
107
  rules = compiler.(ast)
108
108
 
109
- expect(rules).to eql([xor_rule])
109
+ expect(rules).to eql([xor_op])
110
110
  end
111
111
 
112
112
  it 'compiles set rules' do
113
- ast = [[:set, [[:val, [:predicate, [:filled?, [[:input, nil]]]]]]]]
113
+ ast = [[:set, [[:predicate, [:filled?, [[:input, nil]]]]]]]
114
114
 
115
115
  rules = compiler.(ast)
116
116
 
117
- expect(rules).to eql([set_rule])
117
+ expect(rules).to eql([set_op])
118
118
  end
119
119
 
120
120
  it 'compiles each rules' do
121
- ast = [[:each, [:val, [:predicate, [:filled?, [[:input, nil]]]]]]]
121
+ ast = [[:each, [:predicate, [:filled?, [[:input, nil]]]]]]
122
122
 
123
123
  rules = compiler.(ast)
124
124
 
125
- expect(rules).to eql([each_rule])
125
+ expect(rules).to eql([each_op])
126
126
  end
127
127
  end