ruy 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/README.md +37 -24
- data/lib/ruy/conditions.rb +3 -0
- data/lib/ruy/conditions/all.rb +2 -1
- data/lib/ruy/conditions/any.rb +7 -6
- data/lib/ruy/conditions/assert.rb +16 -11
- data/lib/ruy/conditions/between.rb +33 -19
- data/lib/ruy/conditions/compound_condition.rb +26 -6
- data/lib/ruy/conditions/cond.rb +11 -6
- data/lib/ruy/conditions/condition.rb +38 -0
- data/lib/ruy/conditions/day_of_week.rb +30 -20
- data/lib/ruy/conditions/dig.rb +42 -0
- data/lib/ruy/conditions/eq.rb +20 -13
- data/lib/ruy/conditions/every.rb +35 -0
- data/lib/ruy/conditions/except.rb +5 -12
- data/lib/ruy/conditions/greater_than.rb +20 -14
- data/lib/ruy/conditions/greater_than_or_equal.rb +21 -14
- data/lib/ruy/conditions/in.rb +19 -14
- data/lib/ruy/conditions/in_cyclic_order.rb +21 -11
- data/lib/ruy/conditions/include.rb +21 -14
- data/lib/ruy/conditions/less_than.rb +19 -13
- data/lib/ruy/conditions/less_than_or_equal.rb +21 -13
- data/lib/ruy/conditions/some.rb +32 -0
- data/lib/ruy/conditions/tz.rb +33 -41
- data/lib/ruy/context.rb +43 -14
- data/lib/ruy/dsl.rb +60 -28
- data/lib/ruy/outcome.rb +11 -11
- data/lib/ruy/rule.rb +26 -20
- data/lib/ruy/time_pattern.rb +6 -23
- data/lib/ruy/utils/naming.rb +3 -3
- data/lib/ruy/utils/rules.rb +3 -3
- metadata +12 -10
- data/lib/ruy/conditions/included.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NGJkMjk0NDkxN2RlYzQ1Mjc2NDI3YjU4YmFjM2Q4NDVjMTdjN2Y0ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NzM1MTVhYWFiZWU4Mzk1ZjBhODM5N2FiNGY2ZjczMzQyYTMxMTcxMg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YmU4YjRiNDAyNGZmYmEwOWYxNDMwMDA2MjI3YjU2OTczNmNjM2E2Y2E4Yjg1
|
10
|
+
NmEwODFkZTc1MWU2MzM1YzQ1ZGI5OTFkNzcyOGM2YWRjODQ0NTZiMzlkZjU0
|
11
|
+
MTg3OGZmZTQ0OTI1MTk1MzNhMWQwYmI3ZTY1NDgyNDdmOTRjOGU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MmVjZmU2MGQ1MmJlMzY3MzZlNDIwY2M0YWMxZTMwYjU4YWU4YTNiYzdlMDZj
|
14
|
+
NWFhMjk5NjBiMWFlNDQ1NjQwZDEwNzY5NWNkNjg2YWQ5OWYxOTEyMGQ2YTgx
|
15
|
+
ZTI2ODE0NWRlNTVlNWU4MjZjODJmNzU5ZjJiYzBjYzI2ODU1YWY=
|
data/README.md
CHANGED
@@ -68,25 +68,28 @@ A condition evaluates the state of the context.
|
|
68
68
|
|
69
69
|
Available conditions:
|
70
70
|
|
71
|
-
- all *All of the nested conditions must suffice*
|
72
|
-
- any *At least one of its nested conditions must suffice*
|
73
|
-
- assert *
|
74
|
-
- between *Evaluates that a context value must belong to a specified range*
|
75
|
-
- cond *At least one slice of two nested conditions must suffice*
|
76
|
-
- day_of_week *Evaluates that a Date/DateTime/Time weekday is matched*
|
77
|
-
-
|
78
|
-
-
|
79
|
-
-
|
80
|
-
-
|
81
|
-
-
|
82
|
-
-
|
83
|
-
-
|
84
|
-
-
|
85
|
-
-
|
71
|
+
- `all` *All of the nested conditions must suffice*
|
72
|
+
- `any` *At least one of its nested conditions must suffice*
|
73
|
+
- `assert` *Tests that a given context value must be a truthy value*
|
74
|
+
- `between` *Evaluates that a given context value must belong to a specified range*
|
75
|
+
- `cond` *At least one slice of two nested conditions must suffice*
|
76
|
+
- `day_of_week` *Evaluates that a Date/DateTime/Time weekday is matched*
|
77
|
+
- `dig` *Digs into a Hash allowing to define conditions over nested attributes*
|
78
|
+
- `eq` *Tests a context value for equality*
|
79
|
+
- `every` *Evaluates that at all the elements of a context enumerable matches the nested conditions*
|
80
|
+
- `except` *Evaluates that any of the nested conditions does not suffice*
|
81
|
+
- `greater_than` *Tests that a given context value is greater than something*
|
82
|
+
- `greater_than_or_equal` *Tests that a given context value is greater than or equal to something*
|
83
|
+
- `in` *Given context value must belong to a specified list of values*
|
84
|
+
- `in_cyclic_order` *Expects that a given context value is included in a cyclic order*
|
85
|
+
- `include` *The context value must include a specified value*
|
86
|
+
- `less_than_or_equal` *Tests that a given context value is less than or equal to something*
|
87
|
+
- `less_than` *Tests that a given context value is less than something*
|
88
|
+
- `some` *Evaluates that at least one element of a context enumerable matches the nested conditions*
|
86
89
|
|
87
90
|
### Rules
|
88
91
|
|
89
|
-
A Rule is a set of conditions that must suffice and
|
92
|
+
A Rule is a set of conditions that must suffice, and return a value resulting from either an
|
90
93
|
outcome or a fallback.
|
91
94
|
|
92
95
|
### Contexts
|
@@ -95,7 +98,8 @@ A context is a `Hash` from which values are fetched in order to evaluate a Rule.
|
|
95
98
|
|
96
99
|
### Lazy values
|
97
100
|
|
98
|
-
Rules can define lazy values. The context must provide a
|
101
|
+
Rules can define lazy values. The context must provide a `Proc` which is evaluted only once the first
|
102
|
+
time the value is needed. The result returned by the proc is memoized and used to evaluate subsequent conditions.
|
99
103
|
|
100
104
|
|
101
105
|
``` ruby
|
@@ -104,7 +108,7 @@ gifter = Ruy::Rule.new
|
|
104
108
|
|
105
109
|
gifter.let :amounts_average # an expensive calculation
|
106
110
|
|
107
|
-
gifter.eq :friday, :
|
111
|
+
gifter.eq :friday, :day_of_week
|
108
112
|
|
109
113
|
gifter.greater_than_or_equal 10_000, :amounts_average
|
110
114
|
|
@@ -121,7 +125,7 @@ conditions, in such case, if the conditions meet, the outcome value is returned.
|
|
121
125
|
|
122
126
|
A Rule can have multiple outcomes, the first matching one is returned.
|
123
127
|
|
124
|
-
|
128
|
+
## Time Zone awareness
|
125
129
|
|
126
130
|
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.
|
127
131
|
|
@@ -141,9 +145,9 @@ String time patterns follow the Ruy's well-formed time pattern structure as foll
|
|
141
145
|
|
142
146
|
`YYYY-MM-DDTHH:MM:SS[z<IANA Time Zone Database identifier>]`
|
143
147
|
|
144
|
-
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
|
148
|
+
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 pattern specify it, UTC will be used.
|
145
149
|
|
146
|
-
|
150
|
+
### Days of week matcher
|
147
151
|
|
148
152
|
Inside any `tz` block, there's a matcher to look for a specific day of the week in the time zone of the block.
|
149
153
|
|
@@ -167,7 +171,7 @@ This matcher supports both the `Symbol` and number syntax in the range `(0..6)`
|
|
167
171
|
|
168
172
|
The day of week matcher will try to parse timestamps using the ISO8601 format unless the context passes a Time object.
|
169
173
|
|
170
|
-
|
174
|
+
### Nested blocks support
|
171
175
|
|
172
176
|
You cannot use matchers inside nested blocks in a `tz` block expecting them to work as if they were immediate children of `tz`.
|
173
177
|
|
@@ -184,10 +188,19 @@ end
|
|
184
188
|
rule.outcome 'Happy New Year, NYC!'
|
185
189
|
```
|
186
190
|
|
187
|
-
|
191
|
+
The following won't do what you expect. Instead, equality will be evaluated interpreting the time as a simple string.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
rule.tz 'America/New_York' do
|
195
|
+
any do
|
196
|
+
eq '2015-01-01T00:00:00', :timestamp
|
197
|
+
eq '2015-01-01T02:00:00zUTC', :timestamp
|
198
|
+
end
|
199
|
+
end
|
200
|
+
```
|
188
201
|
|
189
202
|
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
203
|
|
191
|
-
|
204
|
+
## Documentation
|
192
205
|
|
193
206
|
[RubyDoc.info](http://www.rubydoc.info/github/moove-it/ruy)
|
data/lib/ruy/conditions.rb
CHANGED
@@ -7,7 +7,9 @@ require_relative 'conditions/between'
|
|
7
7
|
require_relative 'conditions/in_cyclic_order'
|
8
8
|
require_relative 'conditions/cond'
|
9
9
|
require_relative 'conditions/day_of_week'
|
10
|
+
require_relative 'conditions/dig'
|
10
11
|
require_relative 'conditions/eq'
|
12
|
+
require_relative 'conditions/every'
|
11
13
|
require_relative 'conditions/except'
|
12
14
|
require_relative 'conditions/greater_than'
|
13
15
|
require_relative 'conditions/greater_than_or_equal'
|
@@ -15,4 +17,5 @@ require_relative 'conditions/in'
|
|
15
17
|
require_relative 'conditions/include'
|
16
18
|
require_relative 'conditions/less_than'
|
17
19
|
require_relative 'conditions/less_than_or_equal'
|
20
|
+
require_relative 'conditions/some'
|
18
21
|
require_relative 'conditions/tz'
|
data/lib/ruy/conditions/all.rb
CHANGED
data/lib/ruy/conditions/any.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
-
# Expects that at least one of the
|
4
|
+
# Expects that at least one of the sub-conditions is satisfied
|
5
|
+
#
|
5
6
|
class Any < CompoundCondition
|
6
|
-
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
# @see CompoundCondition#evaluate
|
11
|
+
def evaluate(ctx)
|
7
12
|
conditions.any? do |condition|
|
8
13
|
condition.call(ctx)
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
12
|
-
def ==(o)
|
13
|
-
o.kind_of?(Any) &&
|
14
|
-
conditions == o.conditions
|
15
|
-
end
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -1,24 +1,29 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
-
#
|
4
|
+
# Expects that the context key stores a truthy value
|
5
|
+
#
|
5
6
|
class Assert < Condition
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
def initialize(
|
7
|
+
# @param key
|
8
|
+
# @example evaluate that :key passes
|
9
|
+
# Assert.new(:key)
|
10
|
+
def initialize(*key)
|
10
11
|
super
|
11
|
-
@
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(ctx)
|
15
|
-
ctx.resolve(@attr)
|
12
|
+
@key = key.first if key.any?
|
16
13
|
end
|
17
14
|
|
18
15
|
def ==(o)
|
19
16
|
o.kind_of?(Assert) &&
|
20
|
-
|
17
|
+
o.key == @key
|
21
18
|
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# @see Condition#evaluate
|
23
|
+
def evaluate(obj)
|
24
|
+
!!obj
|
25
|
+
end
|
26
|
+
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
@@ -1,34 +1,48 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
-
# Expects that a context value
|
4
|
+
# Expects that a context key stores a value included in a defined range
|
5
5
|
#
|
6
|
-
# Comparison formula: from <= context[attr] <= to
|
7
6
|
class Between < Condition
|
8
|
-
attr_reader :
|
7
|
+
attr_reader :from, :to, :range
|
9
8
|
|
10
|
-
# @
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# @
|
15
|
-
|
9
|
+
# @overload initialize(from, to, key)
|
10
|
+
# @param from Range lower bound
|
11
|
+
# @param to Range upper bound
|
12
|
+
# @param key Key that holds the value to be evaluated
|
13
|
+
# @overload initialize(range, key)
|
14
|
+
# @param range Range
|
15
|
+
# @param key Key that holds the value to be evaluated
|
16
|
+
def initialize(*args)
|
16
17
|
super
|
17
|
-
@from = from
|
18
|
-
@to = to
|
19
|
-
@attr = attr
|
20
|
-
end
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
if Range === args[0]
|
20
|
+
@range = args[0]
|
21
|
+
@key = args[1] if args.length > 1
|
22
|
+
else
|
23
|
+
@from, @to = args[0], args[1]
|
24
|
+
@key = args[2] if args.length > 2
|
25
|
+
end
|
25
26
|
end
|
26
27
|
|
27
28
|
def ==(o)
|
28
29
|
o.kind_of?(Between) &&
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
o.from == @from &&
|
31
|
+
o.to == @to &&
|
32
|
+
o.range == @range &&
|
33
|
+
o.key == @key
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# @see Condition#evaluate
|
39
|
+
# @raise NoMethodError when values that do not support #<=> are passed
|
40
|
+
def evaluate(value)
|
41
|
+
if @range
|
42
|
+
@range.include?(value)
|
43
|
+
else
|
44
|
+
@from <= value && @to >= value
|
45
|
+
end
|
32
46
|
end
|
33
47
|
end
|
34
48
|
end
|
@@ -1,25 +1,45 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
+
# Abstract base class for compound conditions
|
5
|
+
#
|
6
|
+
# @abstract
|
4
7
|
class CompoundCondition < Condition
|
5
|
-
|
6
8
|
attr_reader :conditions
|
7
9
|
|
8
10
|
def initialize(*params)
|
9
11
|
super
|
10
|
-
|
11
12
|
@conditions = []
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
+
def ==(o)
|
16
|
+
o.kind_of?(self.class) &&
|
17
|
+
o.conditions == @conditions
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# Evaluates a context to check if it satisfies the set of sub-conditions
|
15
23
|
#
|
16
|
-
# @
|
17
|
-
# @
|
18
|
-
|
24
|
+
# @abstract
|
25
|
+
# @param ctx [Context]
|
26
|
+
# @return [Boolean] true if satisfies the sub-conditions, false otherwise
|
27
|
+
def evaluate(ctx)
|
19
28
|
@conditions.all? do |condition|
|
20
29
|
condition.call(ctx)
|
21
30
|
end
|
22
31
|
end
|
32
|
+
|
33
|
+
# Lookups the object of evaluation in the context
|
34
|
+
# based on the initialization attribute
|
35
|
+
#
|
36
|
+
# @param ctx [Context]
|
37
|
+
# @return an object if a key has been defined,
|
38
|
+
# the passed context otherwise
|
39
|
+
def resolve(ctx)
|
40
|
+
has_key? ? ctx.resolve(key) : ctx
|
41
|
+
end
|
42
|
+
|
23
43
|
end
|
24
44
|
end
|
25
45
|
end
|
data/lib/ruy/conditions/cond.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
-
#
|
4
|
+
# Evaluates any given number of sub-conditions in subsequent pairs
|
5
|
+
# until one pair evaluates succesfully.
|
6
|
+
# If an uneven number of conditions is given, it will evaluate in pairs
|
7
|
+
# except for the last condition which will be evaluated alone.
|
5
8
|
#
|
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
|
-
#
|
9
|
-
# Cond is handy for mocking if/else if/else constructs.
|
10
9
|
class Cond < CompoundCondition
|
11
|
-
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# @see CompoundCondition#evaluate
|
14
|
+
def evaluate(ctx)
|
12
15
|
clauses = conditions.each_slice(2)
|
13
16
|
|
14
17
|
clauses.any? do |rule_1, rule_2|
|
@@ -21,6 +24,8 @@ module Ruy
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
27
|
+
|
24
28
|
end
|
29
|
+
|
25
30
|
end
|
26
31
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Ruy
|
2
2
|
module Conditions
|
3
3
|
|
4
|
+
# Abstract base class for simple conditions
|
5
|
+
#
|
6
|
+
# @abstract
|
4
7
|
class Condition
|
5
8
|
include Ruy::DSL
|
6
9
|
|
@@ -10,6 +13,41 @@ module Ruy
|
|
10
13
|
@params = params
|
11
14
|
end
|
12
15
|
|
16
|
+
# Evaluates a context's object for its compliance with the condition.
|
17
|
+
#
|
18
|
+
# @param ctx [Context]
|
19
|
+
# @return [Boolean]
|
20
|
+
def call(ctx)
|
21
|
+
evaluate(resolve(ctx))
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :key
|
27
|
+
|
28
|
+
# Returns true if a key has been defined among the condition attributes
|
29
|
+
#
|
30
|
+
# @return [Boolean]
|
31
|
+
def has_key?
|
32
|
+
defined? @key
|
33
|
+
end
|
34
|
+
|
35
|
+
# Evaluates an object to check if it satisfies the condition
|
36
|
+
#
|
37
|
+
# @abstract
|
38
|
+
# @param obj
|
39
|
+
# @return [Boolean] true if it satisfies the condition, false otherwise
|
40
|
+
def evaluate(obj); end
|
41
|
+
|
42
|
+
# Lookups the object of evaluation in the context
|
43
|
+
# based on the initialization attribute
|
44
|
+
#
|
45
|
+
# @param ctx [Context]
|
46
|
+
# @return an object stored in the context
|
47
|
+
def resolve(ctx)
|
48
|
+
has_key? ? ctx.resolve(key) : ctx.object
|
49
|
+
end
|
50
|
+
|
13
51
|
end
|
14
52
|
end
|
15
53
|
end
|
@@ -4,39 +4,49 @@ 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
|
7
|
+
# Expects that a Time object's date corresponds to a specified day of
|
8
|
+
# the week
|
9
|
+
#
|
8
10
|
class DayOfWeek < Condition
|
9
11
|
DAYS_INTO_WEEK = %w(sunday monday tuesday wednesday thursday friday saturday)
|
10
|
-
attr_reader :attr, :value, :tz_identifier
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
attr_reader :day, :tz_identifier
|
14
|
+
|
15
|
+
# @param day
|
16
|
+
# @param key
|
14
17
|
# @param tz_identifier
|
15
|
-
|
18
|
+
# @example to check that the day in 'time' is wednesday
|
19
|
+
# DayOfWeek.new(:wednesday, :time)
|
20
|
+
# @example to check that the day in 'time' is wednesday in CST
|
21
|
+
# DayOfWeek.new(:wednesday, :time, 'CST')
|
22
|
+
def initialize(day, key, tz_identifier = 'UTC')
|
16
23
|
super
|
17
|
-
@
|
18
|
-
@
|
24
|
+
@day = day
|
25
|
+
@key = key
|
19
26
|
@tz_identifier = tz_identifier
|
20
|
-
@tz = TZInfo::Timezone.get(tz_identifier)
|
21
27
|
end
|
22
28
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
29
|
+
def ==(o)
|
30
|
+
o.kind_of?(DayOfWeek) &&
|
31
|
+
o.day == @day &&
|
32
|
+
o.key == @key &&
|
33
|
+
o.tz_identifier == @tz_identifier
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
26
37
|
|
27
|
-
|
28
|
-
|
38
|
+
# @see Condition#evaluate
|
39
|
+
def evaluate(date)
|
40
|
+
# Get the TZInfo::Timezone object here so it can be garbage-collected later
|
41
|
+
cmp = TZInfo::Timezone.get(@tz_identifier).utc_to_local(date.to_time.utc)
|
42
|
+
|
43
|
+
if @day.is_a?(Fixnum)
|
44
|
+
cmp.wday == @day
|
29
45
|
else
|
30
|
-
DAYS_INTO_WEEK.include?(@
|
46
|
+
DAYS_INTO_WEEK.include?(@day.to_s) && cmp.send("#{@day}?")
|
31
47
|
end
|
32
48
|
end
|
33
49
|
|
34
|
-
def ==(o)
|
35
|
-
o.kind_of?(DayOfWeek) &&
|
36
|
-
attr == o.attr &&
|
37
|
-
value == o.value &&
|
38
|
-
tz_identifier == o.tz_identifier
|
39
|
-
end
|
40
50
|
end
|
41
51
|
end
|
42
52
|
end
|