Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

numerous AND/OR bug fixes, and new specs

  • Loading branch information...
commit 7c70691ad926017237e8f23e8303f3e3864fa450 1 parent 45e3558
Joe Kutner jkutner authored
Showing with 227 additions and 82 deletions.
  1. +65 −81 lib/dsl/ferrari.rb
  2. +161 −0 spec/and_or_spec.rb
  3. +1 −1  tests/or_patterns.rb
146 lib/dsl/ferrari.rb
View
@@ -52,82 +52,26 @@ def self.parse_containers(args, container=Container(:and), parent=nil)
return container
end
- class RulesContainer < Array
- def transform_or(parent)
- ors = []
- others = []
- permutations = 1
- index = 0
- parent.each do |child|
- if(child.or?)
- permutations *= child.size
- ors << child
- else
- others[index] = child
- end
- index = index + 1
- end
- # set parent type to or and clear
- parent.kind = :or
- parent.clear
- indexes = []
- # initialize indexes
- ors.each do |o|
- indexes << 0
- end
- # create children
- (1.upto(permutations)).each do |i|
- and_container = Container.new(:and)
-
- mod = 1
- (ors.size - 1).downto(0) do |j|
- and_container.insert(0,ors[j][indexes[j]])
- if((i % mod) == 0)
- indexes[j] = (indexes[j] + 1) % ors[j].size
- end
- mod *= ors[j].size
- end
-
- others.each_with_index do |other, k|
- if others[k] != nil
- and_container.insert(k, others[k])
- end
- end
- # add child to parent
- parent.push(and_container)
- end
- parent.uniq!
- end
-
- def handle_branching(container)
+ class RulesContainer < Array
+ def handle_branching
ands = []
- container.each do |x|
- if x.or?
- x.each do |branch|
- ands << branch
+ each do |x|
+ f = x.flatten_patterns
+ if f.or?
+ f.each do |o|
+ ands << o
end
- elsif x.and?
- ands << x
else
- new_and = Container.new(:and)
- new_and << x
- ands << new_and
- end
+ ands << f
+ end
end
- return ands
+ ands
end
- def build(name,options,engine,&block)
- rules = []
- self.each do |x|
- x.process_tree do |c|
- transform_or(c)
- end
+ def build(name, options, engine, &block)
+ handle_branching.map do |container|
+ build_rule(name, container, options, &block)
end
- handle_branching(self).each do |a|
- rules << build_rule(name, a, options, &block)
- end
- return rules
end
def build_rule(name, container, options, &block)
@@ -143,8 +87,54 @@ def build_rule(name, container, options, &block)
class Container < Array
attr_accessor :kind
- def initialize(kind)
+ def initialize(kind, *vals)
@kind = kind
+ self.push(*vals)
+ end
+
+ def flatten_patterns
+ if or?
+ patterns = []
+ each do |c|
+ f = c.flatten_patterns
+ if f.and?
+ patterns << f
+ else
+ f.each do |o|
+ # i hope this is safe... not entirely sure
+ patterns << (o.size == 1 ? o.first : o)
+# patterns << o
+ end
+ end
+ end
+
+ Container.new(:or, *patterns)
+ elsif and?
+ patterns = []
+ or_patterns = []
+ each do |c|
+ child_patterns = c.flatten_patterns
+ if child_patterns.or? and child_patterns.size > 1
+ child_patterns.each do |o|
+ or_patterns << o
+ end
+ else
+ patterns.push(*child_patterns)
+ end
+ end
+ if or_patterns.empty?
+ flat = Container.new(:and)
+ flat.push(*patterns)
+ else
+ flat = Container.new(:or)
+ or_patterns.each do |op|
+ c = Container.new(:and)
+ c.push(op, *patterns)
+ flat << c
+ end
+ end
+ return flat
+ end
end
def build(builder)
@@ -165,23 +155,17 @@ def or?
def and?
return kind == :and
end
-
- def process_tree(&block)
- has_or_child = false
- uniq!
- each do |c|
- has_or_child = true if (c.process_tree(&block) or c.or?)
- end
- yield(self) if (has_or_child)
- return has_or_child
- end
end
class PatternContainer
def initialize(condition)
@condition = condition
end
-
+
+ def flatten_patterns
+ Container.new(:and, self)
+ end
+
def build(builder)
builder.when(*@condition)
end
161 spec/and_or_spec.rb
View
@@ -0,0 +1,161 @@
+require 'spec_helper'
+
+class AndOrFact
+ attr :value, true
+ def initialize(v=nil); @value = v; end
+end
+
+class AndOrFact2
+ attr :value, true
+ def initialize(v=nil); @value = v; end
+end
+
+include Ruleby
+
+class AndOrRulebook < Rulebook
+ def rules
+ rule AND(
+ OR([AndOrFact, m.value > 0]),
+ OR(
+ OR([AndOrFact, m.value == 1]),
+ AND(
+ AND([AndOrFact, m.value < 1]),
+ OR([AndOrFact, m.value == nil], [:not, AndOrFact])))) do
+ assert Success.new
+ end
+
+# rule [AndOrFact, m.value > 0],
+# OR(
+# [AndOrFact, m.value == 1],
+# AND(
+# [AndOrFact, m.value < 1],
+# OR([AndOrFact, m.value == nil], [:not, AndOrFact]))) do
+# assert Success.new
+# end
+ end
+
+ def rules2
+ rule OR(AND(OR(OR([AndOrFact, m.value == 1])))) do |v|
+ assert Success.new
+ end
+ end
+
+ def rules3
+ rule OR([AndOrFact, m.value == 1], [AndOrFact, m.value == 2], [AndOrFact, m.value == 3]), [AndOrFact, m.value == 4] do |v|
+ assert Success.new
+ end
+ end
+
+ def rules4
+ rule AND([AndOrFact, :a, m.value == 1], [AndOrFact2, :a2, m.value == 2]) do |v|
+ raise "nil" if v[:a].nil?
+ raise "nil" if v[:a2].nil?
+ assert Success.new
+ end
+ end
+
+ def rules5
+ rule OR(AND([AndOrFact, :a, {m.value == 1 => :x}], [AndOrFact2, m.value == b(:x)])) do |v|
+ assert Success.new
+ end
+ end
+end
+
+describe Ruleby::Core::Engine do
+# describe "AND/OR" do
+ context "crazy AND/OR rules" do
+ subject do
+ engine :engine do |e|
+ AndOrRulebook.new(e).rules
+ end
+ end
+
+ before do
+ subject.assert AndOrFact.new(1)
+ subject.match
+ end
+
+ it "should have matched" do
+ subject.errors.should == []
+ subject.retrieve(Success).size.should == 1
+ end
+ end
+
+ context "nested AND/OR rule" do
+ subject do
+ engine :engine do |e|
+ AndOrRulebook.new(e).rules2
+ end
+ end
+
+ before do
+ subject.assert AndOrFact.new(1)
+ subject.match
+ end
+
+ it "should have matched" do
+ subject.errors.should == []
+ subject.retrieve(Success).size.should == 1
+ end
+ end
+
+ context "multi OR rule" do
+ subject do
+ engine :engine do |e|
+ AndOrRulebook.new(e).rules3
+ end
+ end
+
+ context "with one 1 and one 4" do
+ before do
+ subject.assert AndOrFact.new(1)
+ subject.assert AndOrFact.new(4)
+ subject.match
+ end
+
+ it "should have matched" do
+ subject.errors.should == []
+ subject.retrieve(Success).size.should == 1
+ end
+ end
+ end
+
+ context "nested AND/OR rule" do
+ subject do
+ engine :engine do |e|
+ AndOrRulebook.new(e).rules4
+ end
+ end
+
+ before do
+ subject.assert AndOrFact.new(1)
+ subject.assert AndOrFact2.new(2)
+ subject.match
+ end
+
+ it "should have matched" do
+ subject.errors.should == []
+ subject.retrieve(Success).size.should == 1
+ end
+ end
+
+ context "nested AND/OR rule" do
+ subject do
+ engine :engine do |e|
+ AndOrRulebook.new(e).rules5
+ end
+ end
+
+ before do
+ subject.assert AndOrFact.new(1)
+ subject.assert AndOrFact2.new(1)
+ subject.match
+ end
+
+ it "should have matched" do
+ subject.errors.should == []
+ subject.retrieve(Success).size.should == 1
+ end
+ end
+# end
+end
2  tests/or_patterns.rb
View
@@ -131,7 +131,7 @@ def test_0
assert_equal 0, ctx.get(:rule19)
assert_equal 1, ctx.get(:rule18)
assert_equal 2, ctx.get(:rule17)
- assert_equal 2, ctx.get(:rule16)
+ assert_equal 2, ctx.get(:rule16)
assert_equal 1, ctx.get(:rule14b)
assert_equal 1, ctx.get(:rule14a)
assert_equal 1, ctx.get(:rule13b)
Please sign in to comment.
Something went wrong with that request. Please try again.