Repositories
Repositories provide persistence for rules with version management and audit trails.
Overview
Ruleur provides repository implementations for:
- In-memory storage (development/testing)
- ActiveRecord database storage (production)
- Versioned storage with audit trails
Base: Ruleur::Persistence::Repository::Base
Abstract base class for repositories.
Instance Methods
#save(rule)
Saves a rule.
repository.save(rule)Parameters:
rule(Ruleur::Rule) - Rule to save
Returns:
- Saved rule with ID
#find(id)
Finds a rule by ID.
rule = repository.find(123)Parameters:
id- Rule identifier
Returns:
Ruleur::Ruleornil
#find_by_name(name)
Finds a rule by name.
rule = repository.find_by_name('discount_rule')Parameters:
name(String) - Rule name
Returns:
Ruleur::Ruleornil
#all
Returns all rules.
rules = repository.allReturns:
Array\<Ruleur::Rule\>
#delete(id)
Deletes a rule.
repository.delete(123)Parameters:
id- Rule identifier
Returns:
Boolean- Success status
Memory Repository
In-memory storage for development and testing.
Class: Ruleur::Persistence::Repository::Memory
repository = Ruleur::Persistence::Repository::Memory.new
repository.save(rule)Use Cases:
- Testing
- Development
- Temporary rule storage
- Non-persistent engines
ActiveRecord Repository
Database storage using ActiveRecord.
Class: Ruleur::Persistence::Repository::ActiveRecord
repository = Ruleur::Persistence::Repository::ActiveRecord.new
repository.save(rule)Setup
Generate migration:
rails generate ruleur:migration
rails db:migrateConfiguration
# config/initializers/ruleur.rb
Ruleur.configure do |config|
config.repository = Ruleur::Persistence::Repository::ActiveRecord.new
endAdditional Methods
#find_by_tags(tags)
Finds rules by tags.
rules = repository.find_by_tags(%i[permissions admin])#active
Returns only active rules.
rules = repository.activeVersioned ActiveRecord Repository
Database storage with full version history.
Class: Ruleur::Persistence::Repository::VersionedActiveRecord
repository = Ruleur::Persistence::Repository::VersionedActiveRecord.newFeatures
- Full version history
- Change tracking
- Rollback support
- Audit trail (who/when)
Versioning Methods
#save_version(rule, metadata = {})
Saves a new version of a rule.
repository.save_version(
rule,
changed_by: current_user.id,
reason: 'Updated discount threshold'
)Parameters:
rule(Ruleur::Rule) - Rule to savemetadata(Hash) - Version metadatachanged_by- User IDreason- Change reason- Custom fields
#versions(rule_id)
Gets all versions of a rule.
versions = repository.versions(123)
versions.each do |version|
puts "Version #{version.version_number} by #{version.changed_by}"
endReturns:
Array\<RuleVersion\>- All versions, newest first
#version(rule_id, version_number)
Gets a specific version.
rule_v2 = repository.version(123, 2)Parameters:
rule_id- Rule identifierversion_number(Integer) - Version number
Returns:
Ruleur::Rule- Rule at that version
#rollback(rule_id, version_number)
Rolls back to a previous version.
repository.rollback(123, 2)Parameters:
rule_id- Rule identifierversion_number(Integer) - Target version
Returns:
Ruleur::Rule- Restored rule
#diff(rule_id, from_version, to_version)
Shows differences between versions.
changes = repository.diff(123, 1, 2)Returns:
Hash- Change description
Examples
Basic Usage with ActiveRecord
# Initialize repository
repo = Ruleur::Persistence::Repository::ActiveRecord.new
# Save a rule
rule = Ruleur.define do
rule 'discount' do
conditions do
all?(order(:total).gt?(100))
end
actions do
set :discount, 0.1
end
end
end.rules.first
saved_rule = repo.save(rule)
# Find rule
found = repo.find(saved_rule.id)
found = repo.find_by_name('discount')
# Load all rules into engine
rules = repo.all
engine = Ruleur::Engine.new(rules: rules)Versioned Repository
repo = Ruleur::Persistence::Repository::VersionedActiveRecord.new
# Save initial version
rule = create_discount_rule(threshold: 100)
repo.save_version(
rule,
changed_by: user.id,
reason: 'Initial version'
)
# Update rule
rule = create_discount_rule(threshold: 150)
repo.save_version(
rule,
changed_by: user.id,
reason: 'Increased threshold to 150'
)
# View history
versions = repo.versions(rule.id)
versions.each do |v|
puts "v#{v.version_number}: #{v.reason} by #{v.changed_by}"
end
# Rollback if needed
repo.rollback(rule.id, 1)Loading Rules from Database
# Load all active rules
repo = Ruleur::Persistence::Repository::ActiveRecord.new
rules = repo.active
engine = Ruleur::Engine.new(rules: rules)
result = engine.run(context_data)Filtering by Tags
# Load only permission rules
permission_rules = repo.find_by_tags([:permissions])
engine = Ruleur::Engine.new(rules: permission_rules)Database Schema
Rules Table
create_table :rules do |t|
t.string :name, null: false, index: { unique: true }
t.text :yaml_content, null: false
t.integer :salience, default: 0
t.string :tags, array: true, default: []
t.boolean :active, default: true
t.timestamps
endRule Versions Table
create_table :rule_versions do |t|
t.references :rule, null: false, foreign_key: true
t.integer :version_number, null: false
t.text :yaml_content, null: false
t.integer :changed_by
t.string :reason
t.jsonb :metadata, default: {}
t.timestamps
t.index %i[rule_id version_number], unique: true
endBest Practices
Validate Before Saving
def save_rule(rule)
result = Ruleur::Validation.validate(rule)
raise ValidationError, result.errors unless result.valid?
repository.save(rule)
endAlways Provide Version Metadata
repository.save_version(
rule,
changed_by: current_user.id,
reason: params[:change_reason],
ip_address: request.ip,
user_agent: request.user_agent
)Cache Rules in Production
class CachedRuleRepository
def initialize(repository)
@repository = repository
end
def all
Rails.cache.fetch('rules/all', expires_in: 5.minutes) do
@repository.all
end
end
def clear_cache
Rails.cache.delete('rules/all')
end
endSee Also
- Persistence Guide - Persistence patterns
- Versioning Guide - Version management
- Validation - Rule validation