dry-logic 0.6.0 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -5
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/ci.yml +70 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +2 -1
  10. data/.rspec +1 -0
  11. data/.rubocop.yml +89 -0
  12. data/CHANGELOG.md +51 -2
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +3 -3
  15. data/Gemfile +3 -0
  16. data/LICENSE +1 -1
  17. data/README.md +7 -7
  18. data/Rakefile +2 -0
  19. data/benchmarks/rule_application.rb +2 -0
  20. data/benchmarks/setup.rb +2 -0
  21. data/bin/console +1 -0
  22. data/docsite/source/index.html.md +54 -0
  23. data/docsite/source/operations.html.md +62 -0
  24. data/docsite/source/predicates.html.md +102 -0
  25. data/dry-logic.gemspec +8 -0
  26. data/examples/basic.rb +2 -0
  27. data/lib/dry-logic.rb +2 -0
  28. data/lib/dry/logic.rb +2 -0
  29. data/lib/dry/logic/appliable.rb +2 -0
  30. data/lib/dry/logic/evaluator.rb +2 -0
  31. data/lib/dry/logic/operations.rb +2 -0
  32. data/lib/dry/logic/operations/abstract.rb +5 -3
  33. data/lib/dry/logic/operations/and.rb +3 -1
  34. data/lib/dry/logic/operations/attr.rb +2 -0
  35. data/lib/dry/logic/operations/binary.rb +2 -0
  36. data/lib/dry/logic/operations/check.rb +3 -1
  37. data/lib/dry/logic/operations/each.rb +2 -0
  38. data/lib/dry/logic/operations/implication.rb +2 -0
  39. data/lib/dry/logic/operations/key.rb +4 -2
  40. data/lib/dry/logic/operations/negation.rb +2 -0
  41. data/lib/dry/logic/operations/or.rb +2 -0
  42. data/lib/dry/logic/operations/set.rb +2 -0
  43. data/lib/dry/logic/operations/unary.rb +2 -0
  44. data/lib/dry/logic/operations/xor.rb +2 -0
  45. data/lib/dry/logic/operators.rb +2 -0
  46. data/lib/dry/logic/predicates.rb +39 -21
  47. data/lib/dry/logic/result.rb +2 -0
  48. data/lib/dry/logic/rule.rb +5 -3
  49. data/lib/dry/logic/rule/interface.rb +20 -0
  50. data/lib/dry/logic/rule/predicate.rb +2 -0
  51. data/lib/dry/logic/rule_compiler.rb +2 -0
  52. data/lib/dry/logic/version.rb +3 -1
  53. data/spec/integration/result_spec.rb +2 -0
  54. data/spec/integration/rule_spec.rb +2 -0
  55. data/spec/shared/predicates.rb +2 -0
  56. data/spec/shared/rule.rb +2 -0
  57. data/spec/spec_helper.rb +6 -10
  58. data/spec/support/mutant.rb +2 -0
  59. data/spec/unit/operations/and_spec.rb +2 -0
  60. data/spec/unit/operations/attr_spec.rb +2 -0
  61. data/spec/unit/operations/check_spec.rb +2 -0
  62. data/spec/unit/operations/each_spec.rb +2 -0
  63. data/spec/unit/operations/implication_spec.rb +2 -0
  64. data/spec/unit/operations/key_spec.rb +2 -0
  65. data/spec/unit/operations/negation_spec.rb +2 -0
  66. data/spec/unit/operations/or_spec.rb +2 -0
  67. data/spec/unit/operations/set_spec.rb +2 -0
  68. data/spec/unit/operations/xor_spec.rb +2 -0
  69. data/spec/unit/predicates/array_spec.rb +2 -0
  70. data/spec/unit/predicates/attr_spec.rb +2 -0
  71. data/spec/unit/predicates/bool_spec.rb +3 -1
  72. data/spec/unit/predicates/bytesize_spec.rb +48 -0
  73. data/spec/unit/predicates/case_spec.rb +2 -0
  74. data/spec/unit/predicates/date_spec.rb +2 -0
  75. data/spec/unit/predicates/date_time_spec.rb +3 -1
  76. data/spec/unit/predicates/decimal_spec.rb +3 -1
  77. data/spec/unit/predicates/empty_spec.rb +2 -0
  78. data/spec/unit/predicates/eql_spec.rb +2 -0
  79. data/spec/unit/predicates/even_spec.rb +2 -0
  80. data/spec/unit/predicates/excluded_from_spec.rb +2 -0
  81. data/spec/unit/predicates/excludes_spec.rb +2 -0
  82. data/spec/unit/predicates/false_spec.rb +3 -1
  83. data/spec/unit/predicates/filled_spec.rb +2 -0
  84. data/spec/unit/predicates/float_spec.rb +2 -0
  85. data/spec/unit/predicates/format_spec.rb +2 -0
  86. data/spec/unit/predicates/gt_spec.rb +2 -0
  87. data/spec/unit/predicates/gteq_spec.rb +2 -0
  88. data/spec/unit/predicates/hash_spec.rb +40 -0
  89. data/spec/unit/predicates/included_in_spec.rb +2 -0
  90. data/spec/unit/predicates/int_spec.rb +2 -0
  91. data/spec/unit/predicates/key_spec.rb +2 -0
  92. data/spec/unit/predicates/lt_spec.rb +2 -0
  93. data/spec/unit/predicates/lteq_spec.rb +2 -0
  94. data/spec/unit/predicates/max_bytesize_spec.rb +39 -0
  95. data/spec/unit/predicates/max_size_spec.rb +2 -0
  96. data/spec/unit/predicates/min_bytesize_spec.rb +39 -0
  97. data/spec/unit/predicates/min_size_spec.rb +2 -0
  98. data/spec/unit/predicates/none_spec.rb +2 -0
  99. data/spec/unit/predicates/not_eql_spec.rb +2 -0
  100. data/spec/unit/predicates/number_spec.rb +2 -0
  101. data/spec/unit/predicates/odd_spec.rb +2 -0
  102. data/spec/unit/predicates/respond_to_spec.rb +31 -0
  103. data/spec/unit/predicates/size_spec.rb +2 -0
  104. data/spec/unit/predicates/str_spec.rb +2 -0
  105. data/spec/unit/predicates/time_spec.rb +3 -1
  106. data/spec/unit/predicates/true_spec.rb +3 -1
  107. data/spec/unit/predicates/type_spec.rb +2 -0
  108. data/spec/unit/predicates/uuid_v4_spec.rb +29 -0
  109. data/spec/unit/predicates_spec.rb +2 -0
  110. data/spec/unit/rule/predicate_spec.rb +2 -0
  111. data/spec/unit/rule_compiler_spec.rb +2 -0
  112. data/spec/unit/rule_spec.rb +14 -3
  113. metadata +30 -6
  114. data/.travis.yml +0 -31
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014 Ruby Object Mapper
3
+ Copyright (c) 2015-2019 dry-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,26 +1,26 @@
1
1
  [gem]: https://rubygems.org/gems/dry-logic
