Loading rules from external file

81 views
Skip to first unread message

Jake Dempsey

unread,
Oct 26, 2009, 11:56:13 AM10/26/09
to Ruleby
Is there a way to load the rules from an external source? I tried
making my SampleRulebook take in block where the block was a simple
load('filename'), but that doesnt work. I know that there is an effort
underway to create an external dsl using treetop but my question is a
bit different. I would like to be able to define the rule blocks in
an external file..like a text file. Then in the code create the
engine..create the rulebook..and somehow say load these rules.
Something like:

engine :engine do |e|
rulebook = SampleRulebook.new(e)
rulebook.load('sample_rulebook.ruleby')
e.assert Message.new(:HELLO, 'Hello World')
e.match
end

//contents of rules.ruleby
rule :hello, [Message, :m, method.status == :HELLO] do |context|
puts context[:m].message
end

Jake Dempsey

unread,
Oct 26, 2009, 12:52:31 PM10/26/09
to Ruleby
I got this to work..not sure if its the best approach..eval always
makes me worried. Can you think of another way of doing it in a safer
way?

engine :engine do |e|
e.assert Message.new(:HELLO, 'Hello World')

mrb = SampleRulebook.new(e)

mrb.class.class_eval do
def rules
eval(IO.readlines(File.join(File.dirname(__FILE__),
'rules.ruleby'), '').to_s)
end
end

mrb.rules
e.match
end

//contents of rules.ruleby
rule :hello, [Message, :m, method.status == :HELLO] do |context|
puts context[:m].message
end


Jake Dempsey

unread,
Oct 26, 2009, 1:23:52 PM10/26/09
to Ruleby
Ok i put the eval in the actual SampleRulebook class...i think i like
this better because the client accessing the rules doesnt know where
the rules are defined...which i like. So now I have:

engine :engine do |e|
e.assert Message.new(:HELLO, 'Hello World')
mrb = SampleRulebook.new(e)
mrb.rules
e.match
end

class SimpleRulebook < Rulebook
attr :messages

def initialize(e, &block)
@messages = []
super(e)
yield self if block_given?
end

def rules
path =File.join(File.dirname(__FILE__), 'rules/*.ruleby')
Dir[path].each do |f|
eval((IO.readlines(f, '').to_s))
end
end

end

Richard Pruss

unread,
Oct 26, 2009, 3:17:54 PM10/26/09
to rul...@googlegroups.com
I am doing something similar, I have the rules written in a code field in an events table in rails.

@event = Event.find_by_title(f)
  engine :engine do |e|
    TestRulebook.class_eval "def rules;" + @event.code + ";end;"
    TestRulebook.new(e).rules
...etc

While I was reading the doc recently I did notice a method which may be a better way to go for this,
but I have not got around to figuring out how to do it.:

This method is invoked when a new rule is added to the system. The rule is processed and the appropriate nodes are added to the network.


- Ric

Joe Kutner

unread,
Oct 27, 2009, 9:40:06 PM10/27/09
to rul...@googlegroups.com
These are both very interesting ways of building your rule base.
While they seem to work for the two of you, they make me feel like the
Rulebook design is lacking something. I would greatly welcome your
suggestions on how to improve it for these kinds of cases.

The assert_rule method take a Rule object as a parameter. The Rule
class is not complicated, and the DSL simply builds one of these up
behind the scenes. You can see an example of this around line 130 in
ferrari.rb

Joe

Jake Dempsey

unread,
Oct 28, 2009, 8:45:22 AM10/28/09
to Ruleby
I am looking at ruleby as a two part module. The first piece I want to
use it for is the execution of a ruleset. The actual rules management
should be externalized in my case. In an environment where customers
drive the rules you want them to be able to change those rules without
the need of a deployment or a svn commit. I have been noodling how I
can use ruleby and remove the need for a larger implementation such as
drools.

The basic workflow I am after is:
Customer manages rules through a webui that persists their rules.
Ideally this would be done with a dsl specific to my application
domain and I have not yet figured out how best to approach this. I
really would like a grammar for my customer that generates ruleby
syntax. My app would start with a constant rule engine and empty
rulebook. On startup the persisted rules would be loaded. At some
point, either event based or time based, I would want to clear the
current rules and reload them. (this is because the customer may have
modified, added, or removed a rule). I dont want to have to create a
new engine (which blows away my working memory). Ideally I would have
a method .reset_rules on my rulebook that would remove all existing
rules and then calls .rules. This would allow me to keep a single
engine/rulebook and use my rule engine as a webservice where my users
can assert and retract facts at any point and each assert doesnt
create the need to create a new engine and reload all the rules.

Hope that explains what I'm after.

Matt Smith

unread,
Oct 28, 2009, 10:50:37 AM10/28/09
to rul...@googlegroups.com
Hi Jake,

I started working on this several times, but never ended up with a good solution.

The ActiveRule code, http://github.com/mattup/activerule ,  was intending to do something similar.  The idea was to store the rules in the db, including any metadata for each rule.  I did manage to create a simple ActiveRecord model as a base.  The model works in terms of loading the rule from the db.
But as you point out there is not a way to reload the rules without creating a new engine.

Another approach I took was to create an external DSL, but sadly I never finished it either.  Sort of a pattern of mine.....

Matt

Jake Dempsey

