dry-rb / dry-logic Goto Github PK
View Code? Open in Web Editor NEWPredicate logic with rule composition
Home Page: https://dry-rb.org/gems/dry-logic/
License: MIT License
Predicate logic with rule composition
Home Page: https://dry-rb.org/gems/dry-logic/
License: MIT License
It would be useful if we could add some new predicates to the base set. I have some time at the end of this week so can raise a PR, though I will leave number?
to you as I know you have plans and it's likely to involve something with dry-types...
number?
Useful when you aren't bothered what kind of number something is, just that it is a number (a bit like active records numericality
validator).
odd? & even?
These options are built into active record validations and it doesn't seem unreasonable to include in base predicates.
We could probably make use of the ruby methods for .odd?
and .even?
.
Currently the predicate AST simply includes an array of the argument values (excluding the input) as follows:
[:predicate, [:eql?, [1]]
[:predicate, [:filled?, []]
I see a couple of problems with this.
I propose the following change to the AST to resolve the above.
Rather than including an array of the curried argument values lets include a nested array of all the arguments with argument name as the first element and the value as the second. For example:
[:predicate, [:eql?, [[:left, 1], [:right, 2]]]
[:predicate, [:filled?, [[:input, "hello"]]]
The determination of argument names should be dynamic and support custom predicates.
The current inclusion and exclusion predicates take a list and check that the input is included in that list.
I have just run into a use case where I want to turn this on its head and instead check that the input includes a value.
#current predicate:
predicate(:inclusion?) do |list, input|
list.include?(input)
end
#what I need...
predicate(:some_new_predicate?) do |value, input|
input.include?(value)
end
Does this seem like something worth adding to the library?
In terms of names I guess we either go for :includes?
and :excludes?
the idea being that we are checking if an input includes or excludes something. However they might be a little similar to the current predicates...
Can we build predicate which can validate attributes against nested predicates?
It'd be useful for Dry::Struct, something like:
class User < Dry::Struct
attribute :age, Types::Strict::Int
end
Adult = User.constrained(attribute: {age: {gteq: 18}})
Of course, we can invent better DSL
Hi,
@solnic and I recently discussed the possibility of adding success feedback to the AST after calling an operation.
I understand success is not currently returned as it will have an effect on the performance of the response. We use dry logic in a rules engine and we'd be willing to forfeit performance a little in exchange for the ability to provide richer feedback to our users.
We discussed the possibility of passing an optional value (possibly to #with along optional hints) to enable success feedback if needed.
If this is something that could be useful to others, I'd love to help implement this functionality.
Hello.
Using dry-logic 1.4.0, irb 1.5.0.
As I mentioned in ruby/irb#455, a session crashes if irb's autocomplete feature is enabled.
Type AnyClass.new.
in console.
Other details in irb issue link.
No session crash.
Can't use version 0.5.0. Method nil?
was called without an argument.
ArgumentError: wrong number of arguments (given 0, expected 1)
/home/anatoly/.gem/ruby/2.6.0/gems/dry-logic-0.5.0/lib/dry/logic/predicates.rb:17:in `nil?'
/home/anatoly/.gem/ruby/2.6.0/gems/dry-configurable-0.7.0/lib/dry/configurable.rb:110:in `setting'
/home/anatoly/.gem/ruby/2.6.0/gems/dry-validation-0.12.2/lib/dry/validation/schema/class_interface.rb:15:in `<class:Schema>'
/home/anatoly/.gem/ruby/2.6.0/gems/dry-validation-0.12.2/lib/dry/validation/schema/class_interface.rb:7:in `<module:Validation>'
/home/anatoly/.gem/ruby/2.6.0/gems/dry-validation-0.12.2/lib/dry/validation/schema/class_interface.rb:6:in `<module:Dry>'
/home/anatoly/.gem/ruby/2.6.0/gems/dry-validation-0.12.2/lib/dry/validation/schema/class_interface.rb:5:in `<top (required)>'
This error Error: undefined method 'build' for Dry::Logic::Rule::Predicate:Class
is coming when the build
method is called here https://github.com/dry-rb/dry-types/blob/main/lib/dry/types/constraints.rb#L16. Any idea what is wrong here?
Provide detailed steps to reproduce, an executable script would be best.
A clear and concise description of what you expected to happen.
Hi,
When upgrading trailblazer I got dry-logic upgraded as well. As part of the upgrade, method none?
became an alias to a new method nil?
, which implements the same logic.I assume this alias was there in order to keep backward compatibility and not to break the API:
https://github.com/dry-rb/dry-logic/compare/v0.4.2..v0.5.0#diff-e2e34652eb2e31cc606bab1f22f63c82R17
However, This breaks Ruby's nil method: If calling applications called nil?
assuming they are calling Ruby's nil?
method, they now call dry-logic's nil?
method, which requires one argument thus fails:
.../gems/dry-logic-0.5.0/lib/dry/logic/predicates.rb:17:in `nil?': wrong number of arguments (given 0, expected 1) (ArgumentError
.../gems/dry-configurable-0.7.0/lib/dry/configurable.rb:111:in `setting'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema/class_interface.rb:14:in `<class:Schema>'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema/class_interface.rb:7:in `<module:Validation>'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema/class_interface.rb:6:in `<module:Dry>'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema/class_interface.rb:5:in `<top (required)>'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema.rb:15:in `require'
.../gems/dry-validation-0.10.7/lib/dry/validation/schema.rb:15:in `<top (required)>'
.../gems/dry-validation-0.10.7/lib/dry/validation.rb:39:in `require'
.../gems/dry-validation-0.10.7/lib/dry/validation.rb:39:in `<top (required)>'
.../gems/dry-validation-0.10.7/lib/dry-validation.rb:1:in `require'
.../gems/dry-validation-0.10.7/lib/dry-validation.rb:1:in `<top (required)>'
UUIDs are widely used. Would you be open to adding a built-in predicate; something like this?
require 'uuidtools'
predicate(:uuid?) do |value|
UUIDTools::UUID.parse(value).valid?
rescue ArgumentError, TypeError
false
end
I'm happy to make a PR with tests if the idea is accepted.
I have multiple projects now which use dry-validation. I am finding myself frequently reaching for this custom predicate in each:
module MyProject
module Predicates
include Dry::Logic::Predicates
# Based on https://en.wikipedia.org/wiki/Whitespace_character#Unicode
# Matches official ASCII and Unicode whitespace as well as characters
# which are effectively whitespace characters
effective_whitespace = "\u200B\u200C\u200D\u2060\uFEFF"
WHITESPACE_PATTERN = /\A[[:space:]#{effective_whitespace}]*\z/
def self.contains_text?(string)
WHITESPACE_PATTERN !~ string
end
predicate(:contains_text?, &public_method(:contains_text?))
end
end
Usage is usually
Dry::Validation.Schema do
configure { config.type_specs = true }
required(:foo, :str).filled(:str?, :contains_text?)
end
Basically I want to assert that the value is a non-empty string with at least one character that isn't whitespace.
This seems generic enough to be a built-in predicate
It's not my code breaking so I'm just describing it from the outside:
~/gems/dry-logic-1.1.0/lib/dry/logic/predicates.rb:181:in `eql?': wrong number of arguments (given 1, expected 2) (ArgumentError)
from /usr/local/ruby/lib/ruby/3.0.0/set.rb:247:in `include?'
from /~/gems/tapioca-0.5.4/lib/tapioca/compilers/dsl/base.rb:33:in `handles?'
Just based on the error and backtrace, I believe defining eql?
is a bad idea because it's a method used by lots of low-level code, for checking identity "hash equality".
I believe it would be best if that method was renamed (or at least behaved like Object#eql?
when called with a single argument).
Currently we create success and failure results by default. It would be great if we could have that configurable so that we can only trace failures and include them in the results. This will be a huge perf improvement for dry-v.
I guess it could be expected (i. e. dry-types should be updated to >~ 1.6
to make it work), but for the user it's unexpected.
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'dry-types', '1.5.1'
gem 'dry-logic', '~> 1.3'
end
module Types
include Dry.Types()
Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
Age = Integer.constrained(gt: 18)
end
$ ruby /tmp/d.rb
…/gems/dry-types-1.5.1/lib/dry/types/constraints.rb:20:in `block in Rule': undefined method `build' for Dry::Logic::Rule::Predicate:Class (NoMethodError)
Logic::Rule::Predicate.build(
^^^^^^
It works!
dry-schema
has hints
support as a plugin, disabled by default, thus we need a way to configure hint
generation here.
The tests fail in an i386 environment. The failures are reproducible:
https://salsa.debian.org/ruby-team/ruby-dry-logic/-/jobs/2211364
https://salsa.debian.org/ruby-team/ruby-dry-logic/-/jobs/2211361
Failures:
1) Dry::Logic::Predicates#size? with value size is less than n behaves like a failing predicate is expected to equal false
Failure/Error: expect(predicate.call(*args)).to be(false)
expected false
got true
Shared Example Group: "a failing predicate" called from ./spec/unit/predicates/size_spec.rb:48
# ./spec/shared/predicates.rb:56:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:55:in `each'
# ./spec/shared/predicates.rb:55:in `block (2 levels) in <top (required)>'
2) Dry::Logic::Predicates#size? when value size is greater than n behaves like a failing predicate is expected to equal false
Failure/Error: expect(predicate.call(*args)).to be(false)
expected false
got true
Shared Example Group: "a failing predicate" called from ./spec/unit/predicates/size_spec.rb:34
# ./spec/shared/predicates.rb:56:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:55:in `each'
# ./spec/shared/predicates.rb:55:in `block (2 levels) in <top (required)>'
3) Dry::Logic::Predicates#size? when value size is equal to n behaves like a passing predicate is expected to equal true
Got 2 failures:
Shared Example Group: "a passing predicate" called from ./spec/unit/predicates/size_spec.rb:20
3.1) Failure/Error: expect(predicate.call(*args)).to be(true)
expected true
got false
# ./spec/shared/predicates.rb:46:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:45:in `each'
# ./spec/shared/predicates.rb:45:in `block (2 levels) in <top (required)>'
3.2) Failure/Error: expect(predicate.call(*args)).to be(true)
expected true
got false
# ./spec/shared/predicates.rb:46:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:45:in `each'
# ./spec/shared/predicates.rb:45:in `block (2 levels) in <top (required)>'
4) Dry::Logic::Predicates#min_size? when value size is greater than n behaves like a passing predicate is expected to equal true
Failure/Error: expect(predicate.call(*args)).to be(true)
expected true
got false
Shared Example Group: "a passing predicate" called from ./spec/unit/predicates/min_size_spec.rb:20
# ./spec/shared/predicates.rb:46:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:45:in `each'
# ./spec/shared/predicates.rb:45:in `block (2 levels) in <top (required)>'
5) Dry::Logic::Predicates#min_size? when value size is equal to n behaves like a passing predicate is expected to equal true
Failure/Error: expect(predicate.call(*args)).to be(true)
expected true
got false
Shared Example Group: "a passing predicate" called from ./spec/unit/predicates/min_size_spec.rb:34
# ./spec/shared/predicates.rb:46:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:45:in `each'
# ./spec/shared/predicates.rb:45:in `block (2 levels) in <top (required)>'
6) Dry::Logic::Predicates#max_size? with value size is greater than n behaves like a failing predicate is expected to equal false
Failure/Error: expect(predicate.call(*args)).to be(false)
expected false
got true
Shared Example Group: "a failing predicate" called from ./spec/unit/predicates/max_size_spec.rb:48
# ./spec/shared/predicates.rb:56:in `block (3 levels) in <top (required)>'
# ./spec/shared/predicates.rb:55:in `each'
# ./spec/shared/predicates.rb:55:in `block (2 levels) in <top (required)>'
Finished in 0.32758 seconds (files took 0.42136 seconds to load)
399 examples, 6 failures
Moved from dry-validation see dry-rb/dry-validation#150
For :includes
and :excludes
and predicates I propose the following handling of nil and unsupported input types (e.g. Integers)
Includes should return false as the input cannot include the desired value
Excludes should return true as for the same reason as the above.
A greater discussion should be had around other predicates where this may apply (e.g. :gt, :lt etc.)
schema = Dry::Validation.Form{
required(:awesome).filled(:str?)
}
puts schema.rules[:awesome]
puts schema.rules[:awesome].inspect
puts schema.rules[:awesome].to_ast
key?(:awesome) AND key[awesome](filled?) AND key[awesome](str?)
#<Dry::Logic::Operations::And rules=[#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#key?> options={:args=>[:awesome], :id=>:awesome}>, #<Dry::Logic::Operations::And rules=[#<Dry::Logic::Operations::Key rules=[#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#filled?> options={:args=>[]}>] options={:name=>:awesome, :evaluator=>#<Dry::Logic::Evaluator::Key path=[:awesome]>, :path=>:awesome, :id=>:awesome}>, #<Dry::Logic::Operations::Key rules=[#<Dry::Logic::Rule::Predicate predicate=#<Method: Module(Dry::Logic::Predicates::Methods)#str?> options={:args=>[]}>] options={:name=>:awesome, :evaluator=>#<Dry::Logic::Evaluator::Key path=[:awesome]>, :path=>:awesome, :id=>:awesome}>] options={:id=>:awesome}>] options={:id=>:awesome}>
NoMethodError: undefined method `[]' for Undefined:Object
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/evaluator.rb:31:in `block in call'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/evaluator.rb:31:in `each'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/evaluator.rb:31:in `reduce'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/evaluator.rb:31:in `call'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/operations/key.rb:54:in `ast'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/operations/binary.rb:17:in `ast'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/operations/binary.rb:17:in `ast'
./vendor/bundle/gems/dry-logic-0.4.0/lib/dry/logic/operations/abstract.rb:39:in `to_ast'
To facilitate new comers I think is a great idea to have and executable with everything loaded, so they can start playing around with dry-logic
.
What do you think ?
As discussed in #55, its current name overrides Object#respond_to?
in an incompatible fashion.
We can rename it to responds_to?
, for example. Just like includes?
delegates to include?
.
I'm currently attempting to create custom predicates by writing this code:
require 'dry-validation'
module Predicates
include Dry::Logic::Predicates
predicate(:uuid?) do |value|
!Types::UUID_REGEX.match(value.to_s).nil?
end
end
class BaseContract < Dry::Validation::Contract
include Predicates
params do
required(:aggregate_id)
required(:section_id).filled(:uuid?)
end
end
However, this code raises an exception:
dry-schema-0.5.1/lib/dry/schema/trace.rb:104:in `method_missing': uuid? predicate is not defined (ArgumentError)
What am I doing wrong here?
I couldn't find any documentation for this in dry-validation, or dry-logic.
Gemfile
source 'https://rubygems.org'
gem 'dry-validation', '1.0.0.beta2'
Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (1.1.5)
dry-configurable (0.8.2)
concurrent-ruby (~> 1.0)
dry-core (~> 0.4, >= 0.4.7)
dry-container (0.7.0)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.4.7)
concurrent-ruby (~> 1.0)
dry-equalizer (0.2.2)
dry-inflector (0.1.2)
dry-initializer (2.5.0)
dry-logic (0.6.1)
concurrent-ruby (~> 1.0)
dry-core (~> 0.2)
dry-equalizer (~> 0.2)
dry-schema (0.5.1)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.8, >= 0.8.0)
dry-core (~> 0.2, >= 0.2.1)
dry-equalizer (~> 0.2)
dry-initializer (~> 2.4)
dry-logic (~> 0.6, >= 0.6.0)
dry-types (~> 0.15, >= 0.15.0)
dry-types (0.15.0)
concurrent-ruby (~> 1.0)
dry-container (~> 0.3)
dry-core (~> 0.4, >= 0.4.4)
dry-equalizer (~> 0.2, >= 0.2.2)
dry-inflector (~> 0.1, >= 0.1.2)
dry-logic (~> 0.5, >= 0.5)
dry-validation (1.0.0.beta2)
concurrent-ruby (~> 1.0)
dry-core (~> 0.2, >= 0.2.1)
dry-equalizer (~> 0.2)
dry-initializer (~> 2.5)
dry-schema (~> 0.5, >= 0.5)
PLATFORMS
ruby
DEPENDENCIES
dry-validation (= 1.0.0.beta2)
BUNDLED WITH
2.0.1
There are some things missing from the library that is really useful
dry-v now adds its own :error nodes, this should not be needed if dry-logic had a better AST; so ie we could add :failure and :success result nodes, and see where we don’t need to provide result “names” (which can be a symbol or a path array) so that the AST could shrink
tweak things a bit so we could apply rules in 2 modes: full tracing of success/failures or failures exclusively - and this will make dry-v work super fast even when everything is valid (it’s currently a huge bottleneck).
I'll try to come up with some specific node types soon and post it here.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.