ruy 0.3.0 → 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 +4 -4
- data/README.md +33 -37
- data/lib/ruy.rb +0 -1
- data/lib/ruy/conditions.rb +2 -0
- data/lib/ruy/conditions/all.rb +1 -6
- data/lib/ruy/conditions/any.rb +2 -2
- data/lib/ruy/conditions/assert.rb +1 -1
- data/lib/ruy/conditions/between.rb +3 -5
- data/lib/ruy/conditions/compound_condition.rb +25 -0
- data/lib/ruy/conditions/cond.rb +5 -5
- data/lib/ruy/conditions/condition.rb +15 -0
- data/lib/ruy/conditions/day_of_week.rb +1 -1
- data/lib/ruy/conditions/eq.rb +1 -1
- data/lib/ruy/conditions/except.rb +3 -18
- data/lib/ruy/conditions/greater_than.rb +3 -5
- data/lib/ruy/conditions/greater_than_or_equal.rb +3 -5
- data/lib/ruy/conditions/in.rb +3 -5
- data/lib/ruy/conditions/in_cyclic_order.rb +4 -6
- data/lib/ruy/conditions/include.rb +3 -5
- data/lib/ruy/conditions/included.rb +3 -5
- data/lib/ruy/conditions/less_than.rb +1 -2
- data/lib/ruy/conditions/less_than_or_equal.rb +1 -1
- data/lib/ruy/conditions/tz.rb +3 -3
- data/lib/ruy/dsl.rb +6 -6
- data/lib/ruy/outcome.rb +4 -4
- data/lib/ruy/rule.rb +57 -17
- data/lib/ruy/utils.rb +1 -0
- data/lib/ruy/utils/rules.rb +40 -0
- metadata +4 -2
- data/lib/ruy/rule_set.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 062667c3bb0ac6a9796f8bbda6dded7918ec9b11
|
4
|
+
data.tar.gz: bd51df8ff61cab98c007849cf4a1079dc8f83956
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48d547cdb0e47a50ede40c794d3a90eb012ab27c34e31aba0d696ae3936bcbdd0a0501887ba269833135b61e70b34d684892ff84e1e698918f54c9d9c9c7a2e
|
7
|
+
data.tar.gz: 78a9ecccf5c5f9f0549f7c53fa2e3515d7d900afe4695c1b52f7e5b2a019c38158e385e2daa088a7c716a396d7c31c6e3b06f70185b9d9a42b0dc756450f921a
|
data/README.md
CHANGED
@@ -4,7 +4,9 @@ Ruy is a library for defining a set of conditions and evaluating them against a
|
|
4
4
|
|
5
5
|
``` ruby
|
6
6
|
# discount_day.rb
|
7
|
-
gifter = Ruy::
|
7
|
+
gifter = Ruy::Rule.new
|
8
|
+
|
9
|
+
gifter.set :name, 'Unforgettable Fridays'
|
8
10
|
|
9
11
|
gifter.eq :friday, :day_of_week
|
10
12
|
|
@@ -21,7 +23,7 @@ gifter.outcome 3
|
|
21
23
|
gifter.fallback 0
|
22
24
|
```
|
23
25
|
|
24
|
-
|
26
|
+
Rules are evaluated against a context (the `Hash` being passed to `#call`) and return the first outcome that matches.
|
25
27
|
|
26
28
|
``` ruby
|
27
29
|
gifter.call(day_of_week: :friday, amount: 314)
|
@@ -49,6 +51,13 @@ gifter.call(day_of_week: :monday, amount: 124)
|
|
49
51
|
# => 0
|
50
52
|
```
|
51
53
|
|
54
|
+
Retrieve rule attributes.
|
55
|
+
``` ruby
|
56
|
+
gifter.get(:name)
|
57
|
+
|
58
|
+
# => 'Unforgettable Fridays'
|
59
|
+
```
|
60
|
+
|
52
61
|
## Key concepts
|
53
62
|
|
54
63
|
Ruy at its core is about evaluating a set of conditions against a context in order to return a result.
|
@@ -70,46 +79,28 @@ Available conditions:
|
|
70
79
|
- greater_than *Tests that context value is greater than something*
|
71
80
|
- greater_than_or_equal *Tests that context value is greater than or equal to something*
|
72
81
|
- in *A context value must belong to a specified list of values*
|
73
|
-
- in_cyclic_order *
|
82
|
+
- in_cyclic_order *Expects that a value is included in a cyclic order*
|
74
83
|
- include *The context attribute must include a specified value*
|
75
84
|
- less_than_or_equal *Tests that context value is less than or equal to something*
|
76
85
|
- less_than *Tests that context value is less than something*
|
77
86
|
|
78
|
-
|
79
|
-
also be met.
|
80
|
-
|
81
|
-
``` ruby
|
82
|
-
between 0, 1_000, :amount do
|
83
|
-
eq :friday, :day_of_week
|
84
|
-
end
|
85
|
-
```
|
86
|
-
|
87
|
-
is equivalent to:
|
88
|
-
|
89
|
-
``` ruby
|
90
|
-
all do
|
91
|
-
between 0, 1_000, :amount
|
92
|
-
eq :friday, :day_of_week
|
93
|
-
end
|
94
|
-
```
|
87
|
+
### Rules
|
95
88
|
|
96
|
-
|
97
|
-
|
98
|
-
A ruleset is a set of conditions that must suffice and returns a value resulting from either an
|
89
|
+
A Rule is a set of conditions that must suffice and returns a value resulting from either an
|
99
90
|
outcome or a fallback.
|
100
91
|
|
101
92
|
### Contexts
|
102
93
|
|
103
|
-
A context is a `Hash` from which values are fetched in order to evaluate a
|
94
|
+
A context is a `Hash` from which values are fetched in order to evaluate a Rule.
|
104
95
|
|
105
96
|
### Lazy values
|
106
97
|
|
107
|
-
|
98
|
+
Rules can define lazy values. The context must provide a proc which is evaluted only once the first time the value is needed. The result returned by the proc is memoized and used to evaluate subsequent conditions.
|
108
99
|
|
109
100
|
|
110
101
|
``` ruby
|
111
102
|
# premium_discount_day.rb
|
112
|
-
gifter = Ruy::
|
103
|
+
gifter = Ruy::Rule.new
|
113
104
|
|
114
105
|
gifter.let :amounts_average # an expensive calculation
|
115
106
|
|
@@ -125,23 +116,23 @@ gifter.call(day_of_week: :friday, amounts_average: -> { Stats::Amounts.compute_a
|
|
125
116
|
```
|
126
117
|
### Outcomes
|
127
118
|
|
128
|
-
An outcome is the result of a successful
|
119
|
+
An outcome is the result of a successful Rule evaluation. An outcome can also have nested
|
129
120
|
conditions, in such case, if the conditions meet, the outcome value is returned.
|
130
121
|
|
131
|
-
A
|
122
|
+
A Rule can have multiple outcomes, the first matching one is returned.
|
132
123
|
|
133
124
|
### Time Zone awareness
|
134
125
|
|
135
126
|
When it comes to matching times in different time zones, Ruy is bundled with a built in `tz` block that will enable specific matchers to make time zone-aware comparisons.
|
136
127
|
|
137
128
|
```ruby
|
138
|
-
|
129
|
+
rule = Ruy::Rule.new
|
139
130
|
|
140
|
-
|
131
|
+
rule.tz 'America/New_York' do
|
141
132
|
eq '2015-01-01T00:00:00', :timestamp
|
142
133
|
end
|
143
134
|
|
144
|
-
|
135
|
+
rule.outcome 'Happy New Year, NYC!'
|
145
136
|
```
|
146
137
|
|
147
138
|
For example, if the timestamp provided in the context is a Ruby Time object in UTC (zero offset from UTC), `eq` as child of a `tz` block will take the time zone passed as argument to the block (`America/New_York`) to calculate the current offset and make the comparison.
|
@@ -157,9 +148,9 @@ Where the time zone identifier is optional, but if you specify it, will take pre
|
|
157
148
|
Inside any `tz` block, there's a matcher to look for a specific day of the week in the time zone of the block.
|
158
149
|
|
159
150
|
```ruby
|
160
|
-
|
151
|
+
rule = Ruy::Rule.new
|
161
152
|
|
162
|
-
|
153
|
+
rule.any do
|
163
154
|
tz 'America/New_York' do
|
164
155
|
day_of_week :saturday, :timestamp
|
165
156
|
end
|
@@ -169,7 +160,7 @@ ruleset.any do
|
|
169
160
|
end
|
170
161
|
end
|
171
162
|
|
172
|
-
|
163
|
+
rule.outcome 'Have a nice weekend, NYC!'
|
173
164
|
```
|
174
165
|
|
175
166
|
This matcher supports both the `Symbol` and number syntax in the range `(0..6)` starting on Sunday.
|
@@ -183,15 +174,20 @@ You cannot use matchers inside nested blocks in a `tz` block expecting them to w
|
|
183
174
|
A possible workaround for this is to use `tz` blocks inside the nested block in question:
|
184
175
|
|
185
176
|
```ruby
|
186
|
-
|
187
|
-
|
177
|
+
rule = Ruy::Rule.new
|
178
|
+
|
179
|
+
rule.any do
|
188
180
|
tz 'America/New_York' { eq '2015-01-01T00:00:00', :timestamp }
|
189
181
|
tz 'America/New_York' { eq '2015-01-01T02:00:00zUTC', :timestamp }
|
190
182
|
end
|
191
183
|
|
192
|
-
|
184
|
+
rule.outcome 'Happy New Year, NYC!'
|
193
185
|
```
|
194
186
|
|
195
187
|
Support for time zone awareness in nested blocks inside `tz` blocks is planned. This workaround could stop working in future versions; use it at your own risk.
|
196
188
|
|
197
189
|
Ruy depends on [TZInfo](http://tzinfo.github.io/ "TZ Info website") to calculate offsets using IANA's Time Zone Database. Check their website for information about time zone identifiers.
|
190
|
+
|
191
|
+
### Documentation
|
192
|
+
|
193
|
+
[RubyDoc.info](http://www.rubydoc.info/github/moove-it/ruy)
|
data/lib/ruy.rb
CHANGED
data/lib/ruy/conditions.rb
CHANGED
data/lib/ruy/conditions/all.rb
CHANGED
data/lib/ruy/conditions/any.rb
CHANGED
@@ -2,9 +2,9 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that at least one of the rules will succeed.
|
5
|
-
class Any <
|
5
|
+
class Any < CompoundCondition
|
6
6
|
def call(ctx)
|
7
|
-
|
7
|
+
conditions.any? do |condition|
|
8
8
|
condition.call(ctx)
|
9
9
|
end
|
10
10
|
end
|
@@ -4,7 +4,7 @@ module Ruy
|
|
4
4
|
# Expects that a context value belongs to a given range.
|
5
5
|
#
|
6
6
|
# Comparison formula: from <= context[attr] <= to
|
7
|
-
class Between <
|
7
|
+
class Between < Condition
|
8
8
|
attr_reader :attr, :from, :to
|
9
9
|
|
10
10
|
# @param from Range lower bound
|
@@ -17,20 +17,18 @@ module Ruy
|
|
17
17
|
@from = from
|
18
18
|
@to = to
|
19
19
|
@attr = attr
|
20
|
-
instance_exec(&block) if block_given?
|
21
20
|
end
|
22
21
|
|
23
22
|
def call(ctx)
|
24
23
|
value = ctx.resolve(@attr)
|
25
|
-
@from <= value && @to >= value
|
24
|
+
@from <= value && @to >= value
|
26
25
|
end
|
27
26
|
|
28
27
|
def ==(o)
|
29
28
|
o.kind_of?(Between) &&
|
30
29
|
@attr == o.attr &&
|
31
30
|
@from == o.from &&
|
32
|
-
@to == o.to
|
33
|
-
self.conditions == o.conditions
|
31
|
+
@to == o.to
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
class CompoundCondition < Condition
|
5
|
+
|
6
|
+
attr_reader :conditions
|
7
|
+
|
8
|
+
def initialize(*params)
|
9
|
+
super
|
10
|
+
|
11
|
+
@conditions = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Evaluates all conditions.
|
15
|
+
#
|
16
|
+
# @return [true] When all conditions succeeds
|
17
|
+
# @return [false] Otherwise
|
18
|
+
def call(ctx)
|
19
|
+
@conditions.all? do |condition|
|
20
|
+
condition.call(ctx)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/ruy/conditions/cond.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
-
# Expects a successful evaluation of a sub-pair of
|
4
|
+
# Expects a successful evaluation of a sub-pair of conditions.
|
5
5
|
#
|
6
|
-
# Groups rules in slices of 2
|
7
|
-
# If there's an even number of
|
6
|
+
# Groups rules in slices of 2 conditions. Then evalutes each slice until one of them succeds.
|
7
|
+
# If there's an even number of conditions, the last slice will have only one condition.
|
8
8
|
#
|
9
9
|
# Cond is handy for mocking if/else if/else constructs.
|
10
|
-
class Cond <
|
10
|
+
class Cond < CompoundCondition
|
11
11
|
def call(ctx)
|
12
|
-
clauses =
|
12
|
+
clauses = conditions.each_slice(2)
|
13
13
|
|
14
14
|
clauses.any? do |rule_1, rule_2|
|
15
15
|
result = rule_1.call(ctx)
|
@@ -5,7 +5,7 @@ module Ruy
|
|
5
5
|
module Conditions
|
6
6
|
|
7
7
|
# Expects that a Time object's date corresponds to a specified day of the week.
|
8
|
-
class DayOfWeek <
|
8
|
+
class DayOfWeek < Condition
|
9
9
|
DAYS_INTO_WEEK = %w(sunday monday tuesday wednesday thursday friday saturday)
|
10
10
|
attr_reader :attr, :value, :tz_identifier
|
11
11
|
|
data/lib/ruy/conditions/eq.rb
CHANGED
@@ -6,37 +6,22 @@ module Ruy
|
|
6
6
|
# When a sub-rule is given, Except will expect an unsuccessful evaluation of that sub-rule.
|
7
7
|
# When a sub-rule is not given, Except will expect a context attribute is not equal to a given
|
8
8
|
# value.
|
9
|
-
class Except <
|
10
|
-
attr_reader :attr, :value
|
9
|
+
class Except < CompoundCondition
|
11
10
|
|
12
11
|
# @param value Non-expected value
|
13
12
|
# @param attr Context attribute's name
|
14
13
|
# @yield a block in the context of the current rule
|
15
|
-
def initialize(
|
14
|
+
def initialize(&block)
|
16
15
|
super
|
17
|
-
@value = value
|
18
|
-
@attr = attr
|
19
16
|
instance_exec(&block) if block_given?
|
20
17
|
end
|
21
18
|
|
22
19
|
def call(ctx)
|
23
|
-
|
24
|
-
|
25
|
-
if @attr
|
26
|
-
result &&= ctx.resolve(@attr) != @value
|
27
|
-
end
|
28
|
-
|
29
|
-
if self.conditions.any?
|
30
|
-
result &&= !super(ctx)
|
31
|
-
end
|
32
|
-
|
33
|
-
result
|
20
|
+
!super
|
34
21
|
end
|
35
22
|
|
36
23
|
def ==(o)
|
37
24
|
o.kind_of?(Except) &&
|
38
|
-
@attr == o.attr &&
|
39
|
-
@value == o.value &&
|
40
25
|
@conditions == o.conditions
|
41
26
|
end
|
42
27
|
end
|
@@ -2,7 +2,7 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a context attribute will be greater than the given value.
|
5
|
-
class GreaterThan <
|
5
|
+
class GreaterThan < Condition
|
6
6
|
attr_reader :attr, :value
|
7
7
|
|
8
8
|
# @param value
|
@@ -12,18 +12,16 @@ module Ruy
|
|
12
12
|
super
|
13
13
|
@value = value
|
14
14
|
@attr = attr
|
15
|
-
instance_exec(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def call(var_ctx)
|
19
|
-
@value < var_ctx.resolve(@attr)
|
18
|
+
@value < var_ctx.resolve(@attr)
|
20
19
|
end
|
21
20
|
|
22
21
|
def ==(o)
|
23
22
|
o.kind_of?(GreaterThan) &&
|
24
23
|
attr == o.attr &&
|
25
|
-
value == o.value
|
26
|
-
self.conditions == o.conditions
|
24
|
+
value == o.value
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
@@ -2,7 +2,7 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a context attribute will be greater than or equal to the given value.
|
5
|
-
class GreaterThanOrEqual <
|
5
|
+
class GreaterThanOrEqual < Condition
|
6
6
|
attr_reader :attr, :value
|
7
7
|
|
8
8
|
# @param value
|
@@ -12,18 +12,16 @@ module Ruy
|
|
12
12
|
super
|
13
13
|
@value = value
|
14
14
|
@attr = attr
|
15
|
-
instance_exec(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def call(ctx)
|
19
|
-
@value <= ctx.resolve(@attr)
|
18
|
+
@value <= ctx.resolve(@attr)
|
20
19
|
end
|
21
20
|
|
22
21
|
def ==(o)
|
23
22
|
o.kind_of?(GreaterThanOrEqual) &&
|
24
23
|
attr == o.attr &&
|
25
|
-
value == o.value
|
26
|
-
self.conditions == o.conditions
|
24
|
+
value == o.value
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
data/lib/ruy/conditions/in.rb
CHANGED
@@ -2,7 +2,7 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a context attribute is included in a set of values.
|
5
|
-
class In <
|
5
|
+
class In < Condition
|
6
6
|
attr_reader :attr, :values
|
7
7
|
|
8
8
|
# @param values Expected set of values
|
@@ -12,18 +12,16 @@ module Ruy
|
|
12
12
|
super
|
13
13
|
@values = values
|
14
14
|
@attr = attr
|
15
|
-
instance_exec(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def call(var_ctx)
|
19
|
-
self.values.include?(var_ctx.resolve(self.attr))
|
18
|
+
self.values.include?(var_ctx.resolve(self.attr))
|
20
19
|
end
|
21
20
|
|
22
21
|
def ==(o)
|
23
22
|
o.kind_of?(In) &&
|
24
23
|
self.attr == o.attr &&
|
25
|
-
self.values == o.values
|
26
|
-
self.conditions == o.conditions
|
24
|
+
self.values == o.values
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
|
-
class InCyclicOrder <
|
3
|
+
class InCyclicOrder < Condition
|
4
4
|
attr_reader :from, :to, :attr
|
5
5
|
|
6
6
|
def initialize(from, to, attr, &block)
|
@@ -8,16 +8,15 @@ module Ruy
|
|
8
8
|
@from = from
|
9
9
|
@to = to
|
10
10
|
@attr = attr
|
11
|
-
instance_exec(&block) if block_given?
|
12
11
|
end
|
13
12
|
|
14
13
|
def call(ctx)
|
15
14
|
value = ctx.resolve(@attr)
|
16
15
|
|
17
16
|
if @from > @to
|
18
|
-
(@from <= value || @to >= value)
|
17
|
+
(@from <= value || @to >= value)
|
19
18
|
else
|
20
|
-
(@from <= value && @to >= value)
|
19
|
+
(@from <= value && @to >= value)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -25,8 +24,7 @@ module Ruy
|
|
25
24
|
o.kind_of?(self.class) &&
|
26
25
|
@from == o.from &&
|
27
26
|
@to == o.to &&
|
28
|
-
@attr == o.attr
|
29
|
-
self.conditions == o.conditions
|
27
|
+
@attr == o.attr
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
@@ -2,7 +2,7 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a value is included in a set of values from the context attribute.
|
5
|
-
class Include <
|
5
|
+
class Include < Condition
|
6
6
|
attr_reader :attr, :value
|
7
7
|
|
8
8
|
# @param value Expected set of values
|
@@ -12,18 +12,16 @@ module Ruy
|
|
12
12
|
super
|
13
13
|
@value = value
|
14
14
|
@attr = attr
|
15
|
-
instance_exec(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def call(ctx)
|
19
|
-
ctx.resolve(self.attr).include?(self.value)
|
18
|
+
ctx.resolve(self.attr).include?(self.value)
|
20
19
|
end
|
21
20
|
|
22
21
|
def ==(o)
|
23
22
|
o.kind_of?(Include) &&
|
24
23
|
self.attr == o.attr &&
|
25
|
-
self.value == o.value
|
26
|
-
self.conditions == o.conditions
|
24
|
+
self.value == o.value
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
@@ -2,7 +2,7 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a value is included in a set of values from the context attribute.
|
5
|
-
class Included <
|
5
|
+
class Included < Condition
|
6
6
|
attr_reader :attr, :value
|
7
7
|
|
8
8
|
# @param attr Context attribute's name
|
@@ -12,18 +12,16 @@ module Ruy
|
|
12
12
|
super
|
13
13
|
@attr = attr
|
14
14
|
@value = value
|
15
|
-
instance_exec(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def call(ctx)
|
19
|
-
ctx.resolve(self.attr).include?(self.value)
|
18
|
+
ctx.resolve(self.attr).include?(self.value)
|
20
19
|
end
|
21
20
|
|
22
21
|
def ==(o)
|
23
22
|
o.kind_of?(Included) &&
|
24
23
|
self.attr == o.attr &&
|
25
|
-
self.value == o.value
|
26
|
-
self.conditions == o.conditions
|
24
|
+
self.value == o.value
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
@@ -2,13 +2,12 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a context attribute will be less than given value.
|
5
|
-
class LessThan <
|
5
|
+
class LessThan < Condition
|
6
6
|
attr_reader :attr, :value
|
7
7
|
|
8
8
|
# @param value
|
9
9
|
# @param attr Context attribute's name
|
10
10
|
def initialize(value, attr)
|
11
|
-
super
|
12
11
|
@value = value
|
13
12
|
@attr = attr
|
14
13
|
end
|
data/lib/ruy/conditions/tz.rb
CHANGED
@@ -6,7 +6,7 @@ module Ruy
|
|
6
6
|
# @note Does not support time zone-aware matchers inside sub-blocks.
|
7
7
|
# A workaround for this is always surrounding your time zone-aware matchers by a 'tz' block even in sub-blocks
|
8
8
|
# already surrounded by one.
|
9
|
-
class TZ <
|
9
|
+
class TZ < CompoundCondition
|
10
10
|
# @param [String] tz_identifier String representing IANA's time zone identifier.
|
11
11
|
def initialize(tz_identifier)
|
12
12
|
super
|
@@ -15,7 +15,7 @@ module Ruy
|
|
15
15
|
|
16
16
|
# @param [Ruy::VariableContext] ctx
|
17
17
|
def call(ctx)
|
18
|
-
|
18
|
+
conditions.all? do |condition|
|
19
19
|
condition.call(ctx)
|
20
20
|
end
|
21
21
|
end
|
@@ -24,7 +24,7 @@ module Ruy
|
|
24
24
|
#
|
25
25
|
# @param (see Ruy::Conditions::DayOfWeek#initialize)
|
26
26
|
def day_of_week(dow, attr)
|
27
|
-
|
27
|
+
conditions << DayOfWeek.new(dow, attr, @tz_identifier)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Intercepts an 'eq' call to the superclass and enhances its arguments
|
data/lib/ruy/dsl.rb
CHANGED
@@ -55,15 +55,15 @@ module Ruy
|
|
55
55
|
# Adds an Except condition.
|
56
56
|
#
|
57
57
|
# @param (see Conditions::Except#initialize)
|
58
|
-
def except(
|
59
|
-
self.conditions << Conditions::Except.new(
|
58
|
+
def except(&block)
|
59
|
+
self.conditions << Conditions::Except.new(&block)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Adds a GreaterThan condition.
|
63
63
|
#
|
64
64
|
# @param (see Conditions::GreaterThanOrEqual#initialize)
|
65
65
|
def greater_than(value, attr)
|
66
|
-
|
66
|
+
self.conditions << Conditions::GreaterThan.new(value, attr)
|
67
67
|
end
|
68
68
|
|
69
69
|
# Adds a GreaterThanOrEqual condition.
|
@@ -84,7 +84,7 @@ module Ruy
|
|
84
84
|
#
|
85
85
|
# @param (see Conditions::InCyclicOrder#initialize)
|
86
86
|
def in_cyclic_order(from, to, attr, &block)
|
87
|
-
|
87
|
+
self.conditions << Conditions::InCyclicOrder.new(from, to, attr, &block)
|
88
88
|
end
|
89
89
|
|
90
90
|
# Adds an Include condition.
|
@@ -107,7 +107,7 @@ module Ruy
|
|
107
107
|
def less_than(value, attr)
|
108
108
|
self.conditions << Conditions::LessThan.new(value, attr)
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
# Adds a TZ condition block
|
112
112
|
#
|
113
113
|
# @param [String] tz_identifier String representing IANA's
|
@@ -132,7 +132,7 @@ module Ruy
|
|
132
132
|
s << ' ' << stringified_params.join(', ')
|
133
133
|
end
|
134
134
|
|
135
|
-
if self.conditions.empty?
|
135
|
+
if !self.respond_to?(:conditions) || self.conditions.empty?
|
136
136
|
s << "\n"
|
137
137
|
else
|
138
138
|
s << " do\n"
|
data/lib/ruy/outcome.rb
CHANGED
@@ -11,7 +11,7 @@ module Ruy
|
|
11
11
|
# @param value The value of this outcome
|
12
12
|
def initialize(value)
|
13
13
|
@value = value
|
14
|
-
@
|
14
|
+
@root_condition = Ruy::Conditions::All.new
|
15
15
|
end
|
16
16
|
|
17
17
|
# Returns the value of this outcome is all of the conditions succeed.
|
@@ -23,14 +23,14 @@ module Ruy
|
|
23
23
|
# @return [Object]
|
24
24
|
# @return [nil] If some of the conditions does not succeeds
|
25
25
|
def call(ctx)
|
26
|
-
if @
|
26
|
+
if @root_condition.call(ctx)
|
27
27
|
@value
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
# @return [Array<Ruy::
|
31
|
+
# @return [Array<Ruy::Condition>]
|
32
32
|
def conditions
|
33
|
-
@
|
33
|
+
@root_condition.conditions
|
34
34
|
end
|
35
35
|
|
36
36
|
# @return [Array] An array containing the value of this outcome
|
data/lib/ruy/rule.rb
CHANGED
@@ -4,11 +4,14 @@ module Ruy
|
|
4
4
|
include Utils::Printable
|
5
5
|
|
6
6
|
attr_reader :conditions
|
7
|
+
attr_reader :outcomes
|
7
8
|
|
8
|
-
def initialize
|
9
|
+
def initialize
|
9
10
|
@attrs = {}
|
10
11
|
@conditions = []
|
11
|
-
@
|
12
|
+
@fallback = nil
|
13
|
+
@lets = []
|
14
|
+
@outcomes = []
|
12
15
|
end
|
13
16
|
|
14
17
|
# Gets attribute's value from the given name.
|
@@ -28,28 +31,65 @@ module Ruy
|
|
28
31
|
@attrs[name] = value
|
29
32
|
end
|
30
33
|
|
31
|
-
# Evaluates
|
34
|
+
# Evaluates rule conditions return the corresponding outcome or fallback depending on whether
|
35
|
+
# the conditions matched or not.
|
32
36
|
#
|
33
|
-
# @
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
37
|
+
# @param [Hash] context
|
38
|
+
#
|
39
|
+
# @return [Object] outcome or fallback
|
40
|
+
def call(context)
|
41
|
+
ctx = Context.new(context, @lets)
|
39
42
|
|
40
|
-
|
43
|
+
if Ruy::Utils::Rules.evaluate_conditions(@conditions, ctx)
|
44
|
+
Ruy::Utils::Rules.compute_outcome(@outcomes, ctx)
|
45
|
+
|
46
|
+
else
|
47
|
+
@fallback
|
48
|
+
end
|
41
49
|
end
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
|
51
|
+
# TODO document
|
52
|
+
def fallback(value)
|
53
|
+
@fallback = value
|
46
54
|
end
|
47
55
|
|
48
|
-
#
|
56
|
+
# Defines a memoized value.
|
49
57
|
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
58
|
+
# The value will be resolved upon the context during evaluation. Let is lazy evaluated, it is
|
59
|
+
# not evaluated until the first condition referencing it is invoked. Once evaluated, its value
|
60
|
+
# is stored, so subsequent invocations during the same evaluation will resolve again its value.
|
61
|
+
def let(name)
|
62
|
+
@lets << name
|
63
|
+
end
|
64
|
+
|
65
|
+
# TODO document
|
66
|
+
def outcome(value, &block)
|
67
|
+
outcome = Outcome.new(value)
|
68
|
+
outcome.instance_exec(&block) if block_given?
|
69
|
+
@outcomes << outcome
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
s = @attrs.map { |name, value| "set #{name.inspect}, #{value.inspect}" }.join("\n")
|
74
|
+
s << "\n\n" if @attrs.any?
|
75
|
+
|
76
|
+
s << @lets.map { |name| "let #{name.inspect}" }.join("\n")
|
77
|
+
s << "\n\n" if @lets.any?
|
78
|
+
|
79
|
+
s << self.conditions.join("\n")
|
80
|
+
|
81
|
+
if @outcomes.any?
|
82
|
+
s << "\n" unless s == ''
|
83
|
+
s << @outcomes.join("\n")
|
84
|
+
end
|
85
|
+
|
86
|
+
if @fallback
|
87
|
+
s << "\n" unless s == ''
|
88
|
+
s << "fallback #{@fallback.inspect}\n"
|
89
|
+
end
|
90
|
+
|
91
|
+
s
|
53
92
|
end
|
93
|
+
|
54
94
|
end
|
55
95
|
end
|
data/lib/ruy/utils.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Utils
|
3
|
+
module Rules
|
4
|
+
|
5
|
+
# Evaluates conditions against the given context
|
6
|
+
#
|
7
|
+
# Returns true when all the conditions match, false otherwise.
|
8
|
+
#
|
9
|
+
# @param [Array<Condition>] conditions
|
10
|
+
# @param [Hash] ctx
|
11
|
+
#
|
12
|
+
# @return [Boolean]
|
13
|
+
def self.evaluate_conditions(conditions, ctx)
|
14
|
+
succeed_conditions = conditions.take_while do |condition|
|
15
|
+
condition.call(ctx)
|
16
|
+
end
|
17
|
+
|
18
|
+
succeed_conditions.length == conditions.length
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the value of the first outcome that matches
|
22
|
+
#
|
23
|
+
# @param [Ruy::Context] ctx
|
24
|
+
#
|
25
|
+
# @return [Object] The value of the first matching outcome
|
26
|
+
# @return [nil] when no outcome matches
|
27
|
+
def self.compute_outcome(outcomes, ctx)
|
28
|
+
outcomes.each do |outcome|
|
29
|
+
result = outcome.call(ctx)
|
30
|
+
unless result.nil?
|
31
|
+
return result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Moove-IT
|
@@ -67,7 +67,9 @@ files:
|
|
67
67
|
- lib/ruy/conditions/any.rb
|
68
68
|
- lib/ruy/conditions/assert.rb
|
69
69
|
- lib/ruy/conditions/between.rb
|
70
|
+
- lib/ruy/conditions/compound_condition.rb
|
70
71
|
- lib/ruy/conditions/cond.rb
|
72
|
+
- lib/ruy/conditions/condition.rb
|
71
73
|
- lib/ruy/conditions/day_of_week.rb
|
72
74
|
- lib/ruy/conditions/eq.rb
|
73
75
|
- lib/ruy/conditions/except.rb
|
@@ -84,11 +86,11 @@ files:
|
|
84
86
|
- lib/ruy/dsl.rb
|
85
87
|
- lib/ruy/outcome.rb
|
86
88
|
- lib/ruy/rule.rb
|
87
|
-
- lib/ruy/rule_set.rb
|
88
89
|
- lib/ruy/time_pattern.rb
|
89
90
|
- lib/ruy/utils.rb
|
90
91
|
- lib/ruy/utils/naming.rb
|
91
92
|
- lib/ruy/utils/printable.rb
|
93
|
+
- lib/ruy/utils/rules.rb
|
92
94
|
homepage: http://moove-it.github.io/ruy/
|
93
95
|
licenses:
|
94
96
|
- MIT
|
data/lib/ruy/rule_set.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
module Ruy
|
2
|
-
class RuleSet
|
3
|
-
include DSL
|
4
|
-
|
5
|
-
attr_reader :lets
|
6
|
-
attr_reader :outcomes
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
super
|
10
|
-
|
11
|
-
@lets = []
|
12
|
-
@outcomes = []
|
13
|
-
|
14
|
-
@fallback = nil
|
15
|
-
|
16
|
-
@rule = Ruy::Rule.new
|
17
|
-
end
|
18
|
-
|
19
|
-
def conditions
|
20
|
-
@rule.conditions
|
21
|
-
end
|
22
|
-
|
23
|
-
def outcome(value, &block)
|
24
|
-
outcome = Outcome.new(value)
|
25
|
-
outcome.instance_exec(&block) if block_given?
|
26
|
-
@outcomes << outcome
|
27
|
-
end
|
28
|
-
|
29
|
-
def fallback(value)
|
30
|
-
@fallback = value
|
31
|
-
end
|
32
|
-
|
33
|
-
# @param [Hash] context
|
34
|
-
def call(context)
|
35
|
-
ctx = Context.new(context, @lets)
|
36
|
-
if @rule.call(ctx)
|
37
|
-
compute_outcome(ctx)
|
38
|
-
else
|
39
|
-
@fallback
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def compute_outcome(ctx)
|
44
|
-
@outcomes.each do |outcome|
|
45
|
-
result = outcome.call(ctx)
|
46
|
-
unless result.nil?
|
47
|
-
return result
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
|
54
|
-
# Defines a memoized value.
|
55
|
-
#
|
56
|
-
# The value will be resolved upon the context during evaluation. Let is lazy evaluated, it is
|
57
|
-
# not evaluated until the first condition referencing it is invoked. Once evaluated, it's value
|
58
|
-
# is stored, so subsequent invocations during the same evaluation will resolve again its value.
|
59
|
-
def let(name)
|
60
|
-
@lets << name
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_s
|
64
|
-
s = lets.map { |name| "let #{name.inspect}" }.join("\n")
|
65
|
-
|
66
|
-
s << "\n\n" unless s == ''
|
67
|
-
|
68
|
-
s << self.conditions.join("\n")
|
69
|
-
|
70
|
-
if @outcomes.any?
|
71
|
-
s << "\n" unless s == ''
|
72
|
-
s << @outcomes.join("\n")
|
73
|
-
end
|
74
|
-
|
75
|
-
if @fallback
|
76
|
-
s << "\n" unless s == ''
|
77
|
-
s << "fallback #{@fallback.inspect}\n"
|
78
|
-
end
|
79
|
-
|
80
|
-
s
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|