decision_table 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/decision_table.gemspec +25 -0
- data/lib/decision_table/rule.rb +37 -0
- data/lib/decision_table/ruleset.rb +18 -0
- data/lib/decision_table/version.rb +3 -0
- data/lib/decision_table.rb +8 -0
- data/spec/rule_spec.rb +38 -0
- data/spec/rules.csv +3 -0
- data/spec/ruleset_spec.rb +33 -0
- data/spec/spec_helper.rb +8 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "decision_table/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "decision_table"
|
7
|
+
s.version = DecisionTable::VERSION
|
8
|
+
s.authors = ["Chris Nelson"]
|
9
|
+
s.email = ["superchrisnelson@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Table driven decision logic in ruby}
|
12
|
+
s.description = %q{If you have decision algorithm that can be easily expressed in a table, this gem can help}
|
13
|
+
|
14
|
+
s.rubyforge_project = "decision_table"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rspec-given"
|
24
|
+
s.add_runtime_dependency "activesupport"
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module DecisionTable
|
2
|
+
class Rule
|
3
|
+
|
4
|
+
attr_accessor :criteria, :result
|
5
|
+
|
6
|
+
def initialize(criteria, result)
|
7
|
+
@criteria = criteria
|
8
|
+
@result = result
|
9
|
+
end
|
10
|
+
|
11
|
+
def applies?(candidate)
|
12
|
+
criteria.all? do |k, v|
|
13
|
+
v.blank? || matches?(candidate, k, v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(candidate, key, value)
|
18
|
+
if value.to_s == "true"
|
19
|
+
candidate.send(key)
|
20
|
+
elsif value.to_s == "false"
|
21
|
+
!candidate.send(key)
|
22
|
+
else
|
23
|
+
candidate.send(key) == value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse(rule_string)
|
28
|
+
criteria = {}
|
29
|
+
rule_string.split(",").each do |crit|
|
30
|
+
k, v = crit.split(":")
|
31
|
+
criteria[k] = v
|
32
|
+
end
|
33
|
+
Rule.new(criteria, true)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DecisionTable
|
2
|
+
class Ruleset
|
3
|
+
attr_accessor :rules
|
4
|
+
def initialize(rules)
|
5
|
+
@rules = rules
|
6
|
+
end
|
7
|
+
|
8
|
+
def evaluate(candidate)
|
9
|
+
rule = rules.detect { |rule| rule.applies?(candidate) }
|
10
|
+
rule.result if rule
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse_csv(csv)
|
14
|
+
data = FasterCSV.parse(csv)
|
15
|
+
keys = data.shift
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DecisionTable::Rule do
|
4
|
+
Given(:candidate) { double(:candidate, :foo => "bar", :bing => "baz") }
|
5
|
+
|
6
|
+
describe("applies") do
|
7
|
+
context "when all values match" do
|
8
|
+
Given(:rule) { DecisionTable::Rule.new({:foo => "bar", :bing => "baz"}, true) }
|
9
|
+
Then { rule.applies?(candidate).should be_true}
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when all specified values match" do
|
13
|
+
Given(:rule ) { DecisionTable::Rule.new({:foo => "bar", :bing => ""}, true) }
|
14
|
+
Then { rule.applies?(candidate).should be_true}
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when values don't match" do
|
18
|
+
Given(:rule) { DecisionTable::Rule.new({:foo => "not bar", :bing => "baz"}, true) }
|
19
|
+
Then { rule.applies?(candidate).should be_false}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe("parsing rule") do
|
25
|
+
Given(:rule_string) { "foo:bar,bing:baz" }
|
26
|
+
When(:rule) { DecisionTable::Rule.parse(rule_string) }
|
27
|
+
Then { rule.applies?(candidate).should be_true }
|
28
|
+
|
29
|
+
context "with boolean rules" do
|
30
|
+
Given(:candidate) { double(:candidate, foo: true, bar: false) }
|
31
|
+
Given(:non_matching) { double(:candidate, foo: nil, bar: true) }
|
32
|
+
Given(:rule) {DecisionTable::Rule.parse("foo:true,bar:false") }
|
33
|
+
Then { rule.applies?(candidate).should be_true }
|
34
|
+
Then { rule.applies?(non_matching).should be_false }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/rules.csv
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DecisionTable
|
4
|
+
describe Ruleset do
|
5
|
+
Given(:candidate) { double(:candidate, foo: "bar", baz: "bing")}
|
6
|
+
describe "decide" do
|
7
|
+
context "one applicable rule" do
|
8
|
+
Given(:ruleset) do
|
9
|
+
rule = Rule.new({foo: "bar"}, "result")
|
10
|
+
Ruleset.new([rule])
|
11
|
+
end
|
12
|
+
Then { ruleset.evaluate(candidate).should == "result" }
|
13
|
+
end
|
14
|
+
context "two applicable rules" do
|
15
|
+
Given(:first_rule) { first_rule = Rule.new({baz: "bing"}, "first_result") }
|
16
|
+
Given(:second_rule) { first_rule = Rule.new({foo: "bar"}, "second_result") }
|
17
|
+
Given(:ruleset) { Ruleset.new [first_rule, second_rule] }
|
18
|
+
Then { ruleset.evaluate(candidate).should == "first_result"}
|
19
|
+
end
|
20
|
+
context "no applicable rules" do
|
21
|
+
Given(:rule) { Rule.new({baz: "not bing"}, "first_result") }
|
22
|
+
Then { Ruleset.new([rule]).evaluate(candidate).should be_nil }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "parsing CSV" do
|
27
|
+
pending "maybe later"
|
28
|
+
# Given(:csv_data) { File.read(File.join(File.dirname(__FILE__), "rules.csv")) }
|
29
|
+
# When(:ruleset) { Ruleset.parse_csv(csv_data) }
|
30
|
+
# Then { ruleset.rules.size.should == 2}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decision_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Nelson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70177089993400 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70177089993400
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec-given
|
27
|
+
requirement: &70177089992480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70177089992480
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: activesupport
|
38
|
+
requirement: &70177089991940 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70177089991940
|
47
|
+
description: If you have decision algorithm that can be easily expressed in a table,
|
48
|
+
this gem can help
|
49
|
+
email:
|
50
|
+
- superchrisnelson@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- Rakefile
|
58
|
+
- decision_table.gemspec
|
59
|
+
- lib/decision_table.rb
|
60
|
+
- lib/decision_table/rule.rb
|
61
|
+
- lib/decision_table/ruleset.rb
|
62
|
+
- lib/decision_table/version.rb
|
63
|
+
- spec/rule_spec.rb
|
64
|
+
- spec/rules.csv
|
65
|
+
- spec/ruleset_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
homepage: ''
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project: decision_table
|
87
|
+
rubygems_version: 1.8.17
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Table driven decision logic in ruby
|
91
|
+
test_files:
|
92
|
+
- spec/rule_spec.rb
|
93
|
+
- spec/rules.csv
|
94
|
+
- spec/ruleset_spec.rb
|
95
|
+
- spec/spec_helper.rb
|