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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 062667c3bb0ac6a9796f8bbda6dded7918ec9b11
4
- data.tar.gz: bd51df8ff61cab98c007849cf4a1079dc8f83956
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NGJkMjk0NDkxN2RlYzQ1Mjc2NDI3YjU4YmFjM2Q4NDVjMTdjN2Y0ZA==
5
+ data.tar.gz: !binary |-
6
+ NzM1MTVhYWFiZWU4Mzk1ZjBhODM5N2FiNGY2ZjczMzQyYTMxMTcxMg==
5
7
  SHA512:
6
- metadata.gz: e48d547cdb0e47a50ede40c794d3a90eb012ab27c34e31aba0d696ae3936bcbdd0a0501887ba269833135b61e70b34d684892ff84e1e698918f54c9d9c9c7a2e
7
- data.tar.gz: 78a9ecccf5c5f9f0549f7c53fa2e3515d7d900afe4695c1b52f7e5b2a019c38158e385e2daa088a7c716a396d7c31c6e3b06f70185b9d9a42b0dc756450f921a
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 *A context value must be truish*
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
- - eq *Tests a context value for equality*
78
- - except *Evaluates that a context value is not equal to a specified value*
79
- - greater_than *Tests that context value is greater than something*
80
- - greater_than_or_equal *Tests that context value is greater than or equal to something*
81
- - in *A context value must belong to a specified list of values*
82
- - in_cyclic_order *Expects that a value is included in a cyclic order*
83
- - include *The context attribute must include a specified value*
84
- - less_than_or_equal *Tests that context value is less than or equal to something*
85
- - less_than *Tests that context value is less than something*
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 returns a value resulting from either an
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 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.
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, :week_of_day
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
- ### Time Zone awareness
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 pettern specify it, UTC will be used.
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
- #### Days of week matcher
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
- #### Nested blocks support
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
- 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.
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
- ### Documentation
204
+ ## Documentation
192
205
 
193
206
  [RubyDoc.info](http://www.rubydoc.info/github/moove-it/ruy)
@@ -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'
@@ -1,7 +1,8 @@
1
1
  module Ruy
2
2
  module Conditions
3
3
 
4
- # Expects that all rules will succeed.
4
+ # Expects all sub-conditions to be satisfied
5
+ #
5
6
  class All < CompoundCondition
6
7
  end
7
8
  end
@@ -1,18 +1,19 @@
1
1
  module Ruy
2
2
  module Conditions
3
3
 
4
- # Expects that at least one of the rules will succeed.
4
+ # Expects that at least one of the sub-conditions is satisfied
5
+ #
5
6
  class Any < CompoundCondition
6
- def call(ctx)
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
- # Asserts that an attribute has a truth value.
4
+ # Expects that the context key stores a truthy value
5
+ #
5
6
  class Assert < Condition
6
- attr_reader :attr
7
-
8
- # @param attr Context attribute's name
9
- def initialize(attr)
7
+ # @param key
8
+ # @example evaluate that :key passes
9
+ # Assert.new(:key)
10
+ def initialize(*key)
10
11
  super
11
- @attr = attr
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
- @attr == o.attr
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 belongs to a given range.
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 :attr, :from, :to
7
+ attr_reader :from, :to, :range
9
8
 
10
- # @param from Range lower bound
11
- # @param to Range upper bound
12
- # @param attr Name of the attribute that will be evaluated
13
- #
14
- # @yield a block in the context of the current rule
15
- def initialize(from, to, attr, &block)
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
- def call(ctx)
23
- value = ctx.resolve(@attr)
24
- @from <= value && @to >= value
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
- @attr == o.attr &&
30
- @from == o.from &&
31
- @to == o.to
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
- # Evaluates all conditions.
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
- # @return [true] When all conditions succeeds
17
- # @return [false] Otherwise
18
- def call(ctx)
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
@@ -1,14 +1,17 @@
1
1
  module Ruy
2
2
  module Conditions
3
3
 
4
- # Expects a successful evaluation of a sub-pair of conditions.
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
- def call(ctx)
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 the week.
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
- # @param value
13
- # @param attr
13
+ attr_reader :day, :tz_identifier
14
+
15
+ # @param day
16
+ # @param key
14
17
  # @param tz_identifier
15
- def initialize(value, attr, tz_identifier = 'UTC')
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
- @value = value
18
- @attr = attr
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 call(ctx)
24
- resolved = ctx.resolve(@attr)
25
- cmp = @tz.utc_to_local(resolved.to_time.utc)
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
- if @value.is_a?(Fixnum)
28
- cmp.wday == @value
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?(@value.to_s) && cmp.send("#{@value}?")
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