2
- [travis]: https://travis-ci.org/dry-rb/dry-logic
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
6
6
 
7
- # dry-schema [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
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://travis-ci.org/dry-rb/dry-logic.svg?branch=master)][travis]
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
- * [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...?
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
- * [Documentation](http://dry-rb.org/gems/dry-logic)
23
+ - [Documentation](http://dry-rb.org/gems/dry-logic)
24
24
 
25
25
  ## Contributing
26
26
 
data/Rakefile CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rake
2
+ # frozen_string_literal: true
3
+
2
4
  require 'bundler/gem_tasks'
3
5
 
4
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'setup'
2
4
 
3
5
  unless Dry::Logic::Rule.respond_to?(:build)
data/benchmarks/setup.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'benchmark/ips'
2
4
  require 'hotch'
3
5
  ENV['HOTCH_VIEWER'] ||= 'open'
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require 'dry/logic'
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/logic'
2
4
  require 'dry/logic/predicates'
3
5
 
data/lib/dry-logic.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/logic'
data/lib/dry/logic.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A collection of micro-libraries, each intended to encapsulate
2
4
  # a common task in Ruby
3
5
  module Dry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module Logic
3
5
  module Appliable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/equalizer'
2
4
 
3
5
  module Dry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/logic/operations/and'
2
4
  require 'dry/logic/operations/or'
3
5
  require 'dry/logic/operations/xor'
@@ -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.merge(new_options))
33
+ self.class.new(*rules, **options, **new_options)
32
34
  end
33
35
 
34
36
  def with(new_options)
35
- new(rules, options.merge(new_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/key'
2
4
 
3
5
  module Dry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/logic/operations/abstract'
2
4
 
3
5
  module Dry
@@ -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.merge(evaluator: evaluator))
20
+ super(rule, **options, evaluator: evaluator)
19
21
  end
20
22
  end
21
23
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/logic/operations/unary'
2
4
  require 'dry/logic/result'
3
5
 
@@ -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
 
@@ -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'
@@ -10,13 +12,13 @@ module Dry
10
12
 
11
13
  attr_reader :path
12
14
 
13
- def self.new(rules, options)
15
+ def self.new(rules, **options)
14
16
  if options[:evaluator]
15
17
  super
16
18
  else
17
19
  name = options.fetch(:name)
18
20
  eval = options.fetch(:evaluator, evaluator(name))
19
- super(rules, options.merge(evaluator: eval, path: name))
21
+ super(rules, **options, evaluator: eval, path: name)
20
22
  end
21
23
  end
22
24