unread,
Oct 28, 2009, 12:19:14 PM10/28/09
to Ruleby
Yeah I would think the external dsl would be more valuable. I looked
at activerule but I dont think its needed. If a user defines a rule
using a dsl you could "compile" them in to ruleby rule definitions in
a flat file and just load those at runtime. At least that was my
thought :)

On Oct 28, 9:50 am, Matt Smith <codeaspe...@gmail.com> wrote:
> Hi Jake,
>
> I started working on this several times, but never ended up with a good
> solution.
>
> The ActiveRule code,http://github.com/mattup/activerule,  was intending to
> do something similar.  The idea was to store the rules in the db, including
> any metadata for each rule.  I did manage to create a simple ActiveRecord
> model as a base.  The model works in terms of loading the rule from the db.
> But as you point out there is not a way to reload the rules without creating
> a new engine.
>
> Another approach I took was to create an external DSL, but sadly I never
> finished it either.  Sort of a pattern of mine.....
>
> Matt
>

jhc

unread,
Nov 13, 2009, 10:34:19 AM11/13/09
to Ruleby
I'm just starting to play around with Ruleby, so I may be off here.
But wouldn't it be possible to do something like this?

class MyClass
def initialize
@wm = Ruleby::Core::WorkingMemory.new
load_rules
end

def load_rules
@engine = Ruleby::Core::Engine.new(@wm)
# ...Read your (possibly updated) rules here...
@rules = MyRulebook.new(@engine).rules
end
end

Joe Kutner

unread,
Nov 13, 2009, 10:44:46 AM11/13/09
to rul...@googlegroups.com
That should be fine. The only reason we have the Ruleby::engine
function is for convenience. And its probably only convenient for the
tests :)

If you have any suggestions for other idioms that make instantiating
the engine easier, I'd be happy to work them into the sources.

jhc

unread,
Nov 13, 2009, 11:10:50 AM11/13/09
to Ruleby
Ruleby::engine is OK with me. :-)

I just wondered if this approach might help Jake retain his working
memory between reloads, or if there's some pitfall there. I saw
something in the source about needing to iterate over all facts when
new rules are added (the compare_to_wm method). Would that come into
play with a large set of facts?

Adam Davies

unread,
Oct 29, 2012, 3:15:55 AM10/29/12
to rul...@googlegroups.com
I also want to use the core classes directly, as I'm trying to provide a set of structured conditions exposed to our users through a nice UI.
I want to generate a list of conditions based on the user config...


Here's a specific example that uses ferrari, untested at this point:

    one_condition = [Date, :travel_date, f( c{|travel_date| TravelDateCondition.new('2012-12-01'.to_date).matches?(travel_date) } )]
   

But, I really want to end up with ActiveRecord models for my conditions that contain the user input, then load them up and call matches? on each...

    my_structured_conditions.map{|condition| 
        [condition.context_class, :m, f( c{|context|  cond.matches?(context) } ) ]
    }


I see that in ferrari.rb this happens:

    Ruleby::Core::Rule.new @name, @pattern, @action, @priority

I'm wondering if you could provide an example of how the pattern can be passed in?
It seems the really complicated part of the DSL, and am finding it difficult to know where to start.

I would *really*, *really*, *really* appreciate any advice!

Joe Kutner

unread,
Oct 29, 2012, 8:47:18 AM10/29/12
to rul...@googlegroups.com
Building up the pattern object is a very complicate process.  It takes into account a lot of stuff like AND, OR, NOT, and such.  It happens as part of the RuleBuilder.when method.  You could instantiate a RuleBuilder and call #when with your args.


--
You received this message because you are subscribed to the Google Groups "Ruleby" group.
To view this discussion on the web visit https://groups.google.com/d/msg/ruleby/-/FrK1eP0FF8wJ.
To post to this group, send email to rul...@googlegroups.com.
To unsubscribe from this group, send email to ruleby+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/ruleby?hl=en.

Matthew Smith

unread,
Oct 29, 2012, 9:07:02 AM10/29/12
to rul...@googlegroups.com
Not an answer to the questions, but you don't have to use a Rulebook.  You can use the "RuleHelper#rule" method from the RuleHelper module without using a Rulebook.  It returns an array of rules that can then be asserted into an engine.
rules = []
rules += rule …

engine :e do |e|
rules.each do |r|
e.assert_rule®
end
e.assert(fact)
e.match
end

This would allow you to build rules without a rulebook.  Again not exactly what you are looking for, but getting closer.  

Matt

Adam Davies

unread,
Oct 30, 2012, 2:07:42 AM10/30/12
to rul...@googlegroups.com
Thanks guys...  I appreciate your help!

I've had a look more deeply, and seems like at the top of lib/dsl/ferrary.rb that Ferrari::RuleBookHelper#rule does all the AND/OR handling.  It calls #parse_containers that build up Container / PatternContainers which flatten out the :and's and :or's, eventually building atoms.  

So, if I want the nice AND/OR handling, I must use this RuleBookHelper#rule method, or manually call RuleBookHelper#parse_containers.  If I was to use Ferrari::RuleBuilder#new and #when directly, that won't happen.  

Is that right?  

Kind of feels like the AndBuilder and OrBuilder classes should extend AtomBuilder, but then that RuleBookHelper#when is already pretty long.  

---

Also, I'm looking at :master branch.  Magnum seems much more readable, although it appears to be a copy of Ferrari with changes. 

Is it your opinion the :new_dsl branch is stable?  
Reply all
Reply to author
Forward
0 new messages