dry-logic 0.4.1 → 0.5.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.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.travis.yml +19 -25
- data/CHANGELOG.md +71 -46
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +0 -1
- data/README.md +4 -29
- data/bin/console +10 -0
- data/dry-logic.gemspec +1 -3
- data/examples/basic.rb +5 -10
- data/lib/dry/logic/operations/key.rb +4 -4
- data/lib/dry/logic/operations/negation.rb +5 -1
- data/lib/dry/logic/predicates.rb +18 -3
- data/lib/dry/logic/rule.rb +2 -2
- data/lib/dry/logic/rule_compiler.rb +2 -0
- data/lib/dry/logic/version.rb +1 -1
- data/spec/shared/predicates.rb +5 -1
- data/spec/spec_helper.rb +12 -4
- data/spec/unit/operations/key_spec.rb +14 -0
- data/spec/unit/operations/negation_spec.rb +9 -0
- data/spec/unit/operations/or_spec.rb +5 -5
- data/spec/unit/predicates/case_spec.rb +33 -0
- data/spec/unit/predicates/eql_spec.rb +1 -1
- data/spec/unit/predicates/includes_spec.rb +14 -46
- data/spec/unit/predicates/none_spec.rb +2 -2
- data/spec/unit/rule_spec.rb +52 -5
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1a6ff887f61b513496991a96d7903685886ab29dae71cb4f75185cd7866fd4b2
|
4
|
+
data.tar.gz: 9e063f346341b395ac21645f84319e619af19151e62bd97413badf246440d353
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f643a0238e3b67c91c83af1e90cb4398279b4c01957ce663a5186ae8437a87e94c9d9cb2f7a2aabca1c7edb47be28a531317a044e58e270ffff59c44e6a9ccf5
|
7
|
+
data.tar.gz: 17b0ff20fc5c41258a537ad0c57e959cd3359765801261157772a62ee5d1ce525c15bca2ce92c8ee624b1fda67a5cf6cbf4228af552cf99efc8e8428026dbccb
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,36 +1,30 @@
|
|
1
1
|
language: ruby
|
2
|
-
dist: trusty
|
3
|
-
sudo: false
|
4
2
|
cache: bundler
|
5
|
-
|
3
|
+
sudo: false
|
4
|
+
bundler_args: --without benchmarks tools
|
5
|
+
before_install: gem update --system
|
6
|
+
before_script:
|
7
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
8
|
+
- chmod +x ./cc-test-reporter
|
9
|
+
- ./cc-test-reporter before-build
|
10
|
+
after_script:
|
11
|
+
- "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
|
6
12
|
script:
|
7
|
-
- bundle exec rake
|
8
|
-
before_install:
|
9
|
-
- gem update --system
|
10
|
-
- rvm @global do gem uninstall bundler -a -x
|
11
|
-
- rvm @global do gem install bundler -v 1.13.7
|
12
|
-
after_success:
|
13
|
-
# Send coverage report from the job #1 == current MRI release
|
14
|
-
- '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
|
13
|
+
- bundle exec rake
|
15
14
|
rvm:
|
16
|
-
- 2.
|
17
|
-
- 2.
|
18
|
-
- 2.
|
19
|
-
-
|
20
|
-
-
|
21
|
-
- rbx-3
|
15
|
+
- 2.6.0
|
16
|
+
- 2.5.3
|
17
|
+
- 2.4.5
|
18
|
+
- 2.3.8
|
19
|
+
- jruby-9.2.5.0
|
22
20
|
env:
|
23
21
|
global:
|
24
|
-
-
|
25
|
-
matrix:
|
26
|
-
allow_failures:
|
27
|
-
- rvm: ruby-head
|
28
|
-
- rvm: rbx-3
|
22
|
+
- COVERAGE=true
|
29
23
|
notifications:
|
30
24
|
email: false
|
31
25
|
webhooks:
|
32
26
|
urls:
|
33
27
|
- https://webhooks.gitter.im/e/19098b4253a72c9796db
|
34
|
-
on_success: change
|
35
|
-
on_failure: always
|
36
|
-
on_start: false
|
28
|
+
on_success: change # options: [always|never|change] default: always
|
29
|
+
on_failure: always # options: [always|never|change] default: always
|
30
|
+
on_start: false # default: false
|
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,39 @@
|
|
1
|
+
# v0.5.0 2019-01-29
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- `:nil?` predicate (`none?` is now an alias) (solnic)
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
- `Operation::Key#ast` will now return a correct AST with non-Undefined inputs (solnic)
|
10
|
+
|
11
|
+
[Compare v0.4.2...v0.5.0](https://github.com/dry-rb/dry-logic/compare/v0.4.2...v0.5.0)
|
12
|
+
|
13
|
+
# v0.4.2 2017-09-15
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- New `:case?` predicate matches a value against the given object with `#===` (flash-gordon)
|
18
|
+
- New `:is?` predicate checks objects identity (using `#equal?`) (flash-gordon)
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- A bug with using custom predicates within a standalone module in `dry-validation` (flash-gordon)
|
23
|
+
|
24
|
+
[Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-logic/compare/v0.4.1...v0.4.2)
|
25
|
+
|
1
26
|
# v0.4.1 2017-01-23
|
2
27
|
|
3
28
|
### Changed
|
4
29
|
|
5
|
-
|
30
|
+
- Predicates simply reuse other predicate methods instead of referring to them via `#[]` (georgemillo)
|
6
31
|
|
7
32
|
### Fixed
|
8
33
|
|
9
|
-
|
34
|
+
- Warnings on MRI 2.4.0 are gone (jtippett)
|
10
35
|
|
11
|
-
[Compare v0.4.0...v0.4.1](https://github.com/
|
36
|
+
[Compare v0.4.0...v0.4.1](https://github.com/dry-rb/dry-logic/compare/v0.4.0...v0.4.1)
|
12
37
|
|
13
38
|
# v0.4.0 2016-09-21
|
14
39
|
|
@@ -16,116 +41,116 @@ This is a partial rewrite focused on internal clean up and major performance imp
|
|
16
41
|
|
17
42
|
### Added
|
18
43
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
44
|
+
- `Rule#[]` which applies a rule and always returns `true` or `false` (solnic)
|
45
|
+
- `Rule#bind` which returns a rule with its predicate bound to a given object (solnic)
|
46
|
+
- `Rule#eval_args` which evaluates unbound-methods-args in the context of a given object (solnic)
|
47
|
+
- `Logic.Rule` builder function (solnic)
|
48
|
+
- Nice `#inspect` on rules and operation objects (solnic)
|
24
49
|
|
25
50
|
### Changed
|
26
51
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
52
|
+
- [BRAEKING] New result API (solnic)
|
53
|
+
- [BREAKING] `Predicate` is now `Rule::Predicate` (solnic)
|
54
|
+
- [BREAKING] `Rule::Conjunction` is now `Operation::And` (solnic)
|
55
|
+
- [BREAKING] `Rule::Disjunction` is now `Operation::Or` (solnic)
|
56
|
+
- [BREAKING] `Rule::ExlusiveDisjunction` is now `Operation::Xor` (solnic)
|
57
|
+
- [BREAKING] `Rule::Implication` is now `Operation::Implication` (solnic)
|
58
|
+
- [BREAKING] `Rule::Set` is now `Operation::Set` (solnic)
|
59
|
+
- [BREAKING] `Rule::Each` is now `Operation::Each` (solnic)
|
60
|
+
- [BREAKING] `Rule.new` accepts a predicate function as its first arg now (solnic)
|
61
|
+
- [BREAKING] `Rule#name` is now `Rule#id` (solnic)
|
62
|
+
- `Rule#parameters` is public now (solnic)
|
38
63
|
|
39
|
-
[Compare v0.3.0...v0.4.0](https://github.com/
|
64
|
+
[Compare v0.3.0...v0.4.0](https://github.com/dry-rb/dry-logic/compare/v0.3.0...v0.4.0)
|
40
65
|
|
41
66
|
# v0.3.0 2016-07-01
|
42
67
|
|
43
68
|
### Added
|
44
69
|
|
45
|
-
|
46
|
-
|
70
|
+
- `:type?` predicate imported from dry-types (solnic)
|
71
|
+
- `Rule#curry` interface (solnic)
|
47
72
|
|
48
73
|
### Changed
|
49
74
|
|
50
|
-
|
51
|
-
|
52
|
-
|
75
|
+
- Predicates AST now includes information about args (names & possible values) (fran-worley + solnic)
|
76
|
+
- Predicates raise errors when they are called with invalid arity (fran-worley + solnic)
|
77
|
+
- Rules no longer evaluate input twice when building result objects (solnic)
|
53
78
|
|
54
|
-
[Compare v0.2.3...v0.3.0](https://github.com/
|
79
|
+
[Compare v0.2.3...v0.3.0](https://github.com/dry-rb/dry-logic/compare/v0.2.3...v0.3.0)
|
55
80
|
|
56
81
|
# v0.2.3 2016-05-11
|
57
82
|
|
58
83
|
### Added
|
59
84
|
|
60
|
-
|
85
|
+
- `not_eql?`, `includes?`, `excludes?` predicates (fran-worley)
|
61
86
|
|
62
87
|
### Changed
|
63
88
|
|
64
|
-
|
65
|
-
|
89
|
+
- Renamed `inclusion?` to `included_in?` and deprecated `inclusion?` (fran-worley)
|
90
|
+
- Renamed `exclusion?` to `excluded_from?` and deprecated `exclusion?` (fran-worley)
|
66
91
|
|
67
|
-
[Compare v0.2.2...v0.2.3](https://github.com/
|
92
|
+
[Compare v0.2.2...v0.2.3](https://github.com/dry-rb/dry-logic/compare/v0.2.2...v0.2.3)
|
68
93
|
|
69
94
|
# v0.2.2 2016-03-30
|
70
95
|
|
71
96
|
### Added
|
72
97
|
|
73
|
-
|
98
|
+
- `number?`, `odd?`, `even?` predicates (fran-worley)
|
74
99
|
|
75
|
-
[Compare v0.2.1...v0.2.2](https://github.com/
|
100
|
+
[Compare v0.2.1...v0.2.2](https://github.com/dry-rb/dry-logic/compare/v0.2.1...v0.2.2)
|
76
101
|
|
77
102
|
# v0.2.1 2016-03-20
|
78
103
|
|
79
104
|
### Fixed
|
80
105
|
|
81
|
-
|
106
|
+
- Result AST for `Rule::Each` correctly maps elements with eql inputs (solnic)
|
82
107
|
|
83
108
|
# v0.2.0 2016-03-11
|
84
109
|
|
85
110
|
### Changed
|
86
111
|
|
87
|
-
|
112
|
+
- Entire AST has been redefined (solnic)
|
88
113
|
|
89
|
-
[Compare v0.1.4...v0.2.0](https://github.com/
|
114
|
+
[Compare v0.1.4...v0.2.0](https://github.com/dry-rb/dry-logic/compare/v0.1.4...v0.2.0)
|
90
115
|
|
91
116
|
# v0.1.4 2016-01-27
|
92
117
|
|
93
118
|
### Added
|
94
119
|
|
95
|
-
|
120
|
+
- Support for hash-names in `Check` and `Result` which can properly resolve input
|
96
121
|
from nested results (solnic)
|
97
122
|
|
98
|
-
[Compare v0.1.3...v0.1.4](https://github.com/
|
123
|
+
[Compare v0.1.3...v0.1.4](https://github.com/dry-rb/dry-logic/compare/v0.1.3...v0.1.4)
|
99
124
|
|
100
125
|
# v0.1.3 2016-01-27
|
101
126
|
|
102
127
|
### Added
|
103
128
|
|
104
|
-
|
129
|
+
- Support for resolving input from `Rule::Result` (solnic)
|
105
130
|
|
106
131
|
### Changed
|
107
132
|
|
108
|
-
|
133
|
+
- `Check` and `Result` carry original input(s) (solnic)
|
109
134
|
|
110
|
-
[Compare v0.1.2...v0.1.3](https://github.com/
|
135
|
+
[Compare v0.1.2...v0.1.3](https://github.com/dry-rb/dry-logic/compare/v0.1.2...v0.1.3)
|
111
136
|
|
112
137
|
# v0.1.2 2016-01-19
|
113
138
|
|
114
139
|
### Fixed
|
115
140
|
|
116
|
-
|
141
|
+
- `xor` returns wrapped results when used against another result-rule (solnic)
|
117
142
|
|
118
|
-
[Compare v0.1.1...v0.1.2](https://github.com/
|
143
|
+
[Compare v0.1.1...v0.1.2](https://github.com/dry-rb/dry-logic/compare/v0.1.1...v0.1.2)
|
119
144
|
|
120
145
|
# v0.1.1 2016-01-18
|
121
146
|
|
122
147
|
### Added
|
123
148
|
|
124
|
-
|
125
|
-
|
126
|
-
|
149
|
+
- `Rule::Attr` which can be applied to a data object with attr readers (SunnyMagadan)
|
150
|
+
- `Rule::Result` which can be applied to a result object (solnic)
|
151
|
+
- `true?` and `false?` predicates (solnic)
|
127
152
|
|
128
|
-
[Compare v0.1.0...v0.1.1](https://github.com/
|
153
|
+
[Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-logic/compare/v0.1.0...v0.1.1)
|
129
154
|
|
130
155
|
# v0.1.0 2016-01-11
|
131
156
|
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Issue Guidelines
|
2
|
+
|
3
|
+
## Reporting bugs
|
4
|
+
|
5
|
+
If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
|
6
|
+
|
7
|
+
## Reporting feature requests
|
8
|
+
|
9
|
+
Report a feature request **only after discussing it first on [discuss.dry-rb.org](https://discuss.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
|
10
|
+
|
11
|
+
## Reporting questions, support requests, ideas, concerns etc.
|
12
|
+
|
13
|
+
**PLEASE DON'T** - use [discuss.dry-rb.org](http://discuss.dry-rb.org) instead.
|
14
|
+
|
15
|
+
# Pull Request Guidelines
|
16
|
+
|
17
|
+
A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
|
18
|
+
|
19
|
+
Other requirements:
|
20
|
+
|
21
|
+
1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
|
22
|
+
2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
|
23
|
+
3) Add API documentation if it's a new feature
|
24
|
+
4) Update API documentation if it changes an existing feature
|
25
|
+
5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
|
26
|
+
|
27
|
+
# Asking for help
|
28
|
+
|
29
|
+
If these guidelines aren't helpful, and you're stuck, please post a message on [discuss.dry-rb.org](https://discuss.dry-rb.org).
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/dry-logic
|
2
2
|
[travis]: https://travis-ci.org/dry-rb/dry-logic
|
3
|
-
[gemnasium]: https://gemnasium.com/dry-rb/dry-logic
|
4
3
|
[codeclimate]: https://codeclimate.com/github/dry-rb/dry-logic
|
5
4
|
[coveralls]: https://coveralls.io/r/dry-rb/dry-logic
|
6
5
|
[inchpages]: http://inch-ci.org/github/dry-rb/dry-logic
|
@@ -9,7 +8,6 @@
|
|
9
8
|
|
10
9
|
[][gem]
|
11
10
|
[][travis]
|
12
|
-
[][gemnasium]
|
13
11
|
[][codeclimate]
|
14
12
|
[][codeclimate]
|
15
13
|
[][inchpages]
|
@@ -20,36 +18,13 @@ Predicate logic and rule composition used by:
|
|
20
18
|
* [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules
|
21
19
|
* your project...?
|
22
20
|
|
23
|
-
##
|
21
|
+
## Links
|
24
22
|
|
25
|
-
|
26
|
-
require 'dry/logic'
|
27
|
-
require 'dry/logic/predicates'
|
23
|
+
* [Documentation](http://dry-rb.org/gems/dry-logic)
|
28
24
|
|
29
|
-
|
25
|
+
## Contributing
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
has_min_age = Rule::Key.new(Predicates[:int?], name: [:user, :age])
|
34
|
-
& Rule::Key.new(Predicates[:gt?].curry(18), name: [:user, :age])
|
35
|
-
|
36
|
-
user_rule = user_present & has_min_age
|
37
|
-
|
38
|
-
user_rule.(user: { age: 19 })
|
39
|
-
# #<Dry::Logic::Result::Named success?=true input={:user=>{:age=>19}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:gt? args=[18, 19]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
|
40
|
-
|
41
|
-
user_rule.(user: { age: 18 })
|
42
|
-
# #<Dry::Logic::Result::Named success?=false input={:user=>{:age=>18}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:gt? args=[18, 18]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
|
43
|
-
|
44
|
-
user_rule.(user: { age: 'seventeen' }).inspect
|
45
|
-
#<Dry::Logic::Result::Named success?=false input={:user=>{:age=>"seventeen"}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:int? args=["seventeen"]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user, :age]>, :name=>[:user, :age]}>>
|
46
|
-
|
47
|
-
user_rule.(user: { }).inspect
|
48
|
-
#<Dry::Logic::Result::Named success?=false input={:user=>{}} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:filled? args=[{}]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user]>, :name=>:user}>>
|
49
|
-
|
50
|
-
puts user_rule.({}).inspect
|
51
|
-
#<Dry::Logic::Result::Named success?=false input={} rule=#<Dry::Logic::Rule::Key predicate=#<Dry::Logic::Predicate id=:filled? args=[nil]> options={:evaluator=>#<Dry::Logic::Evaluator::Key path=[:user]>, :name=>:user}>>
|
52
|
-
```
|
27
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-logic.
|
53
28
|
|
54
29
|
## License
|
55
30
|
|
data/bin/console
ADDED
data/dry-logic.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
require File.expand_path('../lib/dry/logic/version', __FILE__)
|
3
2
|
|
4
3
|
Gem::Specification.new do |spec|
|
@@ -7,11 +6,10 @@ Gem::Specification.new do |spec|
|
|
7
6
|
spec.authors = ['Piotr Solnica']
|
8
7
|
spec.email = ['piotr.solnica@gmail.com']
|
9
8
|
spec.summary = 'Predicate logic with rule composition'
|
10
|
-
spec.homepage = 'https://github.com/
|
9
|
+
spec.homepage = 'https://github.com/dry-rb/dry-logic'
|
11
10
|
spec.license = 'MIT'
|
12
11
|
|
13
12
|
spec.files = `git ls-files -z`.split("\x0")
|
14
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
13
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
14
|
spec.require_paths = ['lib']
|
17
15
|
|
data/examples/basic.rb
CHANGED
@@ -3,17 +3,12 @@ require 'dry/logic/predicates'
|
|
3
3
|
|
4
4
|
include Dry::Logic
|
5
5
|
|
6
|
-
user_present = Rule::
|
7
|
-
has_min_age = Rule::Key.new(Predicates[:int?], name: [:user, :age]) & Rule::Key.new(Predicates[:gt?].curry(18), name: [:user, :age])
|
6
|
+
user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user)
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
puts user_rule.(user: { age: 19 }).inspect
|
8
|
+
has_min_age = Operations::Key.new(Rule::Predicate.new(Predicates[:gt?]).curry(18), name: [:user, :age])
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
puts user_rule.(user: { age: 'seventeen' }).inspect
|
10
|
+
user_rule = user_present & has_min_age
|
16
11
|
|
17
|
-
puts user_rule.(user: { }).
|
12
|
+
puts user_rule.(user: { age: 19 }).success?
|
18
13
|
|
19
|
-
puts user_rule.({}).
|
14
|
+
puts user_rule.(user: { age: 18 }).success?
|
@@ -49,11 +49,11 @@ module Dry
|
|
49
49
|
rule[evaluator[hash]]
|
50
50
|
end
|
51
51
|
|
52
|
-
def ast(input =
|
53
|
-
if input
|
54
|
-
[type, [path, rule.ast(evaluator[input])]]
|
55
|
-
else
|
52
|
+
def ast(input = Undefined)
|
53
|
+
if input.equal?(Undefined) || !input.is_a?(Hash)
|
56
54
|
[type, [path, rule.ast]]
|
55
|
+
else
|
56
|
+
[type, [path, rule.ast(evaluator[input])]]
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
data/lib/dry/logic/predicates.rb
CHANGED
@@ -14,9 +14,10 @@ module Dry
|
|
14
14
|
input.kind_of?(type)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def nil?(input)
|
18
18
|
input.nil?
|
19
19
|
end
|
20
|
+
alias_method :none?, :nil?
|
20
21
|
|
21
22
|
def key?(name, input)
|
22
23
|
input.key?(name)
|
@@ -166,6 +167,10 @@ module Dry
|
|
166
167
|
left.eql?(right)
|
167
168
|
end
|
168
169
|
|
170
|
+
def is?(left, right)
|
171
|
+
left.equal?(right)
|
172
|
+
end
|
173
|
+
|
169
174
|
def not_eql?(left, right)
|
170
175
|
!left.eql?(right)
|
171
176
|
end
|
@@ -178,8 +183,18 @@ module Dry
|
|
178
183
|
value.equal?(false)
|
179
184
|
end
|
180
185
|
|
181
|
-
|
182
|
-
|
186
|
+
if RUBY_VERSION < '2.4'
|
187
|
+
def format?(regex, input)
|
188
|
+
!regex.match(input).nil?
|
189
|
+
end
|
190
|
+
else
|
191
|
+
def format?(regex, input)
|
192
|
+
regex.match?(input)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def case?(pattern, input)
|
197
|
+
pattern === input
|
183
198
|
end
|
184
199
|
|
185
200
|
def predicate(name, &block)
|
data/lib/dry/logic/rule.rb
CHANGED
@@ -62,7 +62,7 @@ module Dry
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def bind(object)
|
65
|
-
if predicate
|
65
|
+
if UnboundMethod === predicate
|
66
66
|
self.class.new(predicate.bind(object), options)
|
67
67
|
else
|
68
68
|
self.class.new(
|
@@ -73,7 +73,7 @@ module Dry
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def eval_args(object)
|
76
|
-
with(args: args.map { |arg|
|
76
|
+
with(args: args.map { |arg| UnboundMethod === arg ? arg.bind(object).() : arg })
|
77
77
|
end
|
78
78
|
|
79
79
|
def with(new_opts)
|
data/lib/dry/logic/version.rb
CHANGED
data/spec/shared/predicates.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'dry/logic/predicates'
|
2
2
|
|
3
3
|
RSpec.shared_examples 'predicates' do
|
4
|
-
let(:
|
4
|
+
let(:nil?) { Dry::Logic::Predicates[:nil?] }
|
5
5
|
|
6
6
|
let(:array?) { Dry::Logic::Predicates[:array?] }
|
7
7
|
|
@@ -30,6 +30,10 @@ RSpec.shared_examples 'predicates' do
|
|
30
30
|
let(:eql?) { Dry::Logic::Predicates[:eql?] }
|
31
31
|
|
32
32
|
let(:size?) { Dry::Logic::Predicates[:size?] }
|
33
|
+
|
34
|
+
let(:case?) { Dry::Logic::Predicates[:case?] }
|
35
|
+
|
36
|
+
let(:equal?) { Dry::Logic::Predicates[:equal?] }
|
33
37
|
end
|
34
38
|
|
35
39
|
RSpec.shared_examples 'a passing predicate' do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
if RUBY_ENGINE == 'ruby' && ENV['
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
1
|
+
if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
|
2
|
+
require 'yaml'
|
3
|
+
rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
|
4
|
+
latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
|
5
|
+
|
6
|
+
if RUBY_VERSION == latest_mri
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter '/spec/'
|
10
|
+
end
|
5
11
|
end
|
6
12
|
end
|
7
13
|
|
@@ -23,4 +29,6 @@ include Dry::Core::Constants
|
|
23
29
|
|
24
30
|
RSpec.configure do |config|
|
25
31
|
config.disable_monkey_patching!
|
32
|
+
|
33
|
+
config.warnings = true
|
26
34
|
end
|
@@ -92,6 +92,20 @@ RSpec.describe Operations::Key do
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
95
|
+
describe '#ast' do
|
96
|
+
it 'returns ast without the input' do
|
97
|
+
expect(operation.ast).to eql(
|
98
|
+
[:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, Undefined]]]]]]
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns ast with the input' do
|
103
|
+
expect(operation.ast(user: 'jane')).to eql(
|
104
|
+
[:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, 'jane']]]]]]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
95
109
|
describe '#and' do
|
96
110
|
subject(:operation) do
|
97
111
|
Operations::Key.new(Rule::Predicate.new(str?), name: [:user, :name])
|
@@ -10,6 +10,15 @@ RSpec.describe Operations::Negation do
|
|
10
10
|
expect(operation.('19')).to be_success
|
11
11
|
expect(operation.(17)).to be_failure
|
12
12
|
end
|
13
|
+
|
14
|
+
context 'double negation' do
|
15
|
+
subject(:double_negation) { Operations::Negation.new(operation) }
|
16
|
+
|
17
|
+
it 'works as rule' do
|
18
|
+
expect(double_negation.('19')).to be_failure
|
19
|
+
expect(double_negation.(17)).to be_success
|
20
|
+
end
|
21
|
+
end
|
13
22
|
end
|
14
23
|
|
15
24
|
describe '#to_ast' do
|
@@ -3,7 +3,7 @@ RSpec.describe Operations::Or do
|
|
3
3
|
|
4
4
|
include_context 'predicates'
|
5
5
|
|
6
|
-
let(:left) { Rule::Predicate.new(
|
6
|
+
let(:left) { Rule::Predicate.new(nil?) }
|
7
7
|
let(:right) { Rule::Predicate.new(gt?).curry(18) }
|
8
8
|
|
9
9
|
let(:other) do
|
@@ -22,7 +22,7 @@ RSpec.describe Operations::Or do
|
|
22
22
|
it 'returns ast' do
|
23
23
|
expect(operation.to_ast).to eql(
|
24
24
|
[:or, [
|
25
|
-
[:predicate, [:
|
25
|
+
[:predicate, [:nil?, [[:input, Undefined]]]],
|
26
26
|
[:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]
|
27
27
|
]
|
28
28
|
)
|
@@ -31,7 +31,7 @@ RSpec.describe Operations::Or do
|
|
31
31
|
it 'returns result ast' do
|
32
32
|
expect(operation.(17).to_ast).to eql(
|
33
33
|
[:or, [
|
34
|
-
[:predicate, [:
|
34
|
+
[:predicate, [:nil?, [[:input, 17]]]],
|
35
35
|
[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]
|
36
36
|
]
|
37
37
|
)
|
@@ -40,7 +40,7 @@ RSpec.describe Operations::Or do
|
|
40
40
|
it 'returns failure result ast' do
|
41
41
|
expect(operation.with(id: :age).(17).to_ast).to eql(
|
42
42
|
[:failure, [:age, [:or, [
|
43
|
-
[:predicate, [:
|
43
|
+
[:predicate, [:nil?, [[:input, 17]]]],
|
44
44
|
[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]
|
45
45
|
]]]
|
46
46
|
)
|
@@ -67,7 +67,7 @@ RSpec.describe Operations::Or do
|
|
67
67
|
|
68
68
|
describe '#to_s' do
|
69
69
|
it 'returns string representation' do
|
70
|
-
expect(operation.to_s).to eql('
|
70
|
+
expect(operation.to_s).to eql('nil? OR gt?(18)')
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dry/logic/predicates'
|
2
|
+
|
3
|
+
RSpec.describe Dry::Logic::Predicates do
|
4
|
+
describe '#case?' do
|
5
|
+
let(:predicate_name) { :case? }
|
6
|
+
|
7
|
+
context 'when the value matches the pattern' do
|
8
|
+
let(:arguments_list) do
|
9
|
+
[
|
10
|
+
[11, 11],
|
11
|
+
[:odd?.to_proc, 11],
|
12
|
+
[/\Af/, 'foo'],
|
13
|
+
[Integer, 11]
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
it_behaves_like 'a passing predicate'
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the value doesn't match the pattern" do
|
21
|
+
let(:arguments_list) do
|
22
|
+
[
|
23
|
+
[13, 14],
|
24
|
+
[:odd?.to_proc, 12],
|
25
|
+
[/\Af/, 'bar'],
|
26
|
+
[String, 11]
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
it_behaves_like 'a failing predicate'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,56 +1,24 @@
|
|
1
1
|
require 'dry/logic/predicates'
|
2
2
|
|
3
|
-
RSpec.describe Dry::Logic::Predicates do
|
4
|
-
|
5
|
-
|
3
|
+
RSpec.describe Dry::Logic::Predicates, '#is?' do
|
4
|
+
let(:predicate_name) { :is? }
|
5
|
+
let(:one) { Object.new }
|
6
|
+
let(:two) { Object.new }
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
['Jill', ['Jill', 'John']],
|
11
|
-
['John', ['Jill', 'John']],
|
12
|
-
[1, 1..2],
|
13
|
-
[2, 1..2],
|
14
|
-
['Hello', 'Hello World'],
|
15
|
-
['World', 'Hello World'],
|
16
|
-
[:bar, { bar: 0 }],
|
17
|
-
[nil, [nil, false]],
|
18
|
-
[false, [nil, false]]
|
19
|
-
]
|
20
|
-
end
|
21
|
-
|
22
|
-
it_behaves_like 'a passing predicate'
|
8
|
+
context 'when value is equal to the arg' do
|
9
|
+
let(:arguments_list) do
|
10
|
+
[[one, one], [:one, :one]]
|
23
11
|
end
|
24
12
|
|
25
|
-
|
26
|
-
|
27
|
-
[
|
28
|
-
[2, 1],
|
29
|
-
[1, nil],
|
30
|
-
["foo", 1],
|
31
|
-
[1, "foo"],
|
32
|
-
[1..2, "foo"],
|
33
|
-
["foo", 1..2],
|
34
|
-
[:key, "foo"]
|
35
|
-
]
|
36
|
-
end
|
13
|
+
it_behaves_like 'a passing predicate'
|
14
|
+
end
|
37
15
|
|
38
|
-
|
16
|
+
context 'with value is not equal to the arg' do
|
17
|
+
let(:arguments_list) do
|
18
|
+
# Strings are not equal. Yet
|
19
|
+
[[one, two], ['one', 'one']]
|
39
20
|
end
|
40
21
|
|
41
|
-
|
42
|
-
let(:arguments_list) do
|
43
|
-
[
|
44
|
-
['Jack', ['Jill', 'John']],
|
45
|
-
[0, 1..2],
|
46
|
-
[3, 1..2],
|
47
|
-
['foo', 'Hello World'],
|
48
|
-
[:foo, { bar: 0 }],
|
49
|
-
[true, [nil, false]]
|
50
|
-
]
|
51
|
-
end
|
52
|
-
|
53
|
-
it_behaves_like 'a failing predicate'
|
54
|
-
end
|
22
|
+
it_behaves_like 'a failing predicate'
|
55
23
|
end
|
56
24
|
end
|
data/spec/unit/rule_spec.rb
CHANGED
@@ -4,6 +4,32 @@ RSpec.describe Dry::Logic::Rule do
|
|
4
4
|
let(:predicate) { -> { true } }
|
5
5
|
let(:options) { {} }
|
6
6
|
|
7
|
+
let(:schema) do
|
8
|
+
Class.new(BasicObject) do
|
9
|
+
define_method(:class, Kernel.instance_method(:class))
|
10
|
+
|
11
|
+
def method_missing(m, *)
|
12
|
+
if m.to_s.end_with?('?')
|
13
|
+
self.class.new
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_proc
|
20
|
+
-> value { value }
|
21
|
+
end
|
22
|
+
|
23
|
+
def arity
|
24
|
+
1
|
25
|
+
end
|
26
|
+
|
27
|
+
def parameters
|
28
|
+
[[:req, :value]]
|
29
|
+
end
|
30
|
+
end.new
|
31
|
+
end
|
32
|
+
|
7
33
|
it_behaves_like Dry::Logic::Rule
|
8
34
|
|
9
35
|
describe '.new' do
|
@@ -80,15 +106,36 @@ RSpec.describe Dry::Logic::Rule do
|
|
80
106
|
expect(bound.options[:parameters]).to eql(rule.parameters)
|
81
107
|
end
|
82
108
|
end
|
109
|
+
|
110
|
+
context 'with a schema instance' do
|
111
|
+
let(:object) { schema }
|
112
|
+
let(:predicate) { schema }
|
113
|
+
|
114
|
+
it 'returns a new with its predicate executed in the context of the provided object' do
|
115
|
+
expect(bound.(true)).to be_success
|
116
|
+
expect(bound.(false)).to be_failure
|
117
|
+
end
|
118
|
+
end
|
83
119
|
end
|
84
120
|
|
85
121
|
describe '#eval_args' do
|
86
|
-
|
87
|
-
|
88
|
-
|
122
|
+
context 'with an unbound method' do
|
123
|
+
let(:options) { { args: [1, klass.instance_method(:num), :foo] } }
|
124
|
+
let(:klass) { Class.new { def num; 7; end } }
|
125
|
+
let(:object) { klass.new }
|
89
126
|
|
90
|
-
|
91
|
-
|
127
|
+
it 'evaluates args in the context of the provided object' do
|
128
|
+
expect(rule.eval_args(object).args).to eql([1, 7, :foo])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with a schema instance' do
|
133
|
+
let(:options) { { args: [1, schema, :foo] } }
|
134
|
+
let(:object) { Object.new }
|
135
|
+
|
136
|
+
it 'returns a new with its predicate executed in the context of the provided object' do
|
137
|
+
expect(rule.eval_args(object).args).to eql([1, schema, :foo])
|
138
|
+
end
|
92
139
|
end
|
93
140
|
end
|
94
141
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-logic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.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:
|
11
|
+
date: 2019-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-core
|
@@ -114,10 +114,12 @@ files:
|
|
114
114
|
- ".rubocop_todo.yml"
|
115
115
|
- ".travis.yml"
|
116
116
|
- CHANGELOG.md
|
117
|
+
- CONTRIBUTING.md
|
117
118
|
- Gemfile
|
118
119
|
- LICENSE
|
119
120
|
- README.md
|
120
121
|
- Rakefile
|
122
|
+
- bin/console
|
121
123
|
- dry-logic.gemspec
|
122
124
|
- examples/basic.rb
|
123
125
|
- lib/dry-logic.rb
|
@@ -164,6 +166,7 @@ files:
|
|
164
166
|
- spec/unit/predicates/array_spec.rb
|
165
167
|
- spec/unit/predicates/attr_spec.rb
|
166
168
|
- spec/unit/predicates/bool_spec.rb
|
169
|
+
- spec/unit/predicates/case_spec.rb
|
167
170
|
- spec/unit/predicates/date_spec.rb
|
168
171
|
- spec/unit/predicates/date_time_spec.rb
|
169
172
|
- spec/unit/predicates/decimal_spec.rb
|
@@ -199,7 +202,7 @@ files:
|
|
199
202
|
- spec/unit/rule/predicate_spec.rb
|
200
203
|
- spec/unit/rule_compiler_spec.rb
|
201
204
|
- spec/unit/rule_spec.rb
|
202
|
-
homepage: https://github.com/
|
205
|
+
homepage: https://github.com/dry-rb/dry-logic
|
203
206
|
licenses:
|
204
207
|
- MIT
|
205
208
|
metadata: {}
|
@@ -218,8 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
221
|
- !ruby/object:Gem::Version
|
219
222
|
version: '0'
|
220
223
|
requirements: []
|
221
|
-
|
222
|
-
rubygems_version: 2.6.9
|
224
|
+
rubygems_version: 3.0.2
|
223
225
|
signing_key:
|
224
226
|
specification_version: 4
|
225
227
|
summary: Predicate logic with rule composition
|
@@ -243,6 +245,7 @@ test_files:
|
|
243
245
|
- spec/unit/predicates/array_spec.rb
|
244
246
|
- spec/unit/predicates/attr_spec.rb
|
245
247
|
- spec/unit/predicates/bool_spec.rb
|
248
|
+
- spec/unit/predicates/case_spec.rb
|
246
249
|
- spec/unit/predicates/date_spec.rb
|
247
250
|
- spec/unit/predicates/date_time_spec.rb
|
248
251
|
- spec/unit/predicates/decimal_spec.rb
|