7

I feel like that I am about to reinvent the wheel here, so before I do that ...

I have a large set of data that I need to process, and the 'rules' that process the data will evolve over time, so I thought that implementing a simple rules engine would be in order.

Note I am not looking for a natural language parser, I want all of the rules to be ruby procs.

I could imagine the syntax to look something like:

engine = SimpleRulesEngine.new

rule = engine.add_rule(priority: 10) do |row|
  row.name != 'George'
end

rule.action do |row|
  puts "Yikes, name is not George!, it was #{row.name}"
  row.update_attribute :name, 'George'
end

engine.process collection

I was wondering if there was any existing patterns or gems out there that would help with this. The one that seems closest is ruleby, but does not seem to be actively maintained, and seems too complex of a solution for my problem.

Thanks!


Note this is a similar question to : Ruby & Rules Engines, but different in that, I do not care about natural language processing, and rule storage.

Community
  • 1
  • 1
Jonathan
  • 16,077
  • 12
  • 67
  • 106
  • 1
    Without more details it's difficult to help; so far it just looks like you have a "rule should fire" block, and a "rule action" block. Seems like a straight-forward DSL at most, and some reasonably-clear Ruby even if you ignore a thin DSL wrapper. – Dave Newton Aug 28 '12 at 15:29
  • You might want to check out gem state_machine – Arnis Lapsa Aug 28 '12 at 15:37
  • @DaveNewton, i think your exactly right. Below is the solution that I came up with. Thanks for pushing me in the right direction – Jonathan Aug 29 '12 at 15:42

5 Answers5

7

@DaveNewton talked some sence into me, and it is clear that basically I was looking for some simple DSL for my app, this is what I ended up using -- its very simple, but incase it is useful for someone else:

# /lib/simple_rules_engine
# To use, just include it in any file where you need some rules engine love ...
# then defile rules like so:
#
# rule :name_of_rule,
#       priority: 10,
#       validate: lambda {|o| # do something with o}
#       fail: lambda {|o| o.fail!}} 
# 
# then to run the engine
# process_rules(your_data_set)
#   
module SimpleRulesEngine
  extend ActiveSupport::Concern

  included do
    class_attribute :rules
    self.rules = []
  end

  module ClassMethods

    # rule :name_of_rule,
    #       priority: 10,
    #       validate: lambda {|o| # do something with o}
    #       fail: lambda {|o| o.fail!}}
    def rule(name,options={})
      self.rules << SimpleRulesEngine::Rule.new(name,options)
    end

    def process_rules(collection)
      collection.each do |row|
        rules.sort_by(&:priority).each do |rule|
          rule.run(row)
        end
        row.valid!
      end
    end

  end

  ## Helper Classes

  class Rule

    attr_accessor :priority
    attr_accessor :name

    # proc to test
    attr_accessor :validate

    # if valid
    attr_accessor :success


    # if invalid
    attr_accessor :fail

    NO_OP = lambda {|o| true }

    def initialize(name, options={})
      self.name = name
      self.priority = options[:priority] || 10
      self.validate = options[:validate] || NO_OP
      self.fail = options[:fail] || NO_OP
      self.success = options[:success] || NO_OP
    end

    def run(data)

      if validate.call(data)
        success.call(data)
      else
        fail.call(data)
      end

    end
  end

end
Jonathan
  • 16,077
  • 12
  • 67
  • 106
  • Cool; thanks for following up. Hope it works out okay--I've done a few really simple "rules engines" like this, and for many things, they're perfectly adequate. – Dave Newton Aug 29 '12 at 15:59
1

By comparison to the other existing Ruby rules engines, Ruleby seems like the most actively maintained:

However, the Wongi Engine looks promising and may become what you need.

fogus
  • 6,126
  • 5
  • 36
  • 42
1

Perhaps look at Wongi, as Stratus3D suggested. From a first glance it looks nice and has a good intro. I will test it out on a more complex testcase the next few weeks.

Rools on the other hand seems to be unmaintained (rubyforge page is dead, all the forks i found seem to be dead as well).

Pascal
  • 8,464
  • 1
  • 20
  • 31
0

Ive been playing with Ruleby for a few weeks on and off and its not too complicated to use, although where I added complications is using a massive case statement to programmatically load rules into the engine.

Once you understand stuff like facts are persistant in the engine & that each subsequent run doesn't just assess the facts you just put in but the facts you asserted previously as well it is pretty straight forward. Not a fan of how blackbox some of it is though when I get a runtime error its an absolute pain to troubleshoot (as I currently am with one part).

evilspyboy
  • 36
  • 5
0

Don't forget about the wongi-engine gem (https://github.com/ulfurinn/wongi-engine). It is based on the Rete algorithm (http://en.wikipedia.org/wiki/Rete_algorithm/) and has a syntax similar to what you are looking for.

Stratus3D
  • 4,648
  • 4
  • 35
  • 67