ruy 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ruy.rb +131 -0
- data/lib/ruy/adapters.rb +6 -0
- data/lib/ruy/adapters/file_adapter.rb +28 -0
- data/lib/ruy/adapters/sequel_adapter.rb +40 -0
- data/lib/ruy/conditions.rb +16 -0
- data/lib/ruy/conditions/all.rb +13 -0
- data/lib/ruy/conditions/any.rb +18 -0
- data/lib/ruy/conditions/assert.rb +24 -0
- data/lib/ruy/conditions/between.rb +40 -0
- data/lib/ruy/conditions/cond.rb +26 -0
- data/lib/ruy/conditions/eq.rb +30 -0
- data/lib/ruy/conditions/except.rb +50 -0
- data/lib/ruy/conditions/greater_than_or_equal.rb +34 -0
- data/lib/ruy/conditions/include.rb +34 -0
- data/lib/ruy/conditions/included.rb +34 -0
- data/lib/ruy/conditions/less_than.rb +28 -0
- data/lib/ruy/conditions/less_than_or_equal.rb +28 -0
- data/lib/ruy/rule.rb +219 -0
- data/lib/ruy/rule_storage.rb +36 -0
- metadata +36 -4
- data/README.md +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb7f94655e005150da3e73788e1f3a9fd468a736
|
4
|
+
data.tar.gz: 69ef1d941e8316dddcf67559502c091abe7df1af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21f385fc78e17bbb78dc68b26194574c08ed494f16f75b4fa37f570131c8d55aeec4bbc654bcef7b873c5651f5328b274def2f032edfe338c6acbf3edd6e1810
|
7
|
+
data.tar.gz: dd62cdfa7593eb95b43ded250262358f057fa277d583cb7750d04811cbcf116504121fba08d9b93f111c4634371efeb3004cb64c6d7916442802a23d1b6a1748
|
data/lib/ruy.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require_relative 'ruy/rule'
|
2
|
+
require_relative 'ruy/adapters'
|
3
|
+
require_relative 'ruy/conditions'
|
4
|
+
require_relative 'ruy/rule_storage'
|
5
|
+
|
6
|
+
module Ruy
|
7
|
+
class RuleSet < Rule
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :outcomes
|
11
|
+
attr_accessor :metadata
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
|
16
|
+
@outcomes = []
|
17
|
+
@fallback = nil
|
18
|
+
@metadata = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@metadata[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
@metadata[key] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(ctx)
|
30
|
+
var_ctx = VariableContext.new(ctx, @vars)
|
31
|
+
if @apply = super(var_ctx)
|
32
|
+
compute_outcome(var_ctx)
|
33
|
+
else
|
34
|
+
@fallback
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def apply?
|
39
|
+
@apply
|
40
|
+
end
|
41
|
+
|
42
|
+
def compute_outcome(var_ctx)
|
43
|
+
@outcomes.each do |outcome|
|
44
|
+
result = outcome.call(var_ctx)
|
45
|
+
unless result.nil?
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_hash
|
54
|
+
if @outcomes.any?
|
55
|
+
super.merge({ outcomes: @outcomes.map { |o| o.to_hash } })
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def fallback(value)
|
60
|
+
@fallback = value
|
61
|
+
end
|
62
|
+
|
63
|
+
def outcome(value, &block)
|
64
|
+
outcome = Outcome.new(value)
|
65
|
+
|
66
|
+
outcome.instance_exec(&block) if block_given?
|
67
|
+
|
68
|
+
@outcomes << outcome
|
69
|
+
end
|
70
|
+
|
71
|
+
def method_missing(m, *args, &block)
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Context < Hash
|
77
|
+
def self.from_hash(hash)
|
78
|
+
ctx = Context.new
|
79
|
+
ctx.merge!(hash)
|
80
|
+
|
81
|
+
ctx
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Outcome < Rule
|
86
|
+
attr_reader :value
|
87
|
+
|
88
|
+
def initialize(value)
|
89
|
+
super()
|
90
|
+
|
91
|
+
@value = value
|
92
|
+
@params = [@value]
|
93
|
+
end
|
94
|
+
|
95
|
+
def call(ctx)
|
96
|
+
if super(ctx)
|
97
|
+
@value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def ==(o)
|
102
|
+
o.kind_of?(Outcome) &&
|
103
|
+
value == o.value &&
|
104
|
+
conditions == o.conditions
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Context that can resolve variable access
|
109
|
+
class VariableContext
|
110
|
+
def initialize(ctx, vars)
|
111
|
+
@ctx = ctx
|
112
|
+
@vars = vars
|
113
|
+
|
114
|
+
@resolved_vars = {}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Resolve the given attr from the variables or the context
|
118
|
+
# If attribute can't be resolved then throw an exception
|
119
|
+
#
|
120
|
+
# @param [Symbol] attr
|
121
|
+
# @return [Object]
|
122
|
+
def resolve(attr)
|
123
|
+
if @vars.include?(attr)
|
124
|
+
@resolved_vars[attr] ||= @ctx.instance_exec(&@vars[attr])
|
125
|
+
else
|
126
|
+
@ctx.fetch(attr) { |key| @ctx[key.to_s] }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/lib/ruy/adapters.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Adapters
|
3
|
+
class FileAdapter
|
4
|
+
#
|
5
|
+
# @param [String] directory
|
6
|
+
def initialize(directory)
|
7
|
+
@directory = directory
|
8
|
+
end
|
9
|
+
|
10
|
+
# Load all the rule files in the directory specified when the adapter
|
11
|
+
# was created
|
12
|
+
#
|
13
|
+
# @return [Array<Ruy::RuleSet]
|
14
|
+
def load_rules
|
15
|
+
rules = []
|
16
|
+
|
17
|
+
Dir.glob("#{@directory}/*.rb") do |rule_file|
|
18
|
+
rule_set = Ruy::RuleSet.new
|
19
|
+
rule_set.instance_eval(File.read(rule_file))
|
20
|
+
|
21
|
+
rules << rule_set
|
22
|
+
end
|
23
|
+
|
24
|
+
rules
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Ruy
|
5
|
+
module Adapters
|
6
|
+
class SequelAdapter
|
7
|
+
def initialize(connection_data)
|
8
|
+
@db = initialize_connection(connection_data)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Load all the rule objects in the specified database.
|
12
|
+
#
|
13
|
+
# @return [Array<Ruy::RuleSet>]
|
14
|
+
def load_rules(params = {})
|
15
|
+
options = { rules_table: 'event_rules',
|
16
|
+
serialized_data_column: 'data' }.merge(params)
|
17
|
+
|
18
|
+
dataset = @db[options[:rules_table]].all
|
19
|
+
dataset.collect do |row|
|
20
|
+
rule_set = Ruy::RuleSet.from_hash(
|
21
|
+
JSON.parse(row[options[:serialized_data_column]], symbolize_names: true)
|
22
|
+
)
|
23
|
+
|
24
|
+
yield row, rule_set if block_given?
|
25
|
+
rule_set
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
#
|
31
|
+
# @param [Hash] connection_data
|
32
|
+
# @return []
|
33
|
+
def initialize_connection(connection_data)
|
34
|
+
Sequel.connect(adapter: connection_data[:adapter],
|
35
|
+
host: connection_data[:host], database: connection_data[:database],
|
36
|
+
user: connection_data[:user], password: connection_data[:password])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'conditions/all'
|
2
|
+
require_relative 'conditions/any'
|
3
|
+
require_relative 'conditions/assert'
|
4
|
+
require_relative 'conditions/between'
|
5
|
+
require_relative 'conditions/cond'
|
6
|
+
require_relative 'conditions/eq'
|
7
|
+
require_relative 'conditions/except'
|
8
|
+
require_relative 'conditions/greater_than_or_equal'
|
9
|
+
require_relative 'conditions/include'
|
10
|
+
require_relative 'conditions/included'
|
11
|
+
require_relative 'conditions/less_than'
|
12
|
+
require_relative 'conditions/less_than_or_equal'
|
13
|
+
|
14
|
+
module Ruy
|
15
|
+
module Conditions; end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that at least one of the rules will succeed.
|
5
|
+
class Any < Ruy::Rule
|
6
|
+
def call(var_ctx)
|
7
|
+
@conditions.any? do |condition|
|
8
|
+
condition.call(var_ctx)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(o)
|
13
|
+
o.kind_of?(Any) &&
|
14
|
+
conditions == o.conditions
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Asserts that an attribute has a truth value.
|
5
|
+
class Assert < Ruy::Rule
|
6
|
+
attr_reader :attr
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
def initialize(attr)
|
10
|
+
@attr = attr
|
11
|
+
@params = [@attr]
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(var_ctx)
|
15
|
+
var_ctx.resolve(@attr)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(o)
|
19
|
+
o.kind_of?(Assert) &&
|
20
|
+
@attr == o.attr
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context value belongs to a given range.
|
5
|
+
#
|
6
|
+
# Comparison formula: from <= context[attr] <= to
|
7
|
+
class Between < Ruy::Rule
|
8
|
+
attr_reader :attr, :from, :to
|
9
|
+
|
10
|
+
# @param attr Name of the attribute that will be evaluated
|
11
|
+
# @param from Range lower bound
|
12
|
+
# @param to Range upper bound
|
13
|
+
# @yield a block in the context of the current rule
|
14
|
+
def initialize(attr, from, to, &block)
|
15
|
+
super()
|
16
|
+
|
17
|
+
@attr = attr
|
18
|
+
@from = from
|
19
|
+
@to = to
|
20
|
+
|
21
|
+
@params = [@attr, @from, @to]
|
22
|
+
|
23
|
+
instance_exec(&block) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(var_ctx)
|
27
|
+
value = var_ctx.resolve(@attr)
|
28
|
+
@from <= value && value <= @to && super
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(o)
|
32
|
+
o.kind_of?(Between) &&
|
33
|
+
@attr == o.attr &&
|
34
|
+
@from == o.from &&
|
35
|
+
@to == o.to &&
|
36
|
+
self.conditions == o.conditions
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects a successful evaluation of a sub-pair of rules.
|
5
|
+
#
|
6
|
+
# Groups rules in slices of 2 rules. Then evalutes each slice until one of them succeds.
|
7
|
+
# If there's an even number of rules, the last slice will only one rule.
|
8
|
+
#
|
9
|
+
# Cond is handy for mocking if/else if/else constructs.
|
10
|
+
class Cond < Ruy::Rule
|
11
|
+
def call(var_ctx)
|
12
|
+
clauses = @conditions.each_slice(2)
|
13
|
+
|
14
|
+
clauses.any? do |rule_1, rule_2|
|
15
|
+
result = rule_1.call(var_ctx)
|
16
|
+
|
17
|
+
if rule_2
|
18
|
+
result && rule_2.call(var_ctx)
|
19
|
+
else
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute will be equal to a given value.
|
5
|
+
#
|
6
|
+
class Eq < Ruy::Rule
|
7
|
+
attr_reader :attr, :value
|
8
|
+
|
9
|
+
# @param attr Context attribute's name
|
10
|
+
# @param value Expected value
|
11
|
+
def initialize(attr, value)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@attr = attr
|
15
|
+
@value = value
|
16
|
+
@params = [@attr, @value]
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(var_ctx)
|
20
|
+
var_ctx.resolve(@attr) == @value
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(o)
|
24
|
+
o.kind_of?(Eq) &&
|
25
|
+
attr == o.attr &&
|
26
|
+
value == o.value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a condition is not met.
|
5
|
+
#
|
6
|
+
# When a sub-rule is given, Except will expect an unsuccessful evaluation of that sub-rule.
|
7
|
+
# When a sub-rule is not given, Except will expect a context attribute is not equal to a given
|
8
|
+
# value.
|
9
|
+
class Except < Ruy::Rule
|
10
|
+
attr_reader :attr, :value
|
11
|
+
|
12
|
+
# @param attr Context attribute's name
|
13
|
+
# @param value Non-expected value
|
14
|
+
# @yield a block in the context of the current rule
|
15
|
+
def initialize(attr = nil, value = nil, &block)
|
16
|
+
super()
|
17
|
+
|
18
|
+
@attr = attr
|
19
|
+
@value = value
|
20
|
+
|
21
|
+
@params = [@attr, @value]
|
22
|
+
|
23
|
+
if block_given?
|
24
|
+
instance_exec(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(var_ctx)
|
29
|
+
result = true
|
30
|
+
|
31
|
+
if @attr
|
32
|
+
result &&= var_ctx.resolve(@attr) != @value
|
33
|
+
end
|
34
|
+
|
35
|
+
if self.conditions.any?
|
36
|
+
result &&= !super(var_ctx)
|
37
|
+
end
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(o)
|
43
|
+
o.kind_of?(Except) &&
|
44
|
+
@attr == o.attr &&
|
45
|
+
@value == o.value &&
|
46
|
+
@conditions == o.conditions
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute will be greater than or equal to the given value.
|
5
|
+
class GreaterThanOrEqual < Ruy::Rule
|
6
|
+
attr_reader :attr, :value
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
# @param value
|
10
|
+
# @yield a block in the context of the current rule
|
11
|
+
def initialize(attr, value, &block)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@attr = attr
|
15
|
+
@value = value
|
16
|
+
|
17
|
+
@params = [@attr, @value]
|
18
|
+
|
19
|
+
instance_exec(&block) if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(var_ctx)
|
23
|
+
var_ctx.resolve(@attr) >= @value && super
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(o)
|
27
|
+
o.kind_of?(GreaterThanOrEqual) &&
|
28
|
+
attr == o.attr &&
|
29
|
+
value == o.value &&
|
30
|
+
self.conditions == o.conditions
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute is included in a set of values.
|
5
|
+
class Include < Ruy::Rule
|
6
|
+
attr_reader :attr, :values
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
# @param values Expected set of values
|
10
|
+
# @yield a block in the context of the current rule
|
11
|
+
def initialize(attr, values, &block)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@attr = attr
|
15
|
+
@values = values
|
16
|
+
|
17
|
+
@params = [@attr, @values]
|
18
|
+
|
19
|
+
instance_exec(&block) if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(var_ctx)
|
23
|
+
self.values.include?(var_ctx.resolve(self.attr)) && super
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(o)
|
27
|
+
o.kind_of?(Include) &&
|
28
|
+
self.attr == o.attr &&
|
29
|
+
self.values == o.values &&
|
30
|
+
self.conditions == o.conditions
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a value is included in a set of values from the context attribute.
|
5
|
+
class Included < Ruy::Rule
|
6
|
+
attr_reader :attr, :value
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
# @param value Expected set of values
|
10
|
+
# @yield a block in the context of the current rule
|
11
|
+
def initialize(attr, value, &block)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@attr = attr
|
15
|
+
@value = value
|
16
|
+
|
17
|
+
@params = [@attr, @value]
|
18
|
+
|
19
|
+
instance_exec(&block) if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(var_ctx)
|
23
|
+
var_ctx.resolve(self.attr).include?(self.value) && super
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(o)
|
27
|
+
o.kind_of?(Included) &&
|
28
|
+
self.attr == o.attr &&
|
29
|
+
self.value == o.value &&
|
30
|
+
self.conditions == o.conditions
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute will be less than given value.
|
5
|
+
class LessThan < Ruy::Rule
|
6
|
+
attr_reader :attr, :value
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
# @param value
|
10
|
+
def initialize(attr, value)
|
11
|
+
@attr = attr
|
12
|
+
@value = value
|
13
|
+
|
14
|
+
@params = [@attr, @value]
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(var_ctx)
|
18
|
+
var_ctx.resolve(@attr) < @value
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(o)
|
22
|
+
o.kind_of?(LessThan) &&
|
23
|
+
attr == o.attr &&
|
24
|
+
value == o.value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute will be less than or equal to the given value.
|
5
|
+
class LessThanOrEqual < Ruy::Rule
|
6
|
+
attr_reader :attr, :value
|
7
|
+
|
8
|
+
# @param attr Context attribute's name
|
9
|
+
# @param value
|
10
|
+
def initialize(attr, value)
|
11
|
+
@attr = attr
|
12
|
+
@value = value
|
13
|
+
|
14
|
+
@params = [@attr, @value]
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(var_ctx)
|
18
|
+
var_ctx.resolve(@attr) <= @value
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(o)
|
22
|
+
o.kind_of?(LessThanOrEqual) &&
|
23
|
+
attr == o.attr &&
|
24
|
+
value == o.value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/ruy/rule.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
module Ruy
|
2
|
+
|
3
|
+
# Returns a constant using a qualified constant name string
|
4
|
+
#
|
5
|
+
# @param [String] name
|
6
|
+
# @return [Object]
|
7
|
+
def self.qualified_const_get(name)
|
8
|
+
root = Object
|
9
|
+
name.split('::').each do |klass|
|
10
|
+
root = root.const_get(klass)
|
11
|
+
end
|
12
|
+
|
13
|
+
root
|
14
|
+
end
|
15
|
+
|
16
|
+
# A rule is a set of conditions
|
17
|
+
class Rule
|
18
|
+
|
19
|
+
attr_reader :conditions
|
20
|
+
attr_reader :vars
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@conditions = []
|
24
|
+
@vars = {}
|
25
|
+
@attrs = {}
|
26
|
+
@params = []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Serialize a rule object as a hash
|
30
|
+
#
|
31
|
+
# @return [Hash]
|
32
|
+
def to_hash
|
33
|
+
ret = { node: self.class.name, params: params }
|
34
|
+
ret[:conditions] = @conditions.map(&:to_hash) if @conditions.any?
|
35
|
+
|
36
|
+
ret
|
37
|
+
end
|
38
|
+
|
39
|
+
# Look to the given keys, and load the rule nodes in their values
|
40
|
+
#
|
41
|
+
# @param [Array<Symbol>] keys
|
42
|
+
# @param [Hash] hash
|
43
|
+
# @return [Ruy::Rule]
|
44
|
+
def load_rule_objects_from(hash, *keys)
|
45
|
+
keys.each do |key|
|
46
|
+
if hash.has_key?(key)
|
47
|
+
hash[key].each { |o| self.send(key) << Ruy::Rule.from_hash(o) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Load a new role from it hash representation
|
55
|
+
#
|
56
|
+
# @param [Hash] hash
|
57
|
+
# @return [Ruy::Rule]
|
58
|
+
def self.from_hash(hash)
|
59
|
+
rule = Ruy.qualified_const_get(hash[:node]).new(*hash[:params])
|
60
|
+
rule.load_rule_objects_from(hash, :conditions, :outcomes)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Adds an All condition.
|
64
|
+
#
|
65
|
+
# @yield Evaluates the given block in the context of the current rule
|
66
|
+
def all(&block)
|
67
|
+
cond = Conditions::All.new
|
68
|
+
cond.instance_exec(&block)
|
69
|
+
|
70
|
+
@conditions << cond
|
71
|
+
end
|
72
|
+
|
73
|
+
# Adds an Any condition.
|
74
|
+
#
|
75
|
+
# @yield Evaluates the given block in the context of the current rule
|
76
|
+
def any(&block)
|
77
|
+
cond = Conditions::Any.new
|
78
|
+
cond.instance_exec(&block)
|
79
|
+
|
80
|
+
@conditions << cond
|
81
|
+
end
|
82
|
+
|
83
|
+
# Adds an Assert condition.
|
84
|
+
#
|
85
|
+
# @param (see Conditions::Assert#initialize)
|
86
|
+
def assert(attr)
|
87
|
+
@conditions << Conditions::Assert.new(attr)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Adds a Between condition.
|
91
|
+
#
|
92
|
+
# @param (see Conditions::Between#initialize)
|
93
|
+
def between(attr, from, to, &block)
|
94
|
+
@conditions << Conditions::Between.new(attr, from, to, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Adds an Eq condition.
|
98
|
+
#
|
99
|
+
# @param (see Conditions::Eq#initialize)
|
100
|
+
def eq(attr, value, &block)
|
101
|
+
@conditions << Conditions::Eq.new(attr, value, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Adds a Cond condition.
|
105
|
+
#
|
106
|
+
# @yield Evaluates the given block in the context of the current rule
|
107
|
+
def cond(&block)
|
108
|
+
cond = Conditions::Cond.new
|
109
|
+
cond.instance_exec(&block)
|
110
|
+
|
111
|
+
@conditions << cond
|
112
|
+
end
|
113
|
+
|
114
|
+
# Adds an Except condition.
|
115
|
+
#
|
116
|
+
# @param (see Conditions::Except#initialize)
|
117
|
+
def except(attr = nil, value = nil, &block)
|
118
|
+
@conditions << Conditions::Except.new(attr, value, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Adds a GreaterThanOrEqual condition.
|
122
|
+
#
|
123
|
+
# @param (see Conditions::GreaterThanOrEqual#initialize)
|
124
|
+
def greater_than_or_equal(attr, value)
|
125
|
+
@conditions << Conditions::GreaterThanOrEqual.new(attr, value)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Adds an Include condition.
|
129
|
+
#
|
130
|
+
# @param (see Conditions::Include#initialize)
|
131
|
+
def include(attr, values, &block)
|
132
|
+
@conditions << Conditions::Include.new(attr, values, &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Adds an Included condition.
|
136
|
+
#
|
137
|
+
# @param (see Conditions::Included#initialize)
|
138
|
+
def included(attr, value, &block)
|
139
|
+
@conditions << Conditions::Included.new(attr, value, &block)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Adds a LessOrEqualThan condition.
|
143
|
+
#
|
144
|
+
# @param (see Conditions::LessOrEqualThan#initialize)
|
145
|
+
def less_than_or_equal(attr, value, &block)
|
146
|
+
@conditions << Conditions::LessThanOrEqual.new(attr, value)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Adds a LessThan condition.
|
150
|
+
#
|
151
|
+
# @param (see Conditions::LessThan#initialize)
|
152
|
+
def less_than(attr, value)
|
153
|
+
@conditions << Conditions::LessThan.new(attr, value)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Gets attribute's value from the given name.
|
157
|
+
#
|
158
|
+
# @param name
|
159
|
+
#
|
160
|
+
# @return The attribute's value.
|
161
|
+
def get(name)
|
162
|
+
@attrs[name]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Sets a custom attribute.
|
166
|
+
#
|
167
|
+
# @param name Attribute's name
|
168
|
+
# @param value Attribute's value
|
169
|
+
def set(name, value)
|
170
|
+
@attrs[name] = value
|
171
|
+
end
|
172
|
+
|
173
|
+
# Defines a variable.
|
174
|
+
#
|
175
|
+
# If both value and block are given, only the block will be taken into account.
|
176
|
+
#
|
177
|
+
# @param name The name of the variable
|
178
|
+
# @param value The value of the variable
|
179
|
+
#
|
180
|
+
# @yield a block that will resolve the variable's value
|
181
|
+
def var(name, value = nil, &block)
|
182
|
+
if block_given?
|
183
|
+
@vars[name] = block
|
184
|
+
else
|
185
|
+
@vars[name] = lambda { value }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Evaluates all conditions.
|
190
|
+
#
|
191
|
+
# @return [true] When all conditions succeeds
|
192
|
+
# @return [false] Otherwise
|
193
|
+
def call(ctx)
|
194
|
+
success = @conditions.take_while do |condition|
|
195
|
+
condition.call(ctx)
|
196
|
+
end
|
197
|
+
|
198
|
+
success.length == @conditions.length
|
199
|
+
rescue NoMethodError
|
200
|
+
false
|
201
|
+
end
|
202
|
+
|
203
|
+
def ==(o)
|
204
|
+
o.kind_of?(Rule) &&
|
205
|
+
conditions == o.conditions &&
|
206
|
+
vars.keys == o.vars.keys
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
# Getter method for rules params. It returns all the params without nil objects
|
212
|
+
#
|
213
|
+
# @return [Array<Object>]
|
214
|
+
def params
|
215
|
+
@params.compact
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Ruy
|
2
|
+
class RuleStorage
|
3
|
+
attr_writer :adapter
|
4
|
+
|
5
|
+
def initialize(adapter, rules = [])
|
6
|
+
@adapter = adapter
|
7
|
+
@rules = rules
|
8
|
+
end
|
9
|
+
|
10
|
+
# Evaluate the given context against all the loaded rules
|
11
|
+
#
|
12
|
+
# @param [Hash] ctx
|
13
|
+
# @return [Array<Object>]
|
14
|
+
def evaluate_all(ctx)
|
15
|
+
@rules.map { |rule| rule.call(ctx) }.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
# The first rule that apply will return the method
|
19
|
+
#
|
20
|
+
# @param [Hash] ctx
|
21
|
+
# @return [Object]
|
22
|
+
def evaluate_first(ctx)
|
23
|
+
@rules.each do |rule|
|
24
|
+
result = rule.call(ctx)
|
25
|
+
return result if rule.apply?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Load all the rules from the adapter
|
30
|
+
#
|
31
|
+
def load_rules(params = {}, &block)
|
32
|
+
@rules = @adapter.load_rules(params, &block)
|
33
|
+
end
|
34
|
+
alias_method :relaod, :load_rules
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moove-IT
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -24,14 +24,46 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.12.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.12.0
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
executables: []
|
30
44
|
extensions: []
|
31
45
|
extra_rdoc_files: []
|
32
46
|
files:
|
33
|
-
-
|
34
|
-
|
47
|
+
- lib/ruy/adapters/file_adapter.rb
|
48
|
+
- lib/ruy/adapters/sequel_adapter.rb
|
49
|
+
- lib/ruy/adapters.rb
|
50
|
+
- lib/ruy/conditions/all.rb
|
51
|
+
- lib/ruy/conditions/any.rb
|
52
|
+
- lib/ruy/conditions/assert.rb
|
53
|
+
- lib/ruy/conditions/between.rb
|
54
|
+
- lib/ruy/conditions/cond.rb
|
55
|
+
- lib/ruy/conditions/eq.rb
|
56
|
+
- lib/ruy/conditions/except.rb
|
57
|
+
- lib/ruy/conditions/greater_than_or_equal.rb
|
58
|
+
- lib/ruy/conditions/include.rb
|
59
|
+
- lib/ruy/conditions/included.rb
|
60
|
+
- lib/ruy/conditions/less_than.rb
|
61
|
+
- lib/ruy/conditions/less_than_or_equal.rb
|
62
|
+
- lib/ruy/conditions.rb
|
63
|
+
- lib/ruy/rule.rb
|
64
|
+
- lib/ruy/rule_storage.rb
|
65
|
+
- lib/ruy.rb
|
66
|
+
homepage: https://github.com/Moove-it/ruy
|
35
67
|
licenses:
|
36
68
|
- MIT
|
37
69
|
metadata: {}
|
data/README.md
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
ruy comming soon
|