dry-logic 1.0.5 → 1.0.6

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -26
  3. data/LICENSE +1 -1
  4. data/README.md +12 -14
  5. data/dry-logic.gemspec +27 -20
  6. data/lib/dry/logic/predicates.rb +1 -1
  7. data/lib/dry/logic/rule/interface.rb +1 -1
  8. data/lib/dry/logic/version.rb +1 -1
  9. metadata +9 -150
  10. data/.codeclimate.yml +0 -12
  11. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  12. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  13. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  14. data/.github/workflows/ci.yml +0 -70
  15. data/.github/workflows/docsite.yml +0 -34
  16. data/.github/workflows/sync_configs.yml +0 -34
  17. data/.gitignore +0 -9
  18. data/.rspec +0 -4
  19. data/.rubocop.yml +0 -89
  20. data/CODE_OF_CONDUCT.md +0 -13
  21. data/CONTRIBUTING.md +0 -29
  22. data/Gemfile +0 -16
  23. data/Rakefile +0 -14
  24. data/benchmarks/rule_application.rb +0 -30
  25. data/benchmarks/setup.rb +0 -13
  26. data/bin/console +0 -11
  27. data/docsite/source/index.html.md +0 -54
  28. data/docsite/source/operations.html.md +0 -62
  29. data/docsite/source/predicates.html.md +0 -102
  30. data/examples/basic.rb +0 -16
  31. data/spec/integration/result_spec.rb +0 -61
  32. data/spec/integration/rule_spec.rb +0 -55
  33. data/spec/shared/predicates.rb +0 -59
  34. data/spec/shared/rule.rb +0 -74
  35. data/spec/spec_helper.rb +0 -30
  36. data/spec/support/mutant.rb +0 -11
  37. data/spec/unit/operations/and_spec.rb +0 -70
  38. data/spec/unit/operations/attr_spec.rb +0 -29
  39. data/spec/unit/operations/check_spec.rb +0 -51
  40. data/spec/unit/operations/each_spec.rb +0 -49
  41. data/spec/unit/operations/implication_spec.rb +0 -32
  42. data/spec/unit/operations/key_spec.rb +0 -135
  43. data/spec/unit/operations/negation_spec.rb +0 -51
  44. data/spec/unit/operations/or_spec.rb +0 -75
  45. data/spec/unit/operations/set_spec.rb +0 -43
  46. data/spec/unit/operations/xor_spec.rb +0 -63
  47. data/spec/unit/predicates/array_spec.rb +0 -43
  48. data/spec/unit/predicates/attr_spec.rb +0 -31
  49. data/spec/unit/predicates/bool_spec.rb +0 -36
  50. data/spec/unit/predicates/bytesize_spec.rb +0 -48
  51. data/spec/unit/predicates/case_spec.rb +0 -35
  52. data/spec/unit/predicates/date_spec.rb +0 -33
  53. data/spec/unit/predicates/date_time_spec.rb +0 -33
  54. data/spec/unit/predicates/decimal_spec.rb +0 -34
  55. data/spec/unit/predicates/empty_spec.rb +0 -40
  56. data/spec/unit/predicates/eql_spec.rb +0 -23
  57. data/spec/unit/predicates/even_spec.rb +0 -33
  58. data/spec/unit/predicates/excluded_from_spec.rb +0 -37
  59. data/spec/unit/predicates/excludes_spec.rb +0 -58
  60. data/spec/unit/predicates/false_spec.rb +0 -37
  61. data/spec/unit/predicates/filled_spec.rb +0 -40
  62. data/spec/unit/predicates/float_spec.rb +0 -33
  63. data/spec/unit/predicates/format_spec.rb +0 -31
  64. data/spec/unit/predicates/gt_spec.rb +0 -42
  65. data/spec/unit/predicates/gteq_spec.rb +0 -42
  66. data/spec/unit/predicates/hash_spec.rb +0 -40
  67. data/spec/unit/predicates/included_in_spec.rb +0 -37
  68. data/spec/unit/predicates/includes_spec.rb +0 -24
  69. data/spec/unit/predicates/int_spec.rb +0 -36
  70. data/spec/unit/predicates/key_spec.rb +0 -31
  71. data/spec/unit/predicates/lt_spec.rb +0 -42
  72. data/spec/unit/predicates/lteq_spec.rb +0 -42
  73. data/spec/unit/predicates/max_bytesize_spec.rb +0 -39
  74. data/spec/unit/predicates/max_size_spec.rb +0 -51
  75. data/spec/unit/predicates/min_bytesize_spec.rb +0 -39
  76. data/spec/unit/predicates/min_size_spec.rb +0 -51
  77. data/spec/unit/predicates/none_spec.rb +0 -30
  78. data/spec/unit/predicates/not_eql_spec.rb +0 -23
  79. data/spec/unit/predicates/number_spec.rb +0 -39
  80. data/spec/unit/predicates/odd_spec.rb +0 -33
  81. data/spec/unit/predicates/respond_to_spec.rb +0 -31
  82. data/spec/unit/predicates/size_spec.rb +0 -57
  83. data/spec/unit/predicates/str_spec.rb +0 -34
  84. data/spec/unit/predicates/time_spec.rb +0 -33
  85. data/spec/unit/predicates/true_spec.rb +0 -37
  86. data/spec/unit/predicates/type_spec.rb +0 -37
  87. data/spec/unit/predicates/uuid_v4_spec.rb +0 -29
  88. data/spec/unit/predicates_spec.rb +0 -25
  89. data/spec/unit/rule/predicate_spec.rb +0 -55
  90. data/spec/unit/rule_compiler_spec.rb +0 -129
  91. data/spec/unit/rule_spec.rb +0 -213
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require 'dry/logic'
6
- require 'dry/logic/predicates'
7
-
8
- include Dry::Logic
9
-
10
- require "irb"
11
- IRB.start
@@ -1,54 +0,0 @@
1
- ---
2
- title: Introduction
3
- description: Predicate logic with composable rules
4
- layout: gem-single
5
- type: gem
6
- name: dry-logic
7
- sections:
8
- - predicates
9
- - operations
10
- ---
11
-
12
- Predicate logic and rule composition used by:
13
-
14
- * [dry-types](https://github.com/dry-rb/dry-types) for constrained types
15
- * [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules
16
- * your project...?
17
-
18
- ## Synopsis
19
-
20
- ``` ruby
21
- require 'dry/logic'
22
- require 'dry/logic/predicates'
23
-
24
- include Dry::Logic
25
-
26
- # Rule::Predicate will only apply its predicate to its input, that’s all
27
-
28
- # require input to have the key :user
29
- user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user)
30
- # curry allows us to prepare predicates with args, without the input
31
-
32
- # require value to be greater than 18
33
- min_18 = Rule::Predicate.new(Predicates[:gt?]).curry(18)
34
-
35
- # use the min_18 predicate on the the value of user[:age]
36
- has_min_age = Operations::Key.new(min_18, name: [:user, :age])
37
-
38
- user_rule = user_present & has_min_age
39
-
40
- user_rule.(user: { age: 19 }).success?
41
- # => true
42
-
43
- user_rule.(user: { age: 18 }).success?
44
- # => false
45
-
46
- user_rule.(user: { age: 'seventeen' })
47
- # => ArgumentError: comparison of String with 18 failed
48
-
49
- user_rule.(user: { })
50
- # => NoMethodError: undefined method `>' for nil:NilClass
51
-
52
- user_rule.({}).success?
53
- # => false
54
- ```
@@ -1,62 +0,0 @@
1
- ---
2
- title: Operations
3
- layout: gem-single
4
- name: dry-logic
5
- ---
6
-
7
- Dry-logic uses operations to interact with the input passed to the different rules.
8
-
9
- ``` ruby
10
- require 'dry/logic'
11
- require 'dry/logic/predicates'
12
-
13
- include Dry::Logic
14
-
15
- user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user)
16
-
17
- min_18 = Rule::Predicate.new(Predicates[:gt?]).curry(18)
18
-
19
- # Here Operations::Key and Rule::Predicate are use to compose and logic based on the value of a given key e.g [:user, :age]
20
- has_min_age = Operations::Key.new(min_18, name: [:user, :age])
21
- # => #<Dry::Logic::Operations::Key rules=[#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#gt?> options={:args=>[18]}>] options={:name=>[:user, :age], :evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :path=>[:user, :age]}>
22
-
23
- # Thanks to the composable structure of the library we can use multiple Rules and Operations to create custom logic
24
- user_rule = user_present & has_min_age
25
-
26
- user_rule.(user: { age: 19 }).success?
27
- # => true
28
- ```
29
-
30
- * Built-in:
31
- - `and`
32
- - `or`
33
- - `key`
34
- - `attr`
35
- - `binary`
36
- - `check`
37
- - `each`
38
- - `implication`
39
- - `negation`
40
- - `set`
41
- - `xor`
42
-
43
- Another example, lets create the `all?` method from the `Enumerable` module.
44
-
45
- ``` ruby
46
- require 'dry/logic'
47
- require 'dry/logic/predicates'
48
-
49
- include Dry::Logic
50
-
51
- def all?(value)
52
- Operations::Each.new(Rule::Predicate.new(Predicates[:gt?]).curry(value))
53
- end
54
-
55
- all_6 = all?(6)
56
-
57
- all_6.([6,7,8,9]).success?
58
- # => true
59
-
60
- all_6.([1,2,3,4]).success?
61
- # => false
62
- ```
@@ -1,102 +0,0 @@
1
- ---
2
- title: Predicates
3
- layout: gem-single
4
- name: dry-logic
5
- ---
6
-
7
- Dry-logic comes with a lot predicates to compose multiple rules:
8
-
9
- ``` ruby
10
- require 'dry/logic'
11
- require 'dry/logic/predicates'
12
-
13
- include Dry::Logic
14
- ```
15
-
16
- Now you can access all built-in predicates:
17
-
18
- ``` ruby
19
- Predicates[:key?]
20
- # => #<Method: Module(Dry::Logic::Predicates::Methods)#key?>
21
- ```
22
-
23
- In the end predicates return true or false.
24
-
25
- ```ruby
26
- Predicates[:key?].(:name, {name: 'John'})
27
- # => true
28
- ```
29
-
30
- * Built-in:
31
- - `type?`
32
- - `none?`
33
- - `key?`
34
- - `attr?`
35
- - `empty?`
36
- - `filled?`
37
- - `bool?`
38
- - `date?`
39
- - `date_time?`
40
- - `time?`
41
- - `number?`
42
- - `int?`
43
- - `float?`
44
- - `decimal?`
45
- - `str?`
46
- - `hash?`
47
- - `array?`
48
- - `odd?`
49
- - `even?`
50
- - `lt?`
51
- - `gt?`
52
- - `lteq?`
53
- - `gteq?`
54
- - `size?`
55
- - `min_size?`
56
- - `max_size?`
57
- - `bytesize?`
58
- - `min_bytesize?`
59
- - `max_bytesize?`
60
- - `inclusion?`
61
- - `exclusion?`
62
- - `included_in?`
63
- - `excluded_from?`
64
- - `includes?`
65
- - `excludes?`
66
- - `eql?`
67
- - `not_eql?`
68
- - `is?`
69
- - `case?`
70
- - `true?`
71
- - `false?`
72
- - `format?`
73
- - `respond_to?`
74
- - `predicate`
75
- - `uuid_v4?`
76
-
77
- With predicates you can build more composable and complex operations:
78
- For example, let's say we want to check that a given input is a hash and has a specify key.
79
-
80
- ``` ruby
81
- require 'dry/logic'
82
- require 'dry/logic/predicates'
83
-
84
- include Dry::Logic
85
-
86
- is_hash = Rule::Predicate.new(Predicates[:type?]).curry(Hash)
87
- # => #<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[:hash]}>
88
- name_key = Rule::Predicate.new(Predicates[:key?]).curry(:name)
89
- # => #<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#key?> options={:args=>[:name]}>
90
-
91
- hash_with_key = is_hash & name_key
92
- # => #<Dry::Logic::Operations::And rules=[#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#type?> options={:args=>[:hash]}>, #<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#key?> options={:args=>[:name]}>] options={}>
93
-
94
- hash_with_key.(name: 'John').success?
95
- # => true
96
-
97
- hash_with_key.(not_valid: 'John').success?
98
- # => false
99
-
100
- hash_with_key.([1,2]).success?
101
- # => false
102
- ```
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/logic'
4
- require 'dry/logic/predicates'
5
-
6
- include Dry::Logic
7
-
8
- user_present = Rule::Predicate.build(Predicates[:key?]).curry(:user)
9
-
10
- has_min_age = Operations::Key.new(Rule::Predicate.build(Predicates[:gt?]).curry(18), name: [:user, :age])
11
-
12
- user_rule = user_present & has_min_age
13
-
14
- puts user_rule.(user: { age: 19 }).success?
15
-
16
- puts user_rule.(user: { age: 18 }).success?
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Result do
4
- include_context 'predicates'
5
-
6
- describe '#to_s' do
7
- shared_examples_for 'string representation' do
8
- it 'returns string representation' do
9
- expect(rule.(input).to_s).to eql(output)
10
- end
11
- end
12
-
13
- context 'with a predicate' do
14
- let(:rule) { Rule::Predicate.build(gt?, args: [18]) }
15
- let(:input) { 17 }
16
- let(:output) { 'gt?(18, 17)' }
17
-
18
- it_behaves_like 'string representation'
19
- end
20
-
21
- context 'with AND operation' do
22
- let(:rule) { Rule::Predicate.build(array?).and(Rule::Predicate.build(empty?)) }
23
- let(:input) { '' }
24
- let(:output) { 'array?("") AND empty?("")' }
25
-
26
- it_behaves_like 'string representation'
27
- end
28
-
29
- context 'with OR operation' do
30
- let(:rule) { Rule::Predicate.build(array?).or(Rule::Predicate.build(empty?)) }
31
- let(:input) { 123 }
32
- let(:output) { 'array?(123) OR empty?(123)' }
33
-
34
- it_behaves_like 'string representation'
35
- end
36
-
37
- context 'with XOR operation' do
38
- let(:rule) { Rule::Predicate.build(array?).xor(Rule::Predicate.build(empty?)) }
39
- let(:input) { [] }
40
- let(:output) { 'array?([]) XOR empty?([])' }
41
-
42
- it_behaves_like 'string representation'
43
- end
44
-
45
- context 'with THEN operation' do
46
- let(:rule) { Rule::Predicate.build(array?).then(Rule::Predicate.build(empty?)) }
47
- let(:input) { [1, 2, 3] }
48
- let(:output) { 'empty?([1, 2, 3])' }
49
-
50
- it_behaves_like 'string representation'
51
- end
52
-
53
- context 'with NOT operation' do
54
- let(:rule) { Operations::Negation.new(Rule::Predicate.build(array?)) }
55
- let(:input) { 'foo' }
56
- let(:output) { 'not(array?("foo"))' }
57
-
58
- it_behaves_like 'string representation'
59
- end
60
- end
61
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry-logic'
4
-
5
- RSpec.describe 'Rules' do
6
- specify 'defining an anonymous rule with an arbitrary predicate' do
7
- rule = Dry::Logic.Rule { |value| value.is_a?(Integer) }
8
-
9
- expect(rule.(1)).to be_success
10
- expect(rule[1]).to be(true)
11
- end
12
-
13
- specify 'defining a conjunction' do
14
- rule = Dry::Logic.Rule(&:even?) & Dry::Logic.Rule { |v| v > 4 }
15
-
16
- expect(rule.(3)).to be_failure
17
- expect(rule.(4)).to be_failure
18
- expect(rule.(5)).to be_failure
19
- expect(rule.(6)).to be_success
20
- end
21
-
22
- specify 'defining a disjunction' do
23
- rule = Dry::Logic.Rule { |v| v < 4 } | Dry::Logic.Rule { |v| v > 6 }
24
-
25
- expect(rule.(5)).to be_failure
26
- expect(rule.(3)).to be_success
27
- expect(rule.(7)).to be_success
28
- end
29
-
30
- specify 'defining an implication' do
31
- rule = Dry::Logic.Rule(&:empty?) > Dry::Logic.Rule { |v| v.is_a?(Array) }
32
-
33
- expect(rule.('foo')).to be_success
34
- expect(rule.([1, 2])).to be_success
35
- expect(rule.([])).to be_success
36
- expect(rule.('')).to be_failure
37
- end
38
-
39
- specify 'defining an exclusive disjunction' do
40
- rule = Dry::Logic.Rule(&:empty?) ^ Dry::Logic.Rule { |v| v.is_a?(Array) }
41
-
42
- expect(rule.('foo')).to be_failure
43
- expect(rule.([])).to be_failure
44
- expect(rule.([1, 2])).to be_success
45
- expect(rule.('')).to be_success
46
- end
47
-
48
- specify 'defining a rule with options' do
49
- rule = Dry::Logic::Rule(id: :empty?) { |value| value.empty? }
50
-
51
- expect(rule.('foo')).to be_failure
52
- expect(rule.('')).to be_success
53
- expect(rule.ast('foo')).to eql([:predicate, [:empty?, [[:value, 'foo']]]])
54
- end
55
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/logic/predicates'
4
-
5
- RSpec.shared_examples 'predicates' do
6
- let(:nil?) { Dry::Logic::Predicates[:nil?] }
7
-
8
- let(:array?) { Dry::Logic::Predicates[:array?] }
9
-
10
- let(:empty?) { Dry::Logic::Predicates[:empty?] }
11
-
12
- let(:str?) { Dry::Logic::Predicates[:str?] }
13
-
14
- let(:true?) { Dry::Logic::Predicates[:true?] }
15
-
16
- let(:hash?) { Dry::Logic::Predicates[:hash?] }
17
-
18
- let(:int?) { Dry::Logic::Predicates[:int?] }
19
-
20
- let(:filled?) { Dry::Logic::Predicates[:filled?] }
21
-
22
- let(:min_size?) { Dry::Logic::Predicates[:min_size?] }
23
-
24
- let(:lt?) { Dry::Logic::Predicates[:lt?] }
25
-
26
- let(:gt?) { Dry::Logic::Predicates[:gt?] }
27
-
28
- let(:key?) { Dry::Logic::Predicates[:key?] }
29
-
30
- let(:attr?) { Dry::Logic::Predicates[:attr?] }
31
-
32
- let(:eql?) { Dry::Logic::Predicates[:eql?] }
33
-
34
- let(:size?) { Dry::Logic::Predicates[:size?] }
35
-
36
- let(:case?) { Dry::Logic::Predicates[:case?] }
37
-
38
- let(:equal?) { Dry::Logic::Predicates[:equal?] }
39
- end
40
-
41
- RSpec.shared_examples 'a passing predicate' do
42
- let(:predicate) { Dry::Logic::Predicates[predicate_name] }
43
-
44
- it do
45
- arguments_list.each do |args|
46
- expect(predicate.call(*args)).to be(true)
47
- end
48
- end
49
- end
50
-
51
- RSpec.shared_examples 'a failing predicate' do
52
- let(:predicate) { Dry::Logic::Predicates[predicate_name] }
53
-
54
- it do
55
- arguments_list.each do |args|
56
- expect(predicate.call(*args)).to be(false)
57
- end
58
- end
59
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- shared_examples_for Dry::Logic::Rule do
4
- let(:arity) { 2 }
5
- let(:predicate) { double(:predicate, arity: arity, name: predicate_name) }
6
- let(:rule_type) { described_class }
7
- let(:predicate_name) { :good? }
8
-
9
- describe '#arity' do
10
- it 'returns its predicate arity' do
11
- rule = rule_type.build(predicate)
12
-
13
- expect(rule.arity).to be(2)
14
- end
15
- end
16
-
17
- describe '#parameters' do
18
- it 'returns a list of args with their names' do
19
- rule = rule_type.build(-> foo, bar { true }, args: [312])
20
-
21
- expect(rule.parameters).to eql([[:req, :foo], [:req, :bar]])
22
- end
23
- end
24
-
25
- describe '#call' do
26
- let(:arity) { 1 }
27
-
28
- it 'returns success for valid input' do
29
- rule = rule_type.build(predicate)
30
-
31
- expect(predicate).to receive(:[]).with(2).and_return(true)
32
-
33
- expect(rule.(2)).to be_success
34
- end
35
-
36
- it 'returns failure for invalid input' do
37
- rule = rule_type.build(predicate)
38
-
39
- expect(predicate).to receive(:[]).with(2).and_return(false)
40
-
41
- expect(rule.(2)).to be_failure
42
- end
43
- end
44
-
45
- describe '#[]' do
46
- let(:arity) { 1 }
47
-
48
- it 'delegates to its predicate' do
49
- rule = rule_type.build(predicate)
50
-
51
- expect(predicate).to receive(:[]).with(2).and_return(true)
52
- expect(rule[2]).to be(true)
53
- end
54
- end
55
-
56
- describe '#curry' do
57
- it 'returns a curried rule' do
58
- rule = rule_type.build(predicate).curry(3)
59
-
60
- expect(predicate).to receive(:[]).with(3, 2).and_return(true)
61
- expect(rule.args).to eql([3])
62
-
63
- expect(rule.(2)).to be_success
64
- end
65
-
66
- it 'raises argument error when arity does not match' do
67
- expect(predicate).to receive(:arity).and_return(2)
68
-
69
- expect { rule_type.build(predicate).curry(3, 2, 1) }.to raise_error(
70
- ArgumentError, 'wrong number of arguments (3 for 2)'
71
- )
72
- end
73
- end
74
- end