dry-logic 0.3.0 → 0.4.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.
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,94 @@
1
+ RSpec.describe Dry::Logic::Rule do
2
+ subject(:rule) { Rule.new(predicate, options) }
3
+
4
+ let(:predicate) { -> { true } }
5
+ let(:options) { {} }
6
+
7
+ it_behaves_like Dry::Logic::Rule
8
+
9
+ describe '.new' do
10
+ it 'accepts an :id' do
11
+ expect(Rule.new(predicate, id: :check_num).id).to be(:check_num)
12
+ end
13
+ end
14
+
15
+ describe 'with a function returning truthy value' do
16
+ it 'is successful for valid input' do
17
+ expect(Rule.new(-> val { val }).('true')).to be_success
18
+ end
19
+
20
+ it 'is not successful for invalid input' do
21
+ expect(Rule.new(-> val { val }).(nil)).to be_failure
22
+ end
23
+ end
24
+
25
+ describe '#ast' do
26
+ it 'returns predicate node with :id' do
27
+ expect(Rule.new(-> value { true }).with(id: :email?).ast('oops')).to eql(
28
+ [:predicate, [:email?, [[:value, 'oops']]]]
29
+ )
30
+ end
31
+
32
+ it 'returns predicate node with undefined args' do
33
+ expect(Rule.new(-> value { true }).with(id: :email?).ast).to eql(
34
+ [:predicate, [:email?, [[:value, Undefined]]]]
35
+ )
36
+ end
37
+ end
38
+
39
+ describe '#type' do
40
+ it 'returns rule type' do
41
+ expect(rule.type).to be(:rule)
42
+ end
43
+ end
44
+
45
+ describe '#bind' do
46
+ let(:bound) { rule.with(id: :bound).bind(object) }
47
+
48
+ context 'with an unbound method' do
49
+ let(:predicate) { klass.instance_method(:test?) }
50
+ let(:klass) { Class.new { def test?; true; end } }
51
+ let(:object) { klass.new }
52
+
53
+ it 'returns a new rule with its predicate bound to a specific object' do
54
+ expect(bound.()).to be_success
55
+ end
56
+
57
+ it 'carries id' do
58
+ expect(bound.id).to be(:bound)
59
+ end
60
+ end
61
+
62
+ context 'with an arbitrary block' do
63
+ let(:predicate) { -> value { value == expected } }
64
+ let(:object) { Class.new { def expected; 'test'; end }.new }
65
+
66
+ it 'returns a new with its predicate executed in the context of the provided object' do
67
+ expect(bound.('test')).to be_success
68
+ expect(bound.('oops')).to be_failure
69
+ end
70
+
71
+ it 'carries id' do
72
+ expect(bound.id).to be(:bound)
73
+ end
74
+
75
+ it 'stores arity' do
76
+ expect(bound.options[:arity]).to be(rule.arity)
77
+ end
78
+
79
+ it 'stores parameters' do
80
+ expect(bound.options[:parameters]).to eql(rule.parameters)
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#eval_args' do
86
+ let(:options) { { args: [1, klass.instance_method(:num), :foo] } }
87
+ let(:klass) { Class.new { def num; 7; end } }
88
+ let(:object) { klass.new }
89
+
90
+ it 'evaluates args in the context of the provided object' do
91
+ expect(rule.eval_args(object).args).to eql([1, 7, :foo])
92
+ end
93
+ end
94
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-logic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.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-07-01 00:00:00.000000000 Z
11
+ date: 2016-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: dry-container
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -107,30 +121,45 @@ files:
107
121
  - examples/basic.rb
108
122
  - lib/dry-logic.rb
109
123
  - lib/dry/logic.rb
124
+ - lib/dry/logic/appliable.rb
110
125
  - lib/dry/logic/evaluator.rb
111
- - lib/dry/logic/predicate.rb
112
- - lib/dry/logic/predicate_set.rb
126
+ - lib/dry/logic/operations.rb
127
+ - lib/dry/logic/operations/abstract.rb
128
+ - lib/dry/logic/operations/and.rb
129
+ - lib/dry/logic/operations/attr.rb
130
+ - lib/dry/logic/operations/binary.rb
131
+ - lib/dry/logic/operations/check.rb
132
+ - lib/dry/logic/operations/each.rb
133
+ - lib/dry/logic/operations/implication.rb
134
+ - lib/dry/logic/operations/key.rb
135
+ - lib/dry/logic/operations/negation.rb
136
+ - lib/dry/logic/operations/or.rb
137
+ - lib/dry/logic/operations/set.rb
138
+ - lib/dry/logic/operations/unary.rb
139
+ - lib/dry/logic/operations/xor.rb
140
+ - lib/dry/logic/operators.rb
113
141
  - lib/dry/logic/predicates.rb
