dry-logic 0.1.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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.rubocop_todo.yml +7 -0
  6. data/.travis.yml +31 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE +20 -0
  10. data/README.md +45 -0
  11. data/Rakefile +12 -0
  12. data/dry-logic.gemspec +24 -0
  13. data/examples/basic.rb +13 -0
  14. data/lib/dry-logic.rb +1 -0
  15. data/lib/dry/logic.rb +10 -0
  16. data/lib/dry/logic/predicate.rb +35 -0
  17. data/lib/dry/logic/predicate_set.rb +23 -0
  18. data/lib/dry/logic/predicates.rb +129 -0
  19. data/lib/dry/logic/result.rb +119 -0
  20. data/lib/dry/logic/rule.rb +91 -0
  21. data/lib/dry/logic/rule/check.rb +15 -0
  22. data/lib/dry/logic/rule/composite.rb +63 -0
  23. data/lib/dry/logic/rule/each.rb +13 -0
  24. data/lib/dry/logic/rule/group.rb +21 -0
  25. data/lib/dry/logic/rule/key.rb +17 -0
  26. data/lib/dry/logic/rule/set.rb +22 -0
  27. data/lib/dry/logic/rule/value.rb +13 -0
  28. data/lib/dry/logic/rule_compiler.rb +81 -0
  29. data/lib/dry/logic/version.rb +5 -0
  30. data/rakelib/rubocop.rake +18 -0
  31. data/spec/shared/predicates.rb +41 -0
  32. data/spec/spec_helper.rb +18 -0
  33. data/spec/unit/predicate_spec.rb +27 -0
  34. data/spec/unit/predicates/bool_spec.rb +34 -0
  35. data/spec/unit/predicates/date_spec.rb +31 -0
  36. data/spec/unit/predicates/date_time_spec.rb +31 -0
  37. data/spec/unit/predicates/decimal_spec.rb +32 -0
  38. data/spec/unit/predicates/empty_spec.rb +38 -0
  39. data/spec/unit/predicates/eql_spec.rb +21 -0
  40. data/spec/unit/predicates/exclusion_spec.rb +35 -0
  41. data/spec/unit/predicates/filled_spec.rb +38 -0
  42. data/spec/unit/predicates/float_spec.rb +31 -0
  43. data/spec/unit/predicates/format_spec.rb +21 -0
  44. data/spec/unit/predicates/gt_spec.rb +40 -0
  45. data/spec/unit/predicates/gteq_spec.rb +40 -0
  46. data/spec/unit/predicates/inclusion_spec.rb +35 -0
  47. data/spec/unit/predicates/int_spec.rb +34 -0
  48. data/spec/unit/predicates/key_spec.rb +29 -0
  49. data/spec/unit/predicates/lt_spec.rb +40 -0
  50. data/spec/unit/predicates/lteq_spec.rb +40 -0
  51. data/spec/unit/predicates/max_size_spec.rb +49 -0
  52. data/spec/unit/predicates/min_size_spec.rb +49 -0
  53. data/spec/unit/predicates/none_spec.rb +28 -0
  54. data/spec/unit/predicates/size_spec.rb +55 -0
  55. data/spec/unit/predicates/str_spec.rb +32 -0
  56. data/spec/unit/predicates/time_spec.rb +31 -0
  57. data/spec/unit/rule/check_spec.rb +29 -0
  58. data/spec/unit/rule/conjunction_spec.rb +30 -0
  59. data/spec/unit/rule/disjunction_spec.rb +38 -0
  60. data/spec/unit/rule/each_spec.rb +20 -0
  61. data/spec/unit/rule/group_spec.rb +12 -0
  62. data/spec/unit/rule/implication_spec.rb +16 -0
  63. data/spec/unit/rule/key_spec.rb +27 -0
  64. data/spec/unit/rule/set_spec.rb +32 -0
  65. data/spec/unit/rule/value_spec.rb +42 -0
  66. data/spec/unit/rule_compiler_spec.rb +123 -0
  67. metadata +221 -0
