rulz 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +1 -0
- data/lib/rulz.rb +46 -0
- data/lib/rulz/action.rb +18 -0
- data/lib/rulz/attribute.rb +40 -0
- data/lib/rulz/condition.rb +21 -0
- data/lib/rulz/conditions/comparison.rb +52 -0
- data/lib/rulz/conditions/conditions.rb +19 -0
- data/lib/rulz/conditions/container.rb +35 -0
- data/lib/rulz/conditions/string.rb +47 -0
- data/lib/rulz/definer.rb +24 -0
- data/lib/rulz/evaluator.rb +123 -0
- data/lib/rulz/rule.rb +21 -0
- data/lib/rulz/version.rb +3 -0
- data/rulz.gemspec +26 -0
- data/spec/action_spec.rb +38 -0
- data/spec/condition_spec.rb +110 -0
- data/spec/rule_spec.rb +270 -0
- data/spec/spec_helper.rb +20 -0
- metadata +129 -0
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
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rulz
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p195
|
data/Gemfile
ADDED
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
|
data/lib/rulz/action.rb
ADDED
@@ -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
|
data/lib/rulz/definer.rb
ADDED
@@ -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
|
data/lib/rulz/version.rb
ADDED
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
|
data/spec/action_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|