114
142
  - lib/dry/logic/result.rb
115
- - lib/dry/logic/result/each.rb
116
- - lib/dry/logic/result/multi.rb
117
- - lib/dry/logic/result/named.rb
118
- - lib/dry/logic/result/set.rb
119
- - lib/dry/logic/result/value.rb
120
143
  - lib/dry/logic/rule.rb
121
- - lib/dry/logic/rule/attr.rb
122
- - lib/dry/logic/rule/check.rb
123
- - lib/dry/logic/rule/composite.rb
124
- - lib/dry/logic/rule/each.rb
125
- - lib/dry/logic/rule/key.rb
126
- - lib/dry/logic/rule/negation.rb
127
- - lib/dry/logic/rule/set.rb
128
- - lib/dry/logic/rule/value.rb
144
+ - lib/dry/logic/rule/predicate.rb
129
145
  - lib/dry/logic/rule_compiler.rb
130
146
  - lib/dry/logic/version.rb
147
+ - spec/integration/result_spec.rb
148
+ - spec/integration/rule_spec.rb
131
149
  - spec/shared/predicates.rb
150
+ - spec/shared/rule.rb
132
151
  - spec/spec_helper.rb
133
- - spec/unit/predicate_spec.rb
152
+ - spec/support/mutant.rb
153
+ - spec/unit/operations/and_spec.rb
154
+ - spec/unit/operations/attr_spec.rb
155
+ - spec/unit/operations/check_spec.rb
156
+ - spec/unit/operations/each_spec.rb
157
+ - spec/unit/operations/implication_spec.rb
158
+ - spec/unit/operations/key_spec.rb
159
+ - spec/unit/operations/negation_spec.rb
160
+ - spec/unit/operations/or_spec.rb
161
+ - spec/unit/operations/set_spec.rb
162
+ - spec/unit/operations/xor_spec.rb
134
163
  - spec/unit/predicates/array_spec.rb
135
164
  - spec/unit/predicates/attr_spec.rb
136
165
  - spec/unit/predicates/bool_spec.rb
@@ -165,17 +194,10 @@ files:
165
194
  - spec/unit/predicates/time_spec.rb
166
195
  - spec/unit/predicates/true_spec.rb
167
196
  - spec/unit/predicates/type_spec.rb
168
- - spec/unit/rule/attr_spec.rb
169
- - spec/unit/rule/check_spec.rb
170
- - spec/unit/rule/conjunction_spec.rb
171
- - spec/unit/rule/disjunction_spec.rb
172
- - spec/unit/rule/each_spec.rb
173
- - spec/unit/rule/exclusive_disjunction_spec.rb
174
- - spec/unit/rule/implication_spec.rb
175
- - spec/unit/rule/key_spec.rb
176
- - spec/unit/rule/set_spec.rb
177
- - spec/unit/rule/value_spec.rb
197
+ - spec/unit/predicates_spec.rb
198
+ - spec/unit/rule/predicate_spec.rb
178
199
  - spec/unit/rule_compiler_spec.rb
200
+ - spec/unit/rule_spec.rb
179
201
  homepage: https://github.com/dryrb/dry-logic
180
202
  licenses:
181
203
  - MIT
@@ -201,9 +223,22 @@ signing_key:
201
223
  specification_version: 4
202
224
  summary: Predicate logic with rule composition
203
225
  test_files:
226
+ - spec/integration/result_spec.rb
227
+ - spec/integration/rule_spec.rb
204
228
  - spec/shared/predicates.rb
229
+ - spec/shared/rule.rb
205
230
  - spec/spec_helper.rb
206
- - spec/unit/predicate_spec.rb
231
+ - spec/support/mutant.rb
232
+ - spec/unit/operations/and_spec.rb
233
+ - spec/unit/operations/attr_spec.rb
234
+ - spec/unit/operations/check_spec.rb
235
+ - spec/unit/operations/each_spec.rb
236
+ - spec/unit/operations/implication_spec.rb
237
+ - spec/unit/operations/key_spec.rb
238
+ - spec/unit/operations/negation_spec.rb
239
+ - spec/unit/operations/or_spec.rb
240
+ - spec/unit/operations/set_spec.rb
241
+ - spec/unit/operations/xor_spec.rb
207
242
  - spec/unit/predicates/array_spec.rb
208
243
  - spec/unit/predicates/attr_spec.rb
209
244
  - spec/unit/predicates/bool_spec.rb
@@ -238,15 +273,7 @@ test_files:
238
273
  - spec/unit/predicates/time_spec.rb
