The project is in a healthy, maintained state
A lightweight rule engine with a declarative DSL for defining conditions and actions. Supports priority-based ordering, rule tagging with selective evaluation, first-match and all-match modes, dry run, conflict detection, serialization, chaining, and per-rule execution statistics.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-rule_engine

Tests Gem Version Last updated

Lightweight rule engine with declarative conditions and actions

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem 'philiprehberger-rule_engine'

Or install directly:

gem install philiprehberger-rule_engine

Usage

require 'philiprehberger/rule_engine'

engine = Philiprehberger::RuleEngine.new do
  rule 'discount' do
    condition { |f| f[:total] > 100 }
    action { |f| f[:total] * 0.9 }
  end

  rule 'free_shipping' do
    condition { |f| f[:total] > 50 }
    action { |_| 'free shipping applied' }
  end
end

results = engine.evaluate({ total: 150 })
# => [{ rule: 'discount', result: 135.0 }, { rule: 'free_shipping', result: 'free shipping applied' }]

Priority

engine = Philiprehberger::RuleEngine.new do
  rule 'low' do
    priority 10
    condition { |_| true }
    action { |_| 'runs second' }
  end

  rule 'high' do
    priority 1
    condition { |_| true }
    action { |_| 'runs first' }
  end
end

First-Match Mode

engine = Philiprehberger::RuleEngine.new(mode: :first) do
  rule 'premium' do
    priority 1
    condition { |f| f[:tier] == 'premium' }
    action { |_| { discount: 0.20 } }
  end

  rule 'standard' do
    priority 2
    condition { |_| true }
    action { |_| { discount: 0.05 } }
  end
end

results = engine.evaluate({ tier: 'premium' })
# => [{ rule: 'premium', result: { discount: 0.20 } }]

Composite Conditions

engine = Philiprehberger::RuleEngine.new do
  rule 'access' do
    condition { |f| all?(f[:active], any?(f[:admin], f[:moderator])) }
    action { |_| 'granted' }
  end

  rule 'blocked' do
    condition { |f| none?(f[:verified], f[:trusted]) }
    action { |_| 'denied' }
  end
end

Rule Tags

Organize rules into categories and selectively evaluate subsets:

engine = Philiprehberger::RuleEngine.new do
  rule "validate_age", tags: [:validation] do
    condition { |f| f[:age] >= 18 }
    action { |_| "valid" }
  end

  rule "apply_discount", tags: [:pricing] do
    condition { |f| f[:premium] }
    action { |_| "discount applied" }
  end
end

engine.evaluate(facts, tags: [:validation])   # only validation rules
engine.dry_run(facts, tags: [:pricing])        # dry run only pricing
engine.rules_by_tag(:validation)               # list rules with tag

Multiple tags use OR logic — a rule matches if it has any of the specified tags.

Dynamic Rule Management

engine = Philiprehberger::RuleEngine.new

engine.add_rule('dynamic') do
  condition { |f| f[:ready] }
  action { |_| 'go' }
end

engine.disable_rule('dynamic')
engine.evaluate({ ready: true })  # => []

engine.enable_rule('dynamic')
engine.evaluate({ ready: true })  # => [{ rule: 'dynamic', result: 'go' }]

engine.remove_rule('dynamic')

Rule Chaining

engine = Philiprehberger::RuleEngine.new do
  rule 'fetch' do
    action { |_| 10 }
  end

  rule 'double' do
    action { |f| f[:input] * 2 }
  end

  rule 'format' do
    action { |f| "Result: #{f[:input]}" }
  end
end

engine.chain('fetch', 'double', 'format')
# => 'Result: 20'

Execution Statistics

engine = Philiprehberger::RuleEngine.new do
  rule 'tracked' do
    condition { |_| true }
    action { |_| 'ok' }
  end
end

3.times { engine.evaluate({}) }

engine.stats
# => { 'tracked' => { evaluations: 3, matches: 3, executions: 3, avg_time: 0.00001, last_triggered: <Time> } }

engine.reset_stats!

Dry Run

Evaluate without executing actions:

matched = engine.dry_run(facts)
# => [{ name: "discount", priority: 10 }]

Conflict Detection

engine.detect_conflicts
# => [{ rules: ["rule_a", "rule_b"], priorities: [10, 5] }]

Rule Validation

engine.validate_rules
# => { valid: false, issues: ["Rule 'x' has no action"] }

Serialization

engine = Philiprehberger::RuleEngine.new(mode: :first) do
  rule 'alpha' do
    priority 1
    condition { |_| true }
    action { |_| 'go' }
  end
end

data = engine.to_h
# => { mode: :first, rules: [{ name: 'alpha', priority: 1, enabled: true }] }

restored = Philiprehberger::RuleEngine.from_h(data) do |r|
  r.condition { |_| true }
  r.action { |_| 'restored' }
end

API

Engine

Method Description
.new(mode:) { } Create engine with rule definitions
.from_h(data, &resolver) Reconstruct engine from serialized hash
#rule(name) { } Define a rule with condition and action
#evaluate(facts, tags: nil) Evaluate rules against facts; filter by tags if given
#rules Array of registered rules
#mode Current evaluation mode
#to_h Serialize engine configuration to hash
#add_rule(name) { } Add a rule after engine creation
#remove_rule(name) Remove a rule by name
#disable_rule(name) Disable a rule (skipped during evaluation)
#enable_rule(name) Re-enable a disabled rule
#chain(*rule_names) Execute rules sequentially as a pipeline
#stats Per-rule execution statistics
#reset_stats! Clear all execution statistics
#rules_by_tag(tag) Return rules with the given tag
#dry_run(facts, tags: nil) Evaluate rules without executing actions
#detect_conflicts Find rule pairs that could both match
#validate_rules Check all rules have conditions and actions

Rule DSL

Method Description
condition { |facts| } Set the match condition
action { |facts| } Set the action to execute
priority(n) Set priority (lower runs first)

Composite Condition Helpers

Method Description
all?(*conditions) True if all conditions are truthy
any?(*conditions) True if any condition is truthy
none?(*conditions) True if no conditions are truthy

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT