ruy 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +197 -0
- data/lib/ruy.rb +2 -1
- data/lib/ruy/conditions.rb +3 -1
- data/lib/ruy/conditions/all.rb +2 -2
- data/lib/ruy/conditions/any.rb +2 -2
- data/lib/ruy/conditions/assert.rb +2 -2
- data/lib/ruy/conditions/between.rb +7 -6
- data/lib/ruy/conditions/cond.rb +3 -3
- data/lib/ruy/conditions/day_of_week.rb +6 -8
- data/lib/ruy/conditions/eq.rb +5 -6
- data/lib/ruy/conditions/except.rb +6 -6
- data/lib/ruy/conditions/greater_than.rb +30 -0
- data/lib/ruy/conditions/greater_than_or_equal.rb +5 -5
- data/lib/ruy/conditions/in.rb +31 -0
- data/lib/ruy/conditions/in_cyclic_order.rb +33 -0
- data/lib/ruy/conditions/include.rb +8 -8
- data/lib/ruy/conditions/included.rb +2 -2
- data/lib/ruy/conditions/less_than.rb +5 -5
- data/lib/ruy/conditions/less_than_or_equal.rb +4 -4
- data/lib/ruy/conditions/tz.rb +19 -18
- data/lib/ruy/context.rb +23 -5
- data/lib/ruy/dsl.rb +146 -0
- data/lib/ruy/outcome.rb +28 -3
- data/lib/ruy/rule.rb +11 -130
- data/lib/ruy/rule_set.rb +47 -23
- data/lib/ruy/time_pattern.rb +2 -2
- data/lib/ruy/utils.rb +2 -0
- data/lib/ruy/utils/naming.rb +53 -0
- data/lib/ruy/utils/printable.rb +36 -0
- metadata +32 -9
- data/lib/ruy/variable_context.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3f59c87798c8d6d5aa92ca7639e4605f60c36c6
|
4
|
+
data.tar.gz: 4d51c3dd0c5e0a9d7c4a8175caf38eccb19e7763
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71e65bfa1ace338acdcefafbe9a234e5c415bdc1b8075c330fe4831e8f6cebfbd3f70a435656266c7604338c0180692f39af888d1bf7df6f64088233446b550b
|
7
|
+
data.tar.gz: 8c8bb8c7db01b3254d83f53fd44a41dd9257fd2544929dc513360a27ad5805d38676e4568a9d868b6946efe525c7db4f0d101128463eab98a40cfe22f624f0f2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Moove-iT
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
# ruy
|
2
|
+
|
3
|
+
Ruy is a library for defining a set of conditions and evaluating them against a context.
|
4
|
+
|
5
|
+
``` ruby
|
6
|
+
# discount_day.rb
|
7
|
+
gifter = Ruy::RuleSet.new
|
8
|
+
|
9
|
+
gifter.eq :friday, :day_of_week
|
10
|
+
|
11
|
+
gifter.outcome 8 do
|
12
|
+
greater_than_or_equal 300, :amount
|
13
|
+
end
|
14
|
+
|
15
|
+
gifter.outcome 7 do
|
16
|
+
greater_than_or_equal 100, :amount
|
17
|
+
end
|
18
|
+
|
19
|
+
gifter.outcome 3
|
20
|
+
|
21
|
+
gifter.fallback 0
|
22
|
+
```
|
23
|
+
|
24
|
+
RuleSets are evaluated against a context (the `Hash` being passed to `#call`) and return the first outcome that matches.
|
25
|
+
|
26
|
+
``` ruby
|
27
|
+
gifter.call(day_of_week: :friday, amount: 314)
|
28
|
+
|
29
|
+
# => 8
|
30
|
+
```
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
gifter.call(day_of_week: :friday, amount: 256)
|
34
|
+
|
35
|
+
# => 7
|
36
|
+
```
|
37
|
+
|
38
|
+
If no outcome matches, the default one is returned.
|
39
|
+
``` ruby
|
40
|
+
gifter.call(day_of_week: :friday, amount: 99)
|
41
|
+
|
42
|
+
# => 3
|
43
|
+
```
|
44
|
+
|
45
|
+
If conditions are not met, the fallback value is returned.
|
46
|
+
``` ruby
|
47
|
+
gifter.call(day_of_week: :monday, amount: 124)
|
48
|
+
|
49
|
+
# => 0
|
50
|
+
```
|
51
|
+
|
52
|
+
## Key concepts
|
53
|
+
|
54
|
+
Ruy at its core is about evaluating a set of conditions against a context in order to return a result.
|
55
|
+
|
56
|
+
### Conditions
|
57
|
+
|
58
|
+
A condition evaluates the state of the context.
|
59
|
+
|
60
|
+
Available conditions:
|
61
|
+
|
62
|
+
- all *All of the nested conditions must suffice*
|
63
|
+
- any *At least one of its nested conditions must suffice*
|
64
|
+
- assert *A context value must be truish*
|
65
|
+
- between *Evaluates that a context value must belong to a specified range*
|
66
|
+
- cond *At least one slice of two nested conditions must suffice*
|
67
|
+
- day_of_week *Evaluates that a Date/DateTime/Time weekday is matched*
|
68
|
+
- eq *Tests a context value for equality*
|
69
|
+
- except *Evaluates that a context value is not equal to a specified value*
|
70
|
+
- greater_than *Tests that context value is greater than something*
|
71
|
+
- greater_than_or_equal *Tests that context value is greater than or equal to something*
|
72
|
+
- in *A context value must belong to a specified list of values*
|
73
|
+
- in_cyclic_order *TBD*
|
74
|
+
- include *The context attribute must include a specified value*
|
75
|
+
- less_than_or_equal *Tests that context value is less than or equal to something*
|
76
|
+
- less_than *Tests that context value is less than something*
|
77
|
+
|
78
|
+
Conditions can be nested. In such case, for the nesting condition to be met, the nested conditions must
|
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
|
+
```
|
95
|
+
|
96
|
+
### Rulsets
|
97
|
+
|
98
|
+
A ruleset is a set of conditions that must suffice and returns a value resulting from either an
|
99
|
+
outcome or a fallback.
|
100
|
+
|
101
|
+
### Contexts
|
102
|
+
|
103
|
+
A context is a `Hash` from which values are fetched in order to evaluate a ruleset.
|
104
|
+
|
105
|
+
### Lazy values
|
106
|
+
|
107
|
+
Rulesets 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
|
+
|
109
|
+
|
110
|
+
``` ruby
|
111
|
+
# premium_discount_day.rb
|
112
|
+
gifter = Ruy::RuleSet.new
|
113
|
+
|
114
|
+
gifter.let :amounts_average # an expensive calculation
|
115
|
+
|
116
|
+
gifter.eq :friday, :week_of_day
|
117
|
+
|
118
|
+
gifter.greater_than_or_equal 10_000, :amounts_average
|
119
|
+
|
120
|
+
gifter.outcome true
|
121
|
+
```
|
122
|
+
|
123
|
+
``` ruby
|
124
|
+
gifter.call(day_of_week: :friday, amounts_average: -> { Stats::Amounts.compute_average })
|
125
|
+
```
|
126
|
+
### Outcomes
|
127
|
+
|
128
|
+
An outcome is the result of a successful ruleset evaluation. An outcome can also have nested
|
129
|
+
conditions, in such case, if the conditions meet, the outcome value is returned.
|
130
|
+
|
131
|
+
A RuleSet can have multiple outcomes, the first matching one is returned.
|
132
|
+
|
133
|
+
### Time Zone awareness
|
134
|
+
|
135
|
+
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
|
+
|
137
|
+
```ruby
|
138
|
+
ruleset = Ruy::RuleSet.new
|
139
|
+
|
140
|
+
ruleset.tz 'America/New_York' do
|
141
|
+
eq '2015-01-01T00:00:00', :timestamp
|
142
|
+
end
|
143
|
+
|
144
|
+
ruleset.outcome 'Happy New Year, NYC!'
|
145
|
+
```
|
146
|
+
|
147
|
+
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.
|
148
|
+
|
149
|
+
String time patterns follow the Ruy's well-formed time pattern structure as follows:
|
150
|
+
|
151
|
+
`YYYY-MM-DDTHH:MM:SS[z<IANA Time Zone Database identifier>]`
|
152
|
+
|
153
|
+
Where the time zone identifier is optional, but if you specify it, will take precedence over the block's identifier. In case you don't specify it, Ruy will get the time zone from the `tz` block's argument. If neither the block nor the pettern specify it, UTC will be used.
|
154
|
+
|
155
|
+
#### Days of week matcher
|
156
|
+
|
157
|
+
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
|
+
|
159
|
+
```ruby
|
160
|
+
ruleset = Ruy::RuleSet.new
|
161
|
+
|
162
|
+
ruleset.any do
|
163
|
+
tz 'America/New_York' do
|
164
|
+
day_of_week :saturday, :timestamp
|
165
|
+
end
|
166
|
+
|
167
|
+
tz 'America/New_York' do
|
168
|
+
day_of_week 0, :timestamp # Sunday
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
ruleset.outcome 'Have a nice weekend, NYC!'
|
173
|
+
```
|
174
|
+
|
175
|
+
This matcher supports both the `Symbol` and number syntax in the range `(0..6)` starting on Sunday.
|
176
|
+
|
177
|
+
The day of week matcher will try to parse timestamps using the ISO8601 format unless the context passes a Time object.
|
178
|
+
|
179
|
+
#### Nested blocks support
|
180
|
+
|
181
|
+
You cannot use matchers inside nested blocks in a `tz` block expecting them to work as if they were immediate children of `tz`.
|
182
|
+
|
183
|
+
A possible workaround for this is to use `tz` blocks inside the nested block in question:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
ruleset = Ruy::RuleSet.new
|
187
|
+
any do
|
188
|
+
tz 'America/New_York' { eq '2015-01-01T00:00:00', :timestamp }
|
189
|
+
tz 'America/New_York' { eq '2015-01-01T02:00:00zUTC', :timestamp }
|
190
|
+
end
|
191
|
+
|
192
|
+
ruleset.outcome 'Happy New Year, NYC!'
|
193
|
+
```
|
194
|
+
|
195
|
+
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
|
+
|
197
|
+
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.
|
data/lib/ruy.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
require_relative 'ruy/utils'
|
2
|
+
require_relative 'ruy/dsl'
|
1
3
|
require_relative 'ruy/rule'
|
2
4
|
require_relative 'ruy/rule_set'
|
3
5
|
require_relative 'ruy/conditions'
|
4
6
|
require_relative 'ruy/context'
|
5
|
-
require_relative 'ruy/variable_context'
|
6
7
|
require_relative 'ruy/outcome'
|
7
8
|
require_relative 'ruy/time_pattern'
|
data/lib/ruy/conditions.rb
CHANGED
@@ -2,13 +2,15 @@ require_relative 'conditions/all'
|
|
2
2
|
require_relative 'conditions/any'
|
3
3
|
require_relative 'conditions/assert'
|
4
4
|
require_relative 'conditions/between'
|
5
|
+
require_relative 'conditions/in_cyclic_order'
|
5
6
|
require_relative 'conditions/cond'
|
6
7
|
require_relative 'conditions/day_of_week'
|
7
8
|
require_relative 'conditions/eq'
|
8
9
|
require_relative 'conditions/except'
|
10
|
+
require_relative 'conditions/greater_than'
|
9
11
|
require_relative 'conditions/greater_than_or_equal'
|
12
|
+
require_relative 'conditions/in'
|
10
13
|
require_relative 'conditions/include'
|
11
|
-
require_relative 'conditions/included'
|
12
14
|
require_relative 'conditions/less_than'
|
13
15
|
require_relative 'conditions/less_than_or_equal'
|
14
16
|
require_relative 'conditions/tz'
|
data/lib/ruy/conditions/all.rb
CHANGED
data/lib/ruy/conditions/any.rb
CHANGED
@@ -7,21 +7,22 @@ module Ruy
|
|
7
7
|
class Between < Ruy::Rule
|
8
8
|
attr_reader :attr, :from, :to
|
9
9
|
|
10
|
-
# @param attr Name of the attribute that will be evaluated
|
11
10
|
# @param from Range lower bound
|
12
11
|
# @param to Range upper bound
|
12
|
+
# @param attr Name of the attribute that will be evaluated
|
13
|
+
#
|
13
14
|
# @yield a block in the context of the current rule
|
14
|
-
def initialize(
|
15
|
+
def initialize(from, to, attr, &block)
|
15
16
|
super
|
16
|
-
@attr = attr
|
17
17
|
@from = from
|
18
18
|
@to = to
|
19
|
+
@attr = attr
|
19
20
|
instance_exec(&block) if block_given?
|
20
21
|
end
|
21
22
|
|
22
|
-
def call(
|
23
|
-
value =
|
24
|
-
@from <= value &&
|
23
|
+
def call(ctx)
|
24
|
+
value = ctx.resolve(@attr)
|
25
|
+
@from <= value && @to >= value && super
|
25
26
|
end
|
26
27
|
|
27
28
|
def ==(o)
|
data/lib/ruy/conditions/cond.rb
CHANGED
@@ -8,14 +8,14 @@ module Ruy
|
|
8
8
|
#
|
9
9
|
# Cond is handy for mocking if/else if/else constructs.
|
10
10
|
class Cond < Ruy::Rule
|
11
|
-
def call(
|
11
|
+
def call(ctx)
|
12
12
|
clauses = @conditions.each_slice(2)
|
13
13
|
|
14
14
|
clauses.any? do |rule_1, rule_2|
|
15
|
-
result = rule_1.call(
|
15
|
+
result = rule_1.call(ctx)
|
16
16
|
|
17
17
|
if rule_2
|
18
|
-
result && rule_2.call(
|
18
|
+
result && rule_2.call(ctx)
|
19
19
|
else
|
20
20
|
result
|
21
21
|
end
|
@@ -4,25 +4,24 @@ require 'tzinfo'
|
|
4
4
|
module Ruy
|
5
5
|
module Conditions
|
6
6
|
|
7
|
-
# Expects that a Time object's date corresponds to a specified day of the week
|
7
|
+
# Expects that a Time object's date corresponds to a specified day of the week.
|
8
8
|
class DayOfWeek < Ruy::Rule
|
9
9
|
DAYS_INTO_WEEK = %w(sunday monday tuesday wednesday thursday friday saturday)
|
10
10
|
attr_reader :attr, :value, :tz_identifier
|
11
11
|
|
12
|
-
# @param attr
|
13
12
|
# @param value
|
13
|
+
# @param attr
|
14
14
|
# @param tz_identifier
|
15
|
-
def initialize(
|
15
|
+
def initialize(value, attr, tz_identifier = 'UTC')
|
16
16
|
super
|
17
|
-
@attr = attr
|
18
17
|
@value = value
|
18
|
+
@attr = attr
|
19
19
|
@tz_identifier = tz_identifier
|
20
20
|
@tz = TZInfo::Timezone.get(tz_identifier)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
resolved = var_ctx.resolve(@attr)
|
23
|
+
def call(ctx)
|
24
|
+
resolved = ctx.resolve(@attr)
|
26
25
|
cmp = @tz.utc_to_local(resolved.to_time.utc)
|
27
26
|
|
28
27
|
if @value.is_a?(Fixnum)
|
@@ -32,7 +31,6 @@ module Ruy
|
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
|
-
# @param o
|
36
34
|
def ==(o)
|
37
35
|
o.kind_of?(DayOfWeek) &&
|
38
36
|
attr == o.attr &&
|
data/lib/ruy/conditions/eq.rb
CHANGED
@@ -2,20 +2,19 @@ module Ruy
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
# Expects that a context attribute will be equal to a given value.
|
5
|
-
#
|
6
5
|
class Eq < Ruy::Rule
|
7
6
|
attr_reader :attr, :value
|
8
7
|
|
9
|
-
# @param attr Context attribute's name
|
10
8
|
# @param value Expected value
|
11
|
-
|
9
|
+
# @param attr Context attribute's name
|
10
|
+
def initialize(value, attr)
|
12
11
|
super
|
13
|
-
@attr = attr
|
14
12
|
@value = value
|
13
|
+
@attr = attr
|
15
14
|
end
|
16
15
|
|
17
|
-
def call(
|
18
|
-
|
16
|
+
def call(ctx)
|
17
|
+
@value == ctx.resolve(@attr)
|
19
18
|
end
|
20
19
|
|
21
20
|
def ==(o)
|
@@ -9,25 +9,25 @@ module Ruy
|
|
9
9
|
class Except < Ruy::Rule
|
10
10
|
attr_reader :attr, :value
|
11
11
|
|
12
|
-
# @param attr Context attribute's name
|
13
12
|
# @param value Non-expected value
|
13
|
+
# @param attr Context attribute's name
|
14
14
|
# @yield a block in the context of the current rule
|
15
|
-
def initialize(
|
15
|
+
def initialize(value = nil, attr = nil, &block)
|
16
16
|
super
|
17
|
-
@attr = attr
|
18
17
|
@value = value
|
18
|
+
@attr = attr
|
19
19
|
instance_exec(&block) if block_given?
|
20
20
|
end
|
21
21
|
|
22
|
-
def call(
|
22
|
+
def call(ctx)
|
23
23
|
result = true
|
24
24
|
|
25
25
|
if @attr
|
26
|
-
result &&=
|
26
|
+
result &&= ctx.resolve(@attr) != @value
|
27
27
|
end
|
28
28
|
|
29
29
|
if self.conditions.any?
|
30
|
-
result &&= !super(
|
30
|
+
result &&= !super(ctx)
|
31
31
|
end
|
32
32
|
|
33
33
|
result
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ruy
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
# Expects that a context attribute will be greater than the given value.
|
5
|
+
class GreaterThan < Ruy::Rule
|
6
|
+
attr_reader :attr, :value
|
7
|
+
|
8
|
+
# @param value
|
9
|
+
# @param attr Context attribute's name
|
10
|
+
# @yield a block in the context of the current rule
|
11
|
+
def initialize(value, attr, &block)
|
12
|
+
super
|
13
|
+
@value = value
|
14
|
+
@attr = attr
|
15
|
+
instance_exec(&block) if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(var_ctx)
|
19
|
+
@value < var_ctx.resolve(@attr) && super
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(o)
|
23
|
+
o.kind_of?(GreaterThan) &&
|
24
|
+
attr == o.attr &&
|
25
|
+
value == o.value &&
|
26
|
+
self.conditions == o.conditions
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|