Coder Social home page Coder Social logo

dry-rb / dry-logic Goto Github PK

View Code? Open in Web Editor NEW
179.0 18.0 66.0 746 KB

Predicate logic with rule composition

Home Page: https://dry-rb.org/gems/dry-logic/

License: MIT License

Ruby 98.99% HTML 1.01%
dry-rb predicates predicate-logic rule-engine ruby rubygem gem library

dry-logic's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dry-logic's Issues

Error: undefined method `build' for Dry::Logic::Rule::Predicate:Class

Describe the bug

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?

To Reproduce

Provide detailed steps to reproduce, an executable script would be best.

Expected behavior

A clear and concise description of what you expected to happen.

My environment

  • Affects my production application: YES/NO
  • Ruby version: ...
  • OS: ...

Add a `text?` predicate for validating that a string contains non-whitespace characters

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

Success feedback within AST

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.

`nil?` method with argument doesn't work

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)>'

Inclusion & exclusion 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...

Additional predicates

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?.

nil input causes predicate errors

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.)

Test failures on i386 architecture

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

Cannot get AST from rules defined in a dry-validation schema

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'

IRB crash

Describe the bug

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.

To Reproduce

Type AnyClass.new. in console.
Other details in irb issue link.

Expected behavior

No session crash.

Include argmument names in predicate AST

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.

  1. It is hard to read, from the ast alone I should be able to understand what is being passed into the predicate. In addition, the predicate ast only includes the curried arguments, not the called ones.
  2. dry-validation uses the ast to build error messages. Because the AST doesn't provide argument names we end up having to implement methods for each predicate to provide variables to the error messages. This is brittle and means that custom predicates are only given the input value & not the value of other arguments.

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.

`respond_to?` predicate needs to be renamed

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?.

uuid? predicate

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.

Improved result AST

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.

`dry-types 1.5.1` is broken with `dry-logic ~>1.3`

Describe the bug

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.

To Reproduce

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(
                                ^^^^^^

Expected behavior

It works!

My environment

  • Affects my production application: kinda? Not really, but dependabot PR for dry-types cannot pass CI because of that.
  • Ruby version: 3.1.2
  • OS: linux

Nested 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

New `nil?` method break API

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)>'

`Predicate#eql?(left, right)` breaks lots of conventions

Describe the bug

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).

Configurable result tracing

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.

Add bin/console

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 ?

dry-logic needs new docs

There are some things missing from the library that is really useful

  • Predicates
  • Operations
  • More examples

How do I create custom predicates?

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.