ruy 0.0.1 → 0.1.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 +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
|