239
274
  - spec/unit/predicates/true_spec.rb
240
275
  - spec/unit/predicates/type_spec.rb
241
- - spec/unit/rule/attr_spec.rb
242
- - spec/unit/rule/check_spec.rb
243
- - spec/unit/rule/conjunction_spec.rb
244
- - spec/unit/rule/disjunction_spec.rb
245
- - spec/unit/rule/each_spec.rb
246
- - spec/unit/rule/exclusive_disjunction_spec.rb
247
- - spec/unit/rule/implication_spec.rb
248
- - spec/unit/rule/key_spec.rb
249
- - spec/unit/rule/set_spec.rb
250
- - spec/unit/rule/value_spec.rb
276
+ - spec/unit/predicates_spec.rb
277
+ - spec/unit/rule/predicate_spec.rb
251
278
  - spec/unit/rule_compiler_spec.rb
252
- has_rdoc:
279
+ - spec/unit/rule_spec.rb
@@ -1,100 +0,0 @@
1
- module Dry
2
- module Logic
3
- def self.Predicate(block)
4
- case block
5
- when Method then Predicate.new(block.name, &block)
6
- when UnboundMethod then Predicate.new(block.name, fn: block)
7
- else raise ArgumentError, 'predicate needs an :id'
8
- end
9
- end
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
-
19
- include Dry::Equalizer(:id, :args)
20
-
21
- attr_reader :id, :args, :fn, :arity
22
-
23
- class Curried < Predicate
24
- def call(*args)
25
- all_args = @args + args
26
-
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)
37
- @id = id
38
- @args = args
39
- @fn = fn || block
40
- @arity = arity || @fn.arity
41
- end
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
68
- def call(*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
77
- end
78
- alias_method :[], :call
79
-
80
- def parameters
81
- fn.parameters
82
- end
83
-
84
- def to_ast
85
- [:predicate, [id, args_with_names]]
86
- end
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
98
- end
99
- end
100
- end
@@ -1,23 +0,0 @@
1
- require 'dry/logic/predicate'
2
- require 'dry-container'
3
-
4
- module Dry
5
- module Logic
6
- module PredicateSet
7
- module Methods
8
- def predicate(name, &block)
9
- register(name) { Predicate.new(name, &block) }
10
- end
11
-
12
- def import(predicate_set)
13
- merge(predicate_set)
14
- end
15
- end
16
-
17
- def self.extended(other)
18
- super
19
- other.extend(Methods, Dry::Container::Mixin)
20
- end
21
- end
22
- end
23
- end
@@ -1,20 +0,0 @@
1
- module Dry
2
- module Logic
3
- class Result::Each < Result::Multi
4
- def to_ast
5
- failed_rules = failures.map { |idx, el| [:el, [idx, el.to_ast]] }
6
- [:result, [input, [:each, failed_rules]]]
7
- end
8
-
9
- def success?
10
- response.values.all?(&:success?)
11
- end
12
-
13
- def failures
14
- response.each_with_object({}) { |(idx, res), hash|
15
- hash[idx] = res if res.failure?
16
- }
17
- end
18
- end
19
- end
20
- end
@@ -1,14 +0,0 @@
1
- module Dry
2
- module Logic
3
- class Result::Multi < Result
4
- def success?
5
- success.all?(&:success?)
6
- end
7
-
8
- def failures
9
- indices = success.map { |v| v.failure? ? success.index(v) : nil }.compact
10
- success.values_at(*indices)
11
- end
12
- end
13
- end
14
- end
@@ -1,17 +0,0 @@
1
- module Dry
2
- module Logic
3
- class Result::Named < Result::Value
4
- def name
5
- rule.name
6
- end
7
-
8
- def to_ast
9
- if response.respond_to?(:to_ast) && !response.is_a?(Result)
10
- response.to_ast
11
- else
12
- [:input, [rule.name, super]]
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,10 +0,0 @@
1
- module Dry
2
- module Logic
3
- class Result::Set < Result::Multi
4
- def to_ast
5
- failed_rules = failures.map { |el| el.to_ast }
6
- [:result, [input, [:set, failed_rules]]]
7
- end
8
- end
9
- end
10
- end
@@ -1,17 +0,0 @@
1
- module Dry
2
- module Logic
3
- class Result::Value < Result
4
- def to_ast
5
- if response.respond_to?(:to_ast)
6
- response.to_ast
7
- else
8
- [:result, [input, rule.to_ast]]
9
- end
10
- end
11
-
12
- def input
13
- rule.input != Predicate::Undefined ? rule.input : super
14
- end
15
- end
16
- end
17
- end