dry-logic 0.6.1 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +2 -5
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/ci.yml +70 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +34 -0
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/.rubocop.yml +89 -0
- data/CHANGELOG.md +52 -3
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +3 -0
- data/LICENSE +1 -1
- data/README.md +6 -6
- data/Rakefile +2 -0
- data/benchmarks/rule_application.rb +2 -0
- data/benchmarks/setup.rb +2 -0
- data/bin/console +1 -0
- data/docsite/source/index.html.md +54 -0
- data/docsite/source/operations.html.md +62 -0
- data/docsite/source/predicates.html.md +102 -0
- data/dry-logic.gemspec +8 -0
- data/examples/basic.rb +2 -0
- data/lib/dry-logic.rb +2 -0
- data/lib/dry/logic.rb +2 -0
- data/lib/dry/logic/appliable.rb +2 -0
- data/lib/dry/logic/evaluator.rb +2 -0
- data/lib/dry/logic/operations.rb +2 -0
- data/lib/dry/logic/operations/abstract.rb +5 -3
- data/lib/dry/logic/operations/and.rb +3 -1
- data/lib/dry/logic/operations/attr.rb +2 -0
- data/lib/dry/logic/operations/binary.rb +2 -0
- data/lib/dry/logic/operations/check.rb +3 -1
- data/lib/dry/logic/operations/each.rb +2 -0
- data/lib/dry/logic/operations/implication.rb +2 -0
- data/lib/dry/logic/operations/key.rb +4 -2
- data/lib/dry/logic/operations/negation.rb +2 -0
- data/lib/dry/logic/operations/or.rb +2 -0
- data/lib/dry/logic/operations/set.rb +2 -0
- data/lib/dry/logic/operations/unary.rb +2 -0
- data/lib/dry/logic/operations/xor.rb +2 -0
- data/lib/dry/logic/operators.rb +2 -0
- data/lib/dry/logic/predicates.rb +39 -21
- data/lib/dry/logic/result.rb +2 -0
- data/lib/dry/logic/rule.rb +5 -3
- data/lib/dry/logic/rule/interface.rb +2 -0
- data/lib/dry/logic/rule/predicate.rb +2 -0
- data/lib/dry/logic/rule_compiler.rb +2 -0
- data/lib/dry/logic/version.rb +3 -1
- data/spec/integration/result_spec.rb +2 -0
- data/spec/integration/rule_spec.rb +2 -0
- data/spec/shared/predicates.rb +2 -0
- data/spec/shared/rule.rb +2 -0
- data/spec/spec_helper.rb +6 -10
- data/spec/support/mutant.rb +2 -0
- data/spec/unit/operations/and_spec.rb +2 -0
- data/spec/unit/operations/attr_spec.rb +2 -0
- data/spec/unit/operations/check_spec.rb +2 -0
- data/spec/unit/operations/each_spec.rb +2 -0
- data/spec/unit/operations/implication_spec.rb +2 -0
- data/spec/unit/operations/key_spec.rb +2 -0
- data/spec/unit/operations/negation_spec.rb +2 -0
- data/spec/unit/operations/or_spec.rb +2 -0
- data/spec/unit/operations/set_spec.rb +2 -0
- data/spec/unit/operations/xor_spec.rb +2 -0
- data/spec/unit/predicates/array_spec.rb +2 -0
- data/spec/unit/predicates/attr_spec.rb +2 -0
- data/spec/unit/predicates/bool_spec.rb +3 -1
- data/spec/unit/predicates/bytesize_spec.rb +48 -0
- data/spec/unit/predicates/case_spec.rb +2 -0
- data/spec/unit/predicates/date_spec.rb +2 -0
- data/spec/unit/predicates/date_time_spec.rb +3 -1
- data/spec/unit/predicates/decimal_spec.rb +3 -1
- data/spec/unit/predicates/empty_spec.rb +2 -0
- data/spec/unit/predicates/eql_spec.rb +2 -0
- data/spec/unit/predicates/even_spec.rb +2 -0
- data/spec/unit/predicates/excluded_from_spec.rb +2 -0
- data/spec/unit/predicates/excludes_spec.rb +2 -0
- data/spec/unit/predicates/false_spec.rb +3 -1
- data/spec/unit/predicates/filled_spec.rb +2 -0
- data/spec/unit/predicates/float_spec.rb +2 -0
- data/spec/unit/predicates/format_spec.rb +12 -2
- data/spec/unit/predicates/gt_spec.rb +2 -0
- data/spec/unit/predicates/gteq_spec.rb +2 -0
- data/spec/unit/predicates/hash_spec.rb +40 -0
- data/spec/unit/predicates/included_in_spec.rb +2 -0
- data/spec/unit/predicates/int_spec.rb +2 -0
- data/spec/unit/predicates/key_spec.rb +2 -0
- data/spec/unit/predicates/lt_spec.rb +2 -0
- data/spec/unit/predicates/lteq_spec.rb +2 -0
- data/spec/unit/predicates/max_bytesize_spec.rb +39 -0
- data/spec/unit/predicates/max_size_spec.rb +2 -0
- data/spec/unit/predicates/min_bytesize_spec.rb +39 -0
- data/spec/unit/predicates/min_size_spec.rb +2 -0
- data/spec/unit/predicates/none_spec.rb +2 -0
- data/spec/unit/predicates/not_eql_spec.rb +2 -0
- data/spec/unit/predicates/number_spec.rb +2 -0
- data/spec/unit/predicates/odd_spec.rb +2 -0
- data/spec/unit/predicates/respond_to_spec.rb +31 -0
- data/spec/unit/predicates/size_spec.rb +2 -0
- data/spec/unit/predicates/str_spec.rb +2 -0
- data/spec/unit/predicates/time_spec.rb +3 -1
- data/spec/unit/predicates/true_spec.rb +3 -1
- data/spec/unit/predicates/type_spec.rb +2 -0
- data/spec/unit/predicates/uuid_v4_spec.rb +29 -0
- data/spec/unit/predicates_spec.rb +2 -0
- data/spec/unit/rule/predicate_spec.rb +2 -0
- data/spec/unit/rule_compiler_spec.rb +2 -0
- data/spec/unit/rule_spec.rb +6 -4
- metadata +30 -6
- data/.travis.yml +0 -31
data/Gemfile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
gemspec
|
@@ -10,4 +12,5 @@ group :tools do
|
|
10
12
|
gem 'pry-byebug', platform: :mri
|
11
13
|
gem 'benchmark-ips', platform: :mri
|
12
14
|
gem 'hotch', platform: :mri
|
15
|
+
gem 'ossy', git: 'https://github.com/solnic/ossy.git', branch: 'master'
|
13
16
|
end
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/dry-logic
|
2
|
-
[
|
2
|
+
[ci]: https://github.com/dry-rb/dry-logic/actions?query=workflow%3Aci
|
3
3
|
[codeclimate]: https://codeclimate.com/github/dry-rb/dry-logic
|
4
4
|
[chat]: https://dry-rb.zulipchat.com
|
5
5
|
[inchpages]: http://inch-ci.org/github/dry-rb/dry-logic
|
@@ -7,20 +7,20 @@
|
|
7
7
|
# dry-logic [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
|
8
8
|
|
9
9
|
[![Gem Version](https://badge.fury.io/rb/dry-logic.svg)][gem]
|
10
|
-
[![Build Status](https://
|
10
|
+
[![Build Status](https://github.com/dry-rb/dry-logic/workflows/ci/badge.svg)][ci]
|
11
11
|
[![Code Climate](https://codeclimate.com/github/dry-rb/dry-logic/badges/gpa.svg)][codeclimate]
|
12
12
|
[![Test Coverage](https://codeclimate.com/github/dry-rb/dry-logic/badges/coverage.svg)][codeclimate]
|
13
13
|
[![Inline docs](http://inch-ci.org/github/dry-rb/dry-logic.svg?branch=master)][inchpages]
|
14
14
|
|
15
15
|
Predicate logic and rule composition used by:
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
- [dry-types](https://github.com/dry-rb/dry-types) for constrained types
|
18
|
+
- [dry-schema](https://github.com/dry-rb/dry-schema) and [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules
|
19
|
+
- your project...?
|
20
20
|
|
21
21
|
## Links
|
22
22
|
|
23
|
-
|
23
|
+
- [Documentation](http://dry-rb.org/gems/dry-logic)
|
24
24
|
|
25
25
|
## Contributing
|
26
26
|
|
data/Rakefile
CHANGED
data/benchmarks/setup.rb
CHANGED
data/bin/console
CHANGED
@@ -0,0 +1,54 @@
|
|
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
|
+
```
|
@@ -0,0 +1,62 @@
|
|
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
|
+
```
|
@@ -0,0 +1,102 @@
|
|
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
|
+
```
|
data/dry-logic.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('../lib/dry/logic/version', __FILE__)
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
@@ -12,6 +14,12 @@ Gem::Specification.new do |spec|
|
|
12
14
|
spec.files = `git ls-files -z`.split("\x0")
|
13
15
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
14
16
|
spec.require_paths = ['lib']
|
17
|
+
spec.required_ruby_version = '>= 2.4.0'
|
18
|
+
|
19
|
+
spec.metadata = {
|
20
|
+
'source_code_uri' => 'https://github.com/dry-rb/dry-logic',
|
21
|
+
'changelog_uri' => 'https://github.com/dry-rb/dry-logic/blob/master/CHANGELOG.md'
|
22
|
+
}
|
15
23
|
|
16
24
|
spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
|
17
25
|
spec.add_runtime_dependency 'dry-core', '~> 0.2'
|
data/examples/basic.rb
CHANGED
data/lib/dry-logic.rb
CHANGED
data/lib/dry/logic.rb
CHANGED
data/lib/dry/logic/appliable.rb
CHANGED
data/lib/dry/logic/evaluator.rb
CHANGED
data/lib/dry/logic/operations.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/core/constants'
|
2
4
|
require 'dry/equalizer'
|
3
5
|
require 'dry/logic/operators'
|
@@ -24,15 +26,15 @@ module Dry
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def curry(*args)
|
27
|
-
new(rules.map { |rule| rule.curry(*args) }, options)
|
29
|
+
new(rules.map { |rule| rule.curry(*args) }, **options)
|
28
30
|
end
|
29
31
|
|
30
32
|
def new(rules, **new_options)
|
31
|
-
self.class.new(*rules, options
|
33
|
+
self.class.new(*rules, **options, **new_options)
|
32
34
|
end
|
33
35
|
|
34
36
|
def with(new_options)
|
35
|
-
new(rules, options
|
37
|
+
new(rules, **options, **new_options)
|
36
38
|
end
|
37
39
|
|
38
40
|
def to_ast
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/logic/operations/binary'
|
2
4
|
require 'dry/logic/result'
|
3
5
|
|
@@ -7,7 +9,7 @@ module Dry
|
|
7
9
|
class And < Binary
|
8
10
|
attr_reader :hints
|
9
11
|
|
10
|
-
def initialize(
|
12
|
+
def initialize(*, **)
|
11
13
|
super
|
12
14
|
@hints = options.fetch(:hints, true)
|
13
15
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'dry/logic/operations/unary'
|
2
4
|
require 'dry/logic/evaluator'
|
3
5
|
require 'dry/logic/result'
|
@@ -15,7 +17,7 @@ module Dry
|
|
15
17
|
keys = options.fetch(:keys)
|
16
18
|
evaluator = Evaluator::Set.new(keys)
|
17
19
|
|
18
|
-
super(rule, options
|
20
|
+
super(rule, **options, evaluator: evaluator)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|