philiprehberger-rule_engine 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +27 -2
- data/lib/philiprehberger/rule_engine/engine.rb +25 -8
- data/lib/philiprehberger/rule_engine/rule.rb +8 -2
- data/lib/philiprehberger/rule_engine/version.rb +1 -1
- data/lib/philiprehberger/rule_engine.rb +2 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6632ea70fd2a2bd623fdbd89fdd2f1a399254742af0fc9dd737ac3fae82c9e04
|
|
4
|
+
data.tar.gz: 67207e22ad77f60478e22343957f298bd1c431cb5c8410688087b54bb65b1004
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b32d00eedfedbf1e00d49ec7728594d86b222f047135dfd3acbd3d485ff72900b1271e91ac4c358ee40e54f76ffad646e39a3cf939155c9487ac31a4e321087
|
|
7
|
+
data.tar.gz: 7b9d1b7ef2d0173e1c7385f05b3c3af4db9b7af7c0d1afbc7e6cde4aaa042d47b09fa973509613419bbcf2960ab0749138a2f2318bf842c7321b8bfa6fe45dce
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-04-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Rule tagging via `tags:` option on `rule` and `add_rule`
|
|
14
|
+
- `Engine#evaluate(facts, tags:)` and `Engine#dry_run(facts, tags:)` filter by tags (OR logic)
|
|
15
|
+
- `Engine#rules_by_tag(tag)` returns rules with a specific tag
|
|
16
|
+
- Tags included in `Rule#to_h` and restored by `RuleEngine.from_h`
|
|
17
|
+
|
|
10
18
|
## [0.3.0] - 2026-04-01
|
|
11
19
|
|
|
12
20
|
### Added
|
|
@@ -48,3 +56,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
48
56
|
- First-match mode to stop after first match
|
|
49
57
|
- Rule evaluation against arbitrary facts
|
|
50
58
|
- Results with rule name and action output
|
|
59
|
+
|
|
60
|
+
[0.4.0]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.4.0
|
|
61
|
+
[0.3.0]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.3.0
|
|
62
|
+
[0.2.1]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.2.1
|
|
63
|
+
[0.2.0]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.2.0
|
|
64
|
+
[0.1.2]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.1.2
|
|
65
|
+
[0.1.1]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.1.1
|
|
66
|
+
[0.1.0]: https://github.com/philiprehberger/rb-rule-engine/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -100,6 +100,30 @@ engine = Philiprehberger::RuleEngine.new do
|
|
|
100
100
|
end
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Rule Tags
|
|
104
|
+
|
|
105
|
+
Organize rules into categories and selectively evaluate subsets:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
engine = Philiprehberger::RuleEngine.new do
|
|
109
|
+
rule "validate_age", tags: [:validation] do
|
|
110
|
+
condition { |f| f[:age] >= 18 }
|
|
111
|
+
action { |_| "valid" }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
rule "apply_discount", tags: [:pricing] do
|
|
115
|
+
condition { |f| f[:premium] }
|
|
116
|
+
action { |_| "discount applied" }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
engine.evaluate(facts, tags: [:validation]) # only validation rules
|
|
121
|
+
engine.dry_run(facts, tags: [:pricing]) # dry run only pricing
|
|
122
|
+
engine.rules_by_tag(:validation) # list rules with tag
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Multiple tags use OR logic — a rule matches if it has any of the specified tags.
|
|
126
|
+
|
|
103
127
|
### Dynamic Rule Management
|
|
104
128
|
|
|
105
129
|
```ruby
|
|
@@ -210,7 +234,7 @@ end
|
|
|
210
234
|
| `.new(mode:) { }` | Create engine with rule definitions |
|
|
211
235
|
| `.from_h(data, &resolver)` | Reconstruct engine from serialized hash |
|
|
212
236
|
| `#rule(name) { }` | Define a rule with condition and action |
|
|
213
|
-
| `#evaluate(facts)` | Evaluate rules against facts |
|
|
237
|
+
| `#evaluate(facts, tags: nil)` | Evaluate rules against facts; filter by tags if given |
|
|
214
238
|
| `#rules` | Array of registered rules |
|
|
215
239
|
| `#mode` | Current evaluation mode |
|
|
216
240
|
| `#to_h` | Serialize engine configuration to hash |
|
|
@@ -221,7 +245,8 @@ end
|
|
|
221
245
|
| `#chain(*rule_names)` | Execute rules sequentially as a pipeline |
|
|
222
246
|
| `#stats` | Per-rule execution statistics |
|
|
223
247
|
| `#reset_stats!` | Clear all execution statistics |
|
|
224
|
-
| `#
|
|
248
|
+
| `#rules_by_tag(tag)` | Return rules with the given tag |
|
|
249
|
+
| `#dry_run(facts, tags: nil)` | Evaluate rules without executing actions |
|
|
225
250
|
| `#detect_conflicts` | Find rule pairs that could both match |
|
|
226
251
|
| `#validate_rules` | Check all rules have conditions and actions |
|
|
227
252
|
|
|
@@ -30,8 +30,8 @@ module Philiprehberger
|
|
|
30
30
|
# @param name [String] the rule name
|
|
31
31
|
# @yield [rule] block for configuring the rule
|
|
32
32
|
# @return [Rule] the created rule
|
|
33
|
-
def rule(name, &block)
|
|
34
|
-
r = Rule.new(name)
|
|
33
|
+
def rule(name, tags: [], &block)
|
|
34
|
+
r = Rule.new(name, tags: tags)
|
|
35
35
|
r.instance_eval(&block) if block
|
|
36
36
|
@rules << r
|
|
37
37
|
@stats[name] = new_stat_entry
|
|
@@ -41,10 +41,11 @@ module Philiprehberger
|
|
|
41
41
|
# Add a rule after engine creation.
|
|
42
42
|
#
|
|
43
43
|
# @param name [String] the rule name
|
|
44
|
+
# @param tags [Array<Symbol>] optional tags
|
|
44
45
|
# @yield [rule] block for configuring the rule
|
|
45
46
|
# @return [Rule] the created rule
|
|
46
|
-
def add_rule(name, &)
|
|
47
|
-
rule(name, &)
|
|
47
|
+
def add_rule(name, tags: [], &block)
|
|
48
|
+
rule(name, tags: tags, &block)
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
# Remove a rule by name.
|
|
@@ -82,12 +83,21 @@ module Philiprehberger
|
|
|
82
83
|
found.enabled = true
|
|
83
84
|
end
|
|
84
85
|
|
|
86
|
+
# Return rules matching a specific tag.
|
|
87
|
+
#
|
|
88
|
+
# @param tag [Symbol] the tag to filter by
|
|
89
|
+
# @return [Array<Rule>]
|
|
90
|
+
def rules_by_tag(tag)
|
|
91
|
+
@rules.select { |r| r.tags.include?(tag.to_sym) }
|
|
92
|
+
end
|
|
93
|
+
|
|
85
94
|
# Evaluate all rules against the given facts.
|
|
86
95
|
#
|
|
87
96
|
# @param facts [Object] the facts to evaluate
|
|
97
|
+
# @param tags [Array<Symbol>, nil] only evaluate rules with at least one matching tag
|
|
88
98
|
# @return [Array<Hash>] results with :rule and :result for each matched rule
|
|
89
|
-
def evaluate(facts)
|
|
90
|
-
sorted = @rules.select(&:enabled).sort_by(&:priority)
|
|
99
|
+
def evaluate(facts, tags: nil)
|
|
100
|
+
sorted = filter_by_tags(@rules.select(&:enabled), tags).sort_by(&:priority)
|
|
91
101
|
results = []
|
|
92
102
|
|
|
93
103
|
sorted.each do |r|
|
|
@@ -139,8 +149,8 @@ module Philiprehberger
|
|
|
139
149
|
#
|
|
140
150
|
# @param facts [Hash] the facts to evaluate
|
|
141
151
|
# @return [Array<Hash>] matched rules with name and priority
|
|
142
|
-
def dry_run(facts)
|
|
143
|
-
matched = enabled_rules_sorted.select { |rule| rule.matches?(facts) }
|
|
152
|
+
def dry_run(facts, tags: nil)
|
|
153
|
+
matched = filter_by_tags(enabled_rules_sorted, tags).select { |rule| rule.matches?(facts) }
|
|
144
154
|
matched = [matched.first].compact if @mode == :first
|
|
145
155
|
matched.map { |rule| { name: rule.name, priority: rule.priority } }
|
|
146
156
|
end
|
|
@@ -203,6 +213,13 @@ module Philiprehberger
|
|
|
203
213
|
|
|
204
214
|
private
|
|
205
215
|
|
|
216
|
+
def filter_by_tags(rules, tags)
|
|
217
|
+
return rules unless tags
|
|
218
|
+
|
|
219
|
+
tag_syms = tags.map(&:to_sym)
|
|
220
|
+
rules.select { |r| r.tags.intersect?(tag_syms) }
|
|
221
|
+
end
|
|
222
|
+
|
|
206
223
|
def enabled_rules_sorted
|
|
207
224
|
@rules.select(&:enabled).sort_by(&:priority)
|
|
208
225
|
end
|
|
@@ -9,12 +9,17 @@ module Philiprehberger
|
|
|
9
9
|
# @return [String] the rule name
|
|
10
10
|
attr_reader :name
|
|
11
11
|
|
|
12
|
+
# @return [Array<Symbol>] the rule tags
|
|
13
|
+
attr_reader :tags
|
|
14
|
+
|
|
12
15
|
# @return [Boolean] whether the rule is enabled
|
|
13
16
|
attr_accessor :enabled
|
|
14
17
|
|
|
15
18
|
# @param name [String] the rule name
|
|
16
|
-
|
|
19
|
+
# @param tags [Array<Symbol>] optional tags for grouping
|
|
20
|
+
def initialize(name, tags: [])
|
|
17
21
|
@name = name
|
|
22
|
+
@tags = tags.map(&:to_sym)
|
|
18
23
|
@priority = 0
|
|
19
24
|
@condition = nil
|
|
20
25
|
@action = nil
|
|
@@ -76,7 +81,8 @@ module Philiprehberger
|
|
|
76
81
|
{
|
|
77
82
|
name: @name,
|
|
78
83
|
priority: @priority,
|
|
79
|
-
enabled: @enabled
|
|
84
|
+
enabled: @enabled,
|
|
85
|
+
tags: @tags
|
|
80
86
|
}
|
|
81
87
|
end
|
|
82
88
|
end
|
|
@@ -35,8 +35,9 @@ module Philiprehberger
|
|
|
35
35
|
name = rule_data[:name] || rule_data['name']
|
|
36
36
|
priority_val = rule_data[:priority] || rule_data['priority'] || 0
|
|
37
37
|
enabled_val = rule_data.key?(:enabled) ? rule_data[:enabled] : rule_data.fetch('enabled', true)
|
|
38
|
+
tags_val = (rule_data[:tags] || rule_data['tags'] || []).map(&:to_sym)
|
|
38
39
|
|
|
39
|
-
r = engine.add_rule(name) do
|
|
40
|
+
r = engine.add_rule(name, tags: tags_val) do
|
|
40
41
|
priority priority_val
|
|
41
42
|
end
|
|
42
43
|
r.enabled = enabled_val
|
metadata
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-rule_engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A lightweight rule engine with a declarative DSL for defining conditions
|
|
14
|
-
and actions. Supports priority-based ordering,
|
|
15
|
-
modes,
|
|
14
|
+
and actions. Supports priority-based ordering, rule tagging with selective evaluation,
|
|
15
|
+
first-match and all-match modes, dry run, conflict detection, serialization, chaining,
|
|
16
|
+
and per-rule execution statistics.
|
|
16
17
|
email:
|
|
17
18
|
- me@philiprehberger.com
|
|
18
19
|
executables: []
|