@@ -0,0 +1,29 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#key?' do
5
+ let(:predicate_name) { :key? }
6
+
7
+ context 'when key is present in value' do
8
+ let(:arguments_list) do
9
+ [
10
+ [:name, { name: 'John' }],
11
+ [:age, { age: 18 }]
12
+ ]
13
+ end
14
+
15
+ it_behaves_like 'a passing predicate'
16
+ end
17
+
18
+ context 'with key is not present in value' do
19
+ let(:arguments_list) do
20
+ [
21
+ [:name, { age: 18 }],
22
+ [:age, { name: 'Jill' }]
23
+ ]
24
+ end
25
+
26
+ it_behaves_like 'a failing predicate'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#lt?' do
5
+ let(:predicate_name) { :lt? }
6
+
7
+ context 'when value is less than n' do
8
+ let(:arguments_list) do
9
+ [
10
+ [13, 12],
11
+ [13.37, 13.36],
12
+ ]
13
+ end
14
+
15
+ it_behaves_like 'a passing predicate'
16
+ end
17
+
18
+ context 'when value is equal to n' do
19
+ let(:arguments_list) do
20
+ [
21
+ [13, 13],
22
+ [13.37, 13.37]
23
+ ]
24
+ end
25
+
26
+ it_behaves_like 'a failing predicate'
27
+ end
28
+
29
+ context 'with value is greater than n' do
30
+ let(:arguments_list) do
31
+ [
32
+ [13, 14],
33
+ [13.37, 13.38]
34
+ ]
35
+ end
36
+
37
+ it_behaves_like 'a failing predicate'
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#lteq?' do
5
+ let(:predicate_name) { :lteq? }
6
+
7
+ context 'when value is less than n' do
8
+ let(:arguments_list) do
9
+ [
10
+ [13, 12],
11
+ [13.37, 13.36]
12
+ ]
13
+ end
14
+
15
+ it_behaves_like 'a passing predicate'
16
+ end
17
+
18
+ context 'when value is equal to n' do
19
+ let(:arguments_list) do
20
+ [
21
+ [13, 13],
22
+ [13.37, 13.37]
23
+ ]
24
+ end
25
+
26
+ it_behaves_like 'a passing predicate'
27
+ end
28
+
29
+ context 'with value is greater than n' do
30
+ let(:arguments_list) do
31
+ [
32
+ [13, 14],
33
+ [13.37, 13.38]
34
+ ]
35
+ end
36
+
37
+ it_behaves_like 'a failing predicate'
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#max_size?' do
5
+ let(:predicate_name) { :max_size? }
6
+
7
+ context 'when value size is less than n' do
8
+ let(:arguments_list) do
9
+ [
10
+ [3, [1, 2]],
11
+ [5, 'Jill'],
12
+ [3, { 1 => 'st', 2 => 'nd' }],
13
+ [9, 1],
14
+ [6, 1..5]
15
+ ]
16
+ end
17
+
18
+ it_behaves_like 'a passing predicate'
19
+ end
20
+
21
+ context 'when value size is equal to n' do
22
+ let(:arguments_list) do
23
+ [
24
+ [2, [1, 2]],
25
+ [4, 'Jill'],
26
+ [2, { 1 => 'st', 2 => 'nd' }],
27
+ [8, 1],
28
+ [5, 1..5]
29
+ ]
30
+ end
31
+
32
+ it_behaves_like 'a passing predicate'
33
+ end
34
+
35
+ context 'with value size is greater than n' do
36
+ let(:arguments_list) do
37
+ [
38
+ [1, [1, 2]],
39
+ [3, 'Jill'],
40
+ [1, { 1 => 'st', 2 => 'nd' }],
41
+ [7, 1],
42
+ [4, 1..5]
43
+ ]
44
+ end
45
+
46
+ it_behaves_like 'a failing predicate'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#min_size?' do
5
+ let(:predicate_name) { :min_size? }
6
+
7
+ context 'when value size is greater than n' do
8
+ let(:arguments_list) do
9
+ [
10
+ [1, [1, 2]],
11
+ [3, 'Jill'],
12
+ [1, { 1 => 'st', 2 => 'nd' }],
13
+ [7, 1],
14
+ [4, 1..5]
15
+ ]
16
+ end
17
+
18
+ it_behaves_like 'a passing predicate'
19
+ end
20
+
21
+ context 'when value size is equal to n' do
22
+ let(:arguments_list) do
23
+ [
24
+ [2, [1, 2]],
25
+ [4, 'Jill'],
26
+ [2, { 1 => 'st', 2 => 'nd' }],
27
+ [8, 1],
28
+ [5, 1..5]
29
+ ]
30
+ end
31
+
32
+ it_behaves_like 'a passing predicate'
33
+ end
34
+
35
+ context 'with value size is less than n' do
36
+ let(:arguments_list) do
37
+ [
38
+ [3, [1, 2]],
39
+ [5, 'Jill'],
40
+ [3, { 1 => 'st', 2 => 'nd' }],
41
+ [9, 1],
42
+ [6, 1..5]
43
+ ]
44
+ end
45
+
46
+ it_behaves_like 'a failing predicate'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#none?' do
5
+ let(:predicate_name) { :none? }
6
+
7
+ context 'when value is nil' do
8
+ let(:arguments_list) { [[nil]] }
9
+ it_behaves_like 'a passing predicate'
10
+ end
11
+
12
+ context 'when value is not nil' do
13
+ let(:arguments_list) do
14
+ [
15
+ [''],
16
+ [true],
17
+ [false],
18
+ [0],
19
+ [:symbol],
20
+ [[]],
21
+ [{}],
22
+ [String]
23
+ ]
24
+ end
25
+ it_behaves_like 'a failing predicate'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#size?' do
5
+ let(:predicate_name) { :size? }
6
+
7
+ context 'when value size is equal to n' do
8
+ let(:arguments_list) do
9
+ [
10
+ [[8], 2],
11
+ [4, 'Jill'],
12
+ [2, { 1 => 'st', 2 => 'nd' }],
13
+ [8, 8],
14
+ [1..8, 5]
15
+ ]
16
+ end
17
+
18
+ it_behaves_like 'a passing predicate'
19
+ end
20
+
21
+ context 'when value size is greater than n' do
22
+ let(:arguments_list) do
23
+ [
24
+ [[1, 2], 3],
25
+ [5, 'Jill'],
26
+ [3, { 1 => 'st', 2 => 'nd' }],
27
+ [1, 9],
28
+ [1..5, 6]
29
+ ]
30
+ end
31
+
32
+ it_behaves_like 'a failing predicate'
33
+ end
34
+
35
+ context 'with value size is less than n' do
36
+ let(:arguments_list) do
37
+ [
38
+ [[1, 2], 1],
39
+ [3, 'Jill'],
40
+ [1, { 1 => 'st', 2 => 'nd' }],
41
+ [1, 7],
42
+ [1..5, 4]
43
+ ]
44
+ end
45
+
46
+ it_behaves_like 'a failing predicate'
47
+ end
48
+
49
+ context 'with an unsupported size' do
50
+ it 'raises an error' do
51
+ expect { Predicates[:size?].call('oops', 1) }.to raise_error(ArgumentError, /oops/)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#str?' do
5
+ let(:predicate_name) { :str? }
6
+
7
+ context 'when value is a string' do
8
+ let(:arguments_list) do
9
+ [
10
+ [''],
11
+ ['John']
12
+ ]
13
+ end
14
+
15
+ it_behaves_like 'a passing predicate'
16
+ end
17
+
18
+ context 'with value is not a string' do
19
+ let(:arguments_list) do
20
+ [
21
+ [[]],
22
+ [{}],
23
+ [nil],
24
+ [:symbol],
25
+ [String]
26
+ ]
27
+ end
28
+
29
+ it_behaves_like 'a failing predicate'
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry/logic/predicates'
2
+
3
+ RSpec.describe Dry::Logic::Predicates do
4
+ describe '#time?' do
5
+ let(:predicate_name) { :time? }
6
+
7
+ context 'when value is a date' do
8
+ let(:arguments_list) do
9
+ [[Time.now]]
10
+ end
11
+
12
+ it_behaves_like 'a passing predicate'
13
+ end
14
+
15
+ context 'with value is not an integer' do
16
+ let(:arguments_list) do
17
+ [
18
+ [''],
19
+ [[]],
20
+ [{}],
21
+ [nil],
22
+ [:symbol],
23
+ [String],
24
+ [1]
25
+ ]
26
+ end
27
+
28
+ it_behaves_like 'a failing predicate'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ RSpec.describe Rule::Check do
2
+ subject(:rule) { Rule::Check.new(:name, other.(input).curry(predicate)) }
3
+
4
+ include_context 'predicates'
5
+
6
+ let(:other) do
7
+ Rule::Value.new(:name, none?).or(Rule::Value.new(:name, filled?))
8
+ end
9
+
10
+ describe '#call' do
11
+ context 'when a given predicate passed' do
12
+ let(:input) { 'Jane' }
13
+ let(:predicate) { :filled? }
14
+
15
+ it 'returns a success' do
16
+ expect(rule.()).to be_success
17
+ end
18
+ end
19
+
20
+ context 'when a given predicate did not pass' do
21
+ let(:input) { nil }
22
+ let(:predicate) { :filled? }
23
+
24
+ it 'returns a failure' do
25
+ expect(rule.()).to be_failure
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ RSpec.describe Rule::Composite::Conjunction do
2
+ include_context 'predicates'
3
+
4
+ subject(:rule) { Rule::Composite::Conjunction.new(left, right) }
5
+
6
+ let(:left) { Rule::Value.new(:age, int?) }
7
+ let(:right) { Rule::Value.new(:age, gt?.curry(18)) }
8
+
9
+ describe '#call' do
10
+ it 'calls left and right' do
11
+ expect(rule.(18)).to be_failure
12
+ end
13
+ end
14
+
15
+ describe '#and' do
16
+ let(:other) { Rule::Value.new(:age, lt?.curry(30)) }
17
+
18
+ it 'creates conjunction with the other' do
19
+ expect(rule.and(other).(31)).to be_failure
20
+ end
21
+ end
22
+
23
+ describe '#or' do
24
+ let(:other) { Rule::Value.new(:age, lt?.curry(14)) }
25
+
26
+ it 'creates disjunction with the other' do
27
+ expect(rule.or(other).(13)).to be_success
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ RSpec.describe Rule::Composite::Disjunction do
2
+ include_context 'predicates'
3
+
4
+ subject(:rule) { Rule::Composite::Disjunction.new(left, right) }
5
+
6
+ let(:left) { Rule::Value.new(:age, none?) }
7
+ let(:right) { Rule::Value.new(:age, gt?.curry(18)) }
8
+
9
+ let(:other) do
10
+ Rule::Value.new(:age, int?) & Rule::Value.new(:age, lt?.curry(14))
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'calls left and right' do
15
+ expect(rule.(nil)).to be_success
16
+ expect(rule.(19)).to be_success
17
+ expect(rule.(18)).to be_failure
18
+ end
19
+ end
20
+
21
+ describe '#and' do
22
+ it 'creates conjunction with the other' do
23
+ expect(rule.and(other).(nil)).to be_failure
24
+ expect(rule.and(other).(19)).to be_failure
25
+ expect(rule.and(other).(13)).to be_failure
26
+ expect(rule.and(other).(14)).to be_failure
27
+ end
28
+ end
29
+
30
+ describe '#or' do
31
+ it 'creates disjunction with the other' do
32
+ expect(rule.or(other).(nil)).to be_success
33
+ expect(rule.or(other).(19)).to be_success
34
+ expect(rule.or(other).(13)).to be_success
35
+ expect(rule.or(other).(14)).to be_failure
36
+ end
37
+ end
38
+ end