rulz 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6ec3d9f0c732273a67886c4c81dc2c6dac0dbf2
4
+ data.tar.gz: f11c3808909d9eaa72bfaa51aaf55f94ba819896
5
+ SHA512:
6
+ metadata.gz: b1bf5422a9b0552094b4504d9f8a5c3952702f30ddd31538059bf2588d909877becc6487e35cca2e74d379a52399ab93f768b273a0bcb8fe81084a04dabfcc8d
7
+ data.tar.gz: 5cc2faa66281dd963a983ab2fdf6625278d7a258ba8bb4aa0336971fd3f2ecc6614841a77ef5a5a9421d56f9ad4e39cb85596c1eda1395094248b10373f549dc
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ rulz
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p195
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rulz.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Chase Conklin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # Rulz
2
+
3
+ Rulz is a rule engine for Ruby, allowing developers to define rules in their classes.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rulz'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rulz
18
+
19
+ ## Usage
20
+
21
+ In the model(s) which you want to apply rules to, include the rulz module (you may need to `require` it first):
22
+
23
+ include Rulz
24
+
25
+ ### Defining Rules
26
+
27
+ #### Conditions and Actions
28
+
29
+ define_rulz do
30
+ condition "less than" do |other|
31
+ it < other
32
+ end
33
+ condition "contains" do |other|
34
+ it.contains? other
35
+ end
36
+ action "assign to user" do |user|
37
+ its.user = user
38
+ end
39
+ end
40
+
41
+ When defining conditions, it is possible to reference other condition:
42
+
43
+ condition "not equal to" do |other|
44
+ opposite_of "equal to", other
45
+ end
46
+
47
+ Actions can be combined in a similar way:
48
+
49
+ action "put on shoes" do
50
+ action "put on socks"
51
+ its.feet << "shoes"
52
+ end
53
+
54
+ The following conditions come predefined with rulz:
55
+
56
+ * less than
57
+ * greater than
58
+ * equal to
59
+ * not equal to
60
+ * contains
61
+ * does not contain
62
+ * matches
63
+ * does not match
64
+ * like
65
+ * not like
66
+
67
+ To load these conditions for the whole class `include` any of the following modules
68
+
69
+ * Rulz::Conditions::Comparison
70
+ * Rulz::Conditions::Container
71
+ * Rulz::Conditions::String
72
+
73
+ To load these conditions for a cetain attribute, call the `type` method inside the attribute block:
74
+
75
+ define_rulz do
76
+ attribute :foo do
77
+ type :integer
78
+ end
79
+ end
80
+
81
+ The following types are allowed:
82
+
83
+ * integer
84
+ * float
85
+ * string
86
+ * array
87
+ * hash
88
+
89
+ #### It
90
+
91
+ "it" refers to the object that the conditions/rules are being applied to. For readability, "it" is aliased to "its"
92
+
93
+ #### Rules
94
+
95
+ class Number < Fixnum
96
+ define_rulz do
97
+ condition "even" do
98
+ it % 2 == 0
99
+ end
100
+ condition "odd" do
101
+ opposite_of "even"
102
+ end
103
+ action "make even" do
104
+ it += 1
105
+ end
106
+ rule do
107
+ where("odd") { apply! "make even" }
108
+ end
109
+ end
110
+ end
111
+
112
+ Conditions can also be combined in `where` statements:
113
+
114
+ rule do
115
+ where(["odd", "AND", "prime"]) { apply! "make even" }
116
+ end
117
+
118
+ Where statements can also accept the attribute method:
119
+
120
+ rule do
121
+ where :id => "odd" do
122
+ apply! "make even"
123
+ end
124
+ end
125
+
126
+ rule do
127
+ where :id => ["multiple of", 5] do
128
+ apply! "make even"
129
+ end
130
+ end
131
+
132
+ When options need to be combined:
133
+
134
+ rule do
135
+ where [{:id => ["multiple of", 5]}, "AND", "odd"] do
136
+ apply! "make even"
137
+ end
138
+ end
139
+
140
+
141
+ ### Applying Rules
142
+
143
+ To apply rules to a single object, call the `apply_rules!` method like so:
144
+
145
+ m = Melon.new
146
+ m.apply_rules!
147
+
148
+ ## Contributing
149
+
150
+ 1. Fork it
151
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
152
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
153
+ 4. Push to the branch (`git push origin my-new-feature`)
154
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/rulz.rb ADDED
@@ -0,0 +1,46 @@
1
+ require "rulz/version"
2
+ require "rulz/definer"
3
+ require "rulz/condition"
4
+ require "rulz/evaluator"
5
+ require "rulz/action"
6
+ require "rulz/rule"
7
+ require "rulz/conditions/comparison"
8
+ require "rulz/conditions/container"
9
+ require "rulz/conditions/string"
10
+ require "rulz/attribute"
11
+ require "rulz/conditions/conditions"
12
+
13
+ module Rulz
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ base.send(:include, InstanceMethods)
18
+ end
19
+
20
+ module InstanceMethods
21
+ def condition_true?(name, *args)
22
+ Rulz::Evaluator::Condition.new(self, name, *args).evaluate
23
+ end
24
+
25
+ def condition_false?(name, *args)
26
+ not condition_true?(name, *args)
27
+ end
28
+
29
+ def apply!(action, *args)
30
+ Rulz::Evaluator::Action.new(self, action, *args).evaluate
31
+ end
32
+
33
+ def apply_rules!
34
+ Rulz::Rule.rules(self.class).each { |rule| rule.apply!(self) }
35
+ end
36
+
37
+ end
38
+
39
+ module ClassMethods
40
+
41
+ def define_rulz(&block)
42
+ Rulz::Definer.new(self).instance_eval &block
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module Rulz
2
+ class Action
3
+ attr_accessor :name, :proc
4
+
5
+ def initialize(name, klass, proc)
6
+ @name = name
7
+ @proc = proc
8
+ klass.instance_exec self do |action|
9
+ @rulz_actions ||= []
10
+ @rulz_actions << action
11
+ end
12
+ end
13
+
14
+ def self.find(klass, name)
15
+ klass.instance_variable_get(:@rulz_actions).find {|c| c.name == name }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ module Rulz
2
+ module Attribute
3
+ class Block
4
+ def initialize(attr, klass, &block)
5
+ @attr = attr
6
+ @klass = klass
7
+ instance_eval &block
8
+ end
9
+
10
+ def condition(name, &block)
11
+ Rulz::Attribute::Condition.new(@klass, @attr, name, block)
12
+ end
13
+
14
+ def type(kind)
15
+ Rulz::Conditions.load_conditions(@klass, @attr, kind)
16
+ end
17
+
18
+ end
19
+
20
+ class Condition
21
+ attr_reader :name, :proc
22
+ def initialize(klass, attr, name, proc)
23
+ @name = name
24
+ @proc = proc
25
+ klass.instance_exec self do |condition|
26
+ conditions = instance_variable_get("@rulz_#{attr}_conditions") || []
27
+ conditions << condition
28
+ instance_variable_set("@rulz_#{attr}_conditions", conditions)
29
+ end
30
+ end
31
+
32
+ def self.find(klass, attr, name)
33
+ result = klass.instance_variable_get("@rulz_#{attr}_conditions").find {|c| c.name == name }
34
+ raise ArgumentError, "Condition '#{name}' for '#{attr}' does not exist" unless result
35
+ result
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ module Rulz
2
+ class Condition
3
+ attr_accessor :name, :proc
4
+
5
+ def initialize(name, klass, proc)
6
+ @name = name
7
+ @proc = proc
8
+ klass.instance_exec self do |condition|
9
+ @rulz_conditions ||= []
10
+ @rulz_conditions << condition
11
+ end
12
+ end
13
+
14
+ def self.find(klass, name)
15
+ result = klass.instance_eval { @rulz_conditions }.find {|c| c.name == name }
16
+ raise ArgumentError, "Condition '#{name}' does not exist" unless result
17
+ result
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,52 @@
1
+ module Rulz
2
+ module Conditions
3
+ module Comparison
4
+
5
+ def self.load_conditions(reciever, attr)
6
+ reciever.class_eval do
7
+ define_rulz do
8
+ attribute attr do
9
+ condition "greater than" do |other|
10
+ send(attr) > other
11
+ end
12
+
13
+ condition "less than" do |other|
14
+ send(attr) < other
15
+ end
16
+
17
+ condition "equal to" do |other|
18
+ send(attr) == other
19
+ end
20
+
21
+ condition "not equal to" do |other|
22
+ opposite_of "equal to", other
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.included(base)
30
+ base.class_eval do
31
+ define_rulz do
32
+ condition "greater than" do |other|
33
+ it > other
34
+ end
35
+
36
+ condition "less than" do |other|
37
+ it < other
38
+ end
39
+
40
+ condition "equal to" do |other|
41
+ it == other
42
+ end
43
+
44
+ condition "not equal to" do |other|
45
+ opposite_of "equal to", other
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ module Rulz
2
+ module Conditions
3
+ def self.load_conditions(reciever, attribute, kind)
4
+ case kind
5
+ when :integer
6
+ Comparison.load_conditions(reciever, attribute)
7
+ when :float
8
+ Comparison.load_conditions(reciever, attribute)
9
+ when :string
10
+ Container.load_conditions(reciever, attribute)
11
+ String.load_conditions(reciever, attribute)
12
+ when :array
13
+ Container.load_conditions(reciever, attribute)
14
+ when :hash
15
+ Container.load_conditions(reciever, attribute)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ module Rulz
2
+ module Conditions
3
+ module Container
4
+
5
+ def self.load_conditions(reciever, attr)
6
+ reciever.class_eval do
7
+ define_rulz do
8
+ attribute attr do
9
+ condition "contains" do |other|
10
+ send(attr).include? other
11
+ end
12
+ condition "does not contain" do |other|
13
+ opposite_of "contains", other
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ def self.included(base)
22
+ base.class_eval do
23
+ define_rulz do
24
+ condition "contains" do |other|
25
+ it.include? other
26
+ end
27
+ condition "does not contain" do |other|
28
+ opposite_of "contains", other
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module Rulz
2
+ module Conditions
3
+ module String
4
+
5
+ def self.load_conditions(reciever, attr)
6
+ reciever.class_eval do
7
+ define_rulz do
8
+ attribute attr do
9
+ condition "does not match" do |regex|
10
+ send(attr).scan(regex).empty?
11
+ end
12
+ condition "matches" do |regex|
13
+ opposite_of "does not match", regex
14
+ end
15
+ condition "like" do |other|
16
+ send(attr).downcase == other.downcase
17
+ end
18
+ condition "not like" do |other|
19
+ opposite_of "like", other
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.included(base)
27
+ base.class_eval do
28
+ include Rulz::Conditions::Container
29
+ define_rulz do
30
+ condition "does not match" do |regex|
31
+ it.scan(regex).empty?
32
+ end
33
+ condition "matches" do |regex|
34
+ opposite_of "does not match", regex
35
+ end
36
+ condition "like" do |other|
37
+ it.downcase == other.downcase
38
+ end
39
+ condition "not like" do |other|
40
+ opposite_of "like", other
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module Rulz
2
+ class Definer
3
+
4
+ def initialize(klass)
5
+ @klass = klass
6
+ end
7
+
8
+ def condition(name, &block)
9
+ Rulz::Condition.new(name, @klass, block)
10
+ end
11
+
12
+ def action(name, &block)
13
+ Rulz::Action.new(name, @klass, block)
14
+ end
15
+
16
+ def rule(&block)
17
+ Rulz::Rule.new(@klass, block)
18
+ end
19
+
20
+ def attribute(attr, &block)
21
+ Rulz::Attribute::Block.new(attr, @klass, &block)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,123 @@
1
+ module Rulz
2
+ module Evaluator
3
+
4
+ class Base
5
+
6
+ def initialize(receiver, *args)
7
+ @receiver = receiver
8
+ @args = args || []
9
+ end
10
+
11
+ def it
12
+ @receiver
13
+ end
14
+
15
+ alias :its :it
16
+ end
17
+
18
+ class Condition < Evaluator::Base
19
+ def initialize(receiver, name, *args)
20
+ @condition = Rulz::Condition.find(receiver.class, name)
21
+ super(receiver, *args)
22
+ end
23
+
24
+
25
+ def condition(name, *args)
26
+ Rulz::Evaluator::Condition.new(@receiver, name, *args).evaluate
27
+ end
28
+
29
+ def opposite_of(name, *args)
30
+ not condition(name, *args)
31
+ end
32
+
33
+ def evaluate
34
+ instance_exec(*@args, &@condition.proc)
35
+ end
36
+
37
+
38
+ end
39
+
40
+ class AttributeCondition < Evaluator::Base
41
+ def initialize(receiver, attribute, name, *args)
42
+ @condition = Rulz::Attribute::Condition.find(receiver.class, attribute, name)
43
+ super(receiver, *args)
44
+ define_singleton_method attribute do
45
+ @receiver.send(attribute)
46
+ end
47
+ end
48
+
49
+ def evaluate
50
+ instance_exec(*@args, &@condition.proc)
51
+ end
52
+
53
+ end
54
+
55
+ class Action < Evaluator::Base
56
+ def initialize(receiver, name, *args)
57
+ @action = Rulz::Action.find(receiver.class, name)
58
+ super(receiver, *args)
59
+ end
60
+
61
+ def evaluate
62
+ instance_exec(*@args, &@action.proc)
63
+ end
64
+
65
+ def action(name, *args)
66
+ Rulz::Evaluator::Action.new(@receiver, name, *args).evaluate
67
+ end
68
+
69
+
70
+ end
71
+
72
+ class Rule < Evaluator::Base
73
+ def initialize(receiver, proc)
74
+ @receiver = receiver
75
+ @proc = proc
76
+ end
77
+
78
+ def evaluate
79
+ instance_eval(&@proc)
80
+ end
81
+
82
+ def where(condition, *args, &block)
83
+ if condition.is_a? String
84
+ @receiver.instance_eval(&block) if Rulz::Evaluator::Condition.new(@receiver, condition, *args).evaluate
85
+ elsif condition.is_a? Proc
86
+ @receiver.instance_eval(&block) if condition.call(*args)
87
+ elsif condition.is_a? Array
88
+ raise ArgumentError, "Invalid condition" unless condition.include? "AND" or condition.include? "OR"
89
+ split_condition = condition.inject([[]]) do |results, element|
90
+ if ["AND", "OR"].include? element
91
+ results << {"AND" => "&&", "OR" => "||"}[element]
92
+ results << []
93
+ elsif element.is_a?(Hash)
94
+ results.pop if results.last.respond_to?(:empty?) && results.last.empty?
95
+ results << element
96
+ else
97
+ results.last << element
98
+ end
99
+ results
100
+ end
101
+ split_condition.map! do |element|
102
+ if element.is_a?(Array)
103
+ Rulz::Evaluator::Condition.new(@receiver, *element).evaluate
104
+ elsif element.is_a?(Hash)
105
+ attribute, element = *element.first
106
+ Rulz::Evaluator::AttributeCondition.new(@receiver, attribute, *element).evaluate
107
+ else
108
+ element
109
+ end
110
+ end
111
+ @receiver.instance_eval(&block) if eval(split_condition.join(" "))
112
+ elsif condition.is_a? Hash
113
+ attribute, condition = *condition.first
114
+ @receiver.instance_eval(&block) if Rulz::Evaluator::AttributeCondition.new(@receiver, attribute, *condition).evaluate
115
+ else
116
+ raise ArgumentError, "Invalid condition"
117
+ end
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+
data/lib/rulz/rule.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Rulz
2
+ class Rule
3
+ attr_accessor :proc
4
+
5
+ def initialize(klass, proc)
6
+ @proc = proc
7
+ klass.instance_exec self do |rule|
8
+ @rulz_rules ||= []
9
+ @rulz_rules << rule
10
+ end
11
+ end
12
+
13
+ def apply!(receiver)
14
+ Rulz::Evaluator::Rule.new(receiver, @proc).evaluate
15
+ end
16
+
17
+ def self.rules(klass)
18
+ klass.instance_eval { @rulz_rules }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Rulz
2
+ VERSION = "1.0.0"
3
+ end
data/rulz.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rulz/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rulz"
8
+ spec.version = Rulz::VERSION
9
+ spec.authors = ["Chase Conklin"]
10
+ spec.email = ["chase@conklins.net"]
11
+ spec.description = %q{Rule Engine for Ruby}
12
+ spec.summary = %q{Enables users to create mail-like rules for processing Ruby objects.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "debugger"
25
+
26
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe "action" do
4
+ before :all do
5
+
6
+ class Person
7
+ attr_accessor :name
8
+ include Rulz
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ end
13
+
14
+ define_rulz do
15
+ action "add prefix" do |prefix|
16
+ its.name = "#{prefix} #{its.name}"
17
+ end
18
+
19
+ action "make doctor" do
20
+ action "add prefix", "Dr."
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ it "should act" do
28
+ bobby = Person.new("Bobby")
29
+ bobby.apply! "add prefix", "Mr."
30
+ bobby.name.should == "Mr. Bobby"
31
+ end
32
+
33
+ it "should be definable with other actions" do
34
+ bobby = Person.new("Bobby")
35
+ bobby.apply! "make doctor"
36
+ bobby.name.should == "Dr. Bobby"
37
+ end
38
+ end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe "condition" do
4
+ context "application" do
5
+ before :all do
6
+ class Person
7
+ include Rulz
8
+ attr_accessor :name, :age
9
+
10
+ def initialize(name, age)
11
+ @name = name
12
+ @age = age
13
+ end
14
+
15
+ define_rulz do
16
+ condition "older than" do |age|
17
+ its.age > age
18
+ end
19
+
20
+ condition "younger than" do |age|
21
+ opposite_of "older than", age
22
+ end
23
+
24
+ condition "middle aged" do
25
+ condition("older than", 40) and condition("younger than", 50)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+
32
+ it "recognizes when conditions are true" do
33
+ billy = Person.new("Billy", 54)
34
+ billy.condition_true?("older than", 50).should be_true
35
+ bobby = Person.new("Bobby", 23)
36
+ bobby.condition_true?("older than", 50).should be_false
37
+ end
38
+
39
+ it "recognizes when conditions are false" do
40
+ bilbo = Person.new("Bilbo", 46)
41
+ bilbo.condition_false?("older than", 23).should be_false
42
+ baggins = Person.new("Baggins", 12)
43
+ baggins.condition_false?("older than", 23).should be_true
44
+ end
45
+
46
+ it "can define conditions based on other conditions" do
47
+ bobby = Person.new("Bobby", 11)
48
+ bobby.condition_true?("younger than", 13).should be_true
49
+ billy = Person.new("Billy", 45)
50
+ billy.condition_true?("middle aged").should be_true
51
+ end
52
+ end
53
+
54
+ context "come predefined in modules" do
55
+ before :all do
56
+ class String
57
+ include Rulz
58
+ include Rulz::Conditions::String
59
+ include Rulz::Conditions::Comparison
60
+ end
61
+ end
62
+
63
+ it "should recognize contains" do
64
+ "foobar".condition_true?("contains", "bar").should be_true
65
+ "foobar".condition_true?("contains", "baz").should be_false
66
+ end
67
+
68
+ it "should recognize matches" do
69
+ "123".condition_true?("matches", /[0-4][0-4][0-4]/).should be_true
70
+ "baz".condition_true?("matches", /[0-4][0-4][0-4]/).should be_false
71
+ end
72
+
73
+ it "should recognize does not match" do
74
+ "baz".condition_true?("does not match", /[0-4][0-4][0-4]/).should be_true
75
+ "123".condition_true?("does not match", /[0-4][0-4][0-4]/).should be_false
76
+ end
77
+
78
+ it "should recognize like" do
79
+ "abc".condition_true?("like", "ABC").should be_true
80
+ "123".condition_true?("like", "ABC").should be_false
81
+ end
82
+
83
+ it "should recognize not like" do
84
+ "123".condition_true?("not like", "ABC").should be_true
85
+ "abc".condition_true?("not like", "ABC").should be_false
86
+ end
87
+
88
+ it "should recognize equal to" do
89
+ "abc".condition_true?("equal to", "abc").should be_true
90
+ "ABC".condition_true?("equal to", "abc").should be_false
91
+ end
92
+
93
+ it "should recognize not equal to" do
94
+ "ABC".condition_true?("not equal to", "abc").should be_true
95
+ "abc".condition_true?("not equal to", "abc").should be_false
96
+ end
97
+
98
+ it "should recognize greater than" do
99
+ "b".condition_true?("greater than", "a").should be_true
100
+ "a".condition_true?("greater than", "b").should be_false
101
+ end
102
+
103
+ it "should recognize less than" do
104
+ "a".condition_true?("less than", "b").should be_true
105
+ "b".condition_true?("less than", "a").should be_false
106
+ end
107
+
108
+ end
109
+
110
+ end
data/spec/rule_spec.rb ADDED
@@ -0,0 +1,270 @@
1
+ require "spec_helper"
2
+
3
+ describe "rules" do
4
+ context "functionality" do
5
+ before :all do
6
+ class Person
7
+ attr_accessor :name, :age, :job
8
+ include Rulz
9
+
10
+ def initialize(name, age, job = nil)
11
+ @name = name
12
+ @age = age
13
+ @job = job
14
+ end
15
+
16
+ define_rulz do
17
+ condition "older than" do |age|
18
+ its.age > age
19
+ end
20
+ condition "younger than" do |age|
21
+ opposite_of "older than", age
22
+ end
23
+ condition "old" do
24
+ condition "older than", 50
25
+ end
26
+ condition "elderly" do
27
+ condition "older than", 80
28
+ end
29
+ condition "name is" do |name|
30
+ its.name == name
31
+ end
32
+ condition "job is" do |job|
33
+ its.job == job
34
+ end
35
+ action "add prefix" do |prefix|
36
+ its.name = "#{prefix} #{its.name}"
37
+ end
38
+ action "add suffix" do |suffix|
39
+ its.name += " #{suffix}"
40
+ end
41
+ action "add job prefix" do |prefix|
42
+ its.job = "#{prefix} #{its.job}"
43
+ end
44
+ rule do
45
+ where("old") { apply! "add prefix", "Grandpa" }
46
+ end
47
+ rule do
48
+ where("elderly") { apply! "add suffix", "Sr."}
49
+ end
50
+ rule do
51
+ where("younger than", 20) { apply! "add suffix", "Jr."}
52
+ end
53
+ rule do
54
+ where(["name is", "Prometheous", "OR", "name is", "Wisdom"]) { apply! "add suffix", "the wise" }
55
+ end
56
+ rule do
57
+ where(["job is", "Contractor", "AND", "younger than", 40]) { apply! "add job prefix", "Jr."}
58
+ end
59
+ rule do
60
+ where ["job is", "Electrician", "OR", "name is", "Bobby", "AND", "job is", "Contractor"] do
61
+ apply! "add prefix", "The skilled"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ it "should apply when the condition is true" do
69
+ bobby = Person.new("Bobby", 52)
70
+ bobby.apply_rules!
71
+ bobby.name.should == "Grandpa Bobby"
72
+ end
73
+
74
+ it "should not apply when the condition is false" do
75
+ bobby = Person.new("Bobby", 32)
76
+ bobby.apply_rules!
77
+ bobby.name.should == "Bobby"
78
+ end
79
+
80
+ it "should accept conditions with arguments" do
81
+ bobby = Person.new("Bobby", 17)
82
+ bobby.apply_rules!
83
+ bobby.name.should == "Bobby Jr."
84
+ end
85
+
86
+ it "should be combined to produce a combined effect" do
87
+ bobby = Person.new("Bobby", 87)
88
+ bobby.apply_rules!
89
+ bobby.name.should == "Grandpa Bobby Sr."
90
+ end
91
+
92
+ it "accepts 'OR' syntax" do
93
+ wisdom = Person.new("Wisdom", 32)
94
+ wisdom.apply_rules!
95
+ wisdom.name.should == "Wisdom the wise"
96
+ prometheous = Person.new("Prometheous", 32)
97
+ prometheous.apply_rules!
98
+ prometheous.name.should == "Prometheous the wise"
99
+ end
100
+
101
+ it "accepts 'AND' syntax" do
102
+ worker = Person.new("Worker", 32, "Contractor")
103
+ worker.apply_rules!
104
+ worker.job.should == "Jr. Contractor"
105
+
106
+ worker = Person.new("Worker", 32, "Builder")
107
+ worker.apply_rules!
108
+ worker.job.should == "Builder"
109
+ end
110
+
111
+ it "accepts both 'AND' and 'OR' syntax together" do
112
+ electrician = Person.new("Electrician", 32, "Electrician")
113
+ electrician.apply_rules!
114
+ electrician.name.should == "The skilled Electrician"
115
+ end
116
+ end
117
+
118
+ context "errors" do
119
+ before :all do
120
+ class Cactus
121
+ include Rulz
122
+ attr_accessor :name, :age
123
+
124
+ def initialize(name, age)
125
+ @name = name
126
+ @age = age
127
+ end
128
+
129
+ define_rulz do
130
+ condition "older than" do |age|
131
+ its.age > age
132
+ end
133
+
134
+ condition "younger than" do |age|
135
+ opposite_of "older than", age
136
+ end
137
+
138
+ condition "middle aged" do
139
+ condition("older than", 40) and condition("younger than", 50)
140
+ end
141
+
142
+ rule do
143
+ where("old") { apply! "add prefix", "Grandpa" } # "old" does not exist
144
+ end
145
+
146
+ end
147
+ end
148
+ end
149
+
150
+ it "should be raised if a condition is not found" do
151
+ bobby = Cactus.new("Bobby", 42)
152
+ expect { bobby.apply_rules! }.to raise_error ArgumentError
153
+ end
154
+
155
+ end
156
+
157
+ context "attributes" do
158
+ before :all do
159
+ class Watermelon
160
+ include Rulz
161
+ attr_accessor :length, :weight, :width
162
+
163
+ def initialize(length, weight, width=0)
164
+ @length = length
165
+ @weight = weight
166
+ @width = width
167
+ end
168
+
169
+ define_rulz do
170
+ attribute :length do
171
+ condition "greater than" do |other_length|
172
+ length > other_length
173
+ end
174
+ condition "less than" do |other_length|
175
+ length < other_length
176
+ end
177
+ end
178
+ attribute :weight do
179
+ condition "greater than" do |other_weight|
180
+ weight > other_weight
181
+ end
182
+ end
183
+ condition "lopsided" do
184
+ its.width > its.length
185
+ end
186
+ action "cut in half" do
187
+ its.length /= 2
188
+ end
189
+ action "scoop out inside" do
190
+ action "cut in half"
191
+ its.weight /= 3
192
+ end
193
+ action "rotate" do
194
+ its.length, its.width = its.width, its.length
195
+ end
196
+ rule do
197
+ where :length => [ "greater than", 12 ] do
198
+ apply! "cut in half"
199
+ end
200
+ end
201
+ rule do
202
+ where [{:length => [ "less than", 6]}, "AND", {:weight => ["greater than", 15]}] do
203
+ apply! "scoop out inside"
204
+ end
205
+ end
206
+ rule do
207
+ where [{:length => ["less than", 6]}, "AND", "lopsided"] do
208
+ apply! "rotate"
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ it "should recognize attribute conditions" do
216
+ w = Watermelon.new(13, 11)
217
+ w.apply_rules!
218
+ w.length.should == 6
219
+ m = Watermelon.new(11, 11)
220
+ m.apply_rules!
221
+ m.length.should == 11
222
+ end
223
+
224
+ it "should allow attribute conditions to be combined" do
225
+ w = Watermelon.new(4, 18)
226
+ w.apply_rules!
227
+ w.length.should == 2
228
+ w.weight.should == 6
229
+ end
230
+
231
+ it "should allow attribute conditions to be combined with normal conditions" do
232
+ w = Watermelon.new(4, 4, 6)
233
+ w.apply_rules!
234
+ w.length.should == 6
235
+ w.width.should == 4
236
+ end
237
+
238
+ end
239
+
240
+ context "using predefined conditions" do
241
+ class Honeydew
242
+ include Rulz
243
+ attr_accessor :diameter
244
+
245
+ define_rulz do
246
+ attribute :diameter do
247
+ type :integer
248
+ end
249
+ action "cut in half" do
250
+ its.diameter /= 2
251
+ end
252
+ rule do
253
+ where(diameter: ["greater than", 5]) { apply! "cut in half" }
254
+ end
255
+ end
256
+
257
+ def initialize(diameter)
258
+ @diameter = diameter
259
+ end
260
+ end
261
+
262
+ it "should not require conidions to be defined" do
263
+ h = Honeydew.new(6)
264
+ h.apply_rules!
265
+ h.diameter.should == 3
266
+ end
267
+
268
+ end
269
+
270
+ end
@@ -0,0 +1,20 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require "rulz"
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rulz
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chase Conklin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: debugger
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Rule Engine for Ruby
70
+ email:
71
+ - chase@conklins.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .ruby-gemset
79
+ - .ruby-version
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - lib/rulz.rb
85
+ - lib/rulz/action.rb
86
+ - lib/rulz/attribute.rb
87
+ - lib/rulz/condition.rb
88
+ - lib/rulz/conditions/comparison.rb
89
+ - lib/rulz/conditions/conditions.rb
90
+ - lib/rulz/conditions/container.rb
91
+ - lib/rulz/conditions/string.rb
92
+ - lib/rulz/definer.rb
93
+ - lib/rulz/evaluator.rb
94
+ - lib/rulz/rule.rb
95
+ - lib/rulz/version.rb
96
+ - rulz.gemspec
97
+ - spec/action_spec.rb
98
+ - spec/condition_spec.rb
99
+ - spec/rule_spec.rb
100
+ - spec/spec_helper.rb
101
+ homepage: ''
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.0.3
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Enables users to create mail-like rules for processing Ruby objects.
125
+ test_files:
126
+ - spec/action_spec.rb
127
+ - spec/condition_spec.rb
128
+ - spec/rule_spec.rb
129
+ - spec/spec_helper.rb