ruy 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,27 +1,35 @@
1
1
  module Ruy
2
2
  module Conditions
3
3
 
4
- # Expects that a context attribute will be less than or equal to the given value.
4
+ # Expects that a context attribute will be less than or equal
5
+ # to the given value
6
+ #
5
7
  class LessThanOrEqual < Condition
6
- attr_reader :attr, :value
8
+ attr_reader :obj
7
9
 
8
- # @param attr Context attribute's name
9
- # @param value
10
- def initialize(value, attr)
10
+ # @param obj
11
+ # @param key
12
+ # @example check that :key is less than or equal to 5
13
+ # LessThanOrEqual.new(5, :key)
14
+ def initialize(obj, *key)
11
15
  super
12
- @value = value
13
- @attr = attr
14
- end
15
-
16
- def call(ctx)
17
- @value >= ctx.resolve(@attr)
16
+ @obj = obj
17
+ @key = key.first if key.any?
18
18
  end
19
19
 
20
20
  def ==(o)
21
21
  o.kind_of?(LessThanOrEqual) &&
22
- attr == o.attr &&
23
- value == o.value
22
+ o.obj == @obj &&
23
+ o.key == @key
24
+ end
25
+
26
+ protected
27
+
28
+ # @see Condition#evaluate
29
+ def evaluate(value)
30
+ @obj >= value
24
31
  end
32
+
25
33
  end
26
34
  end
27
35
  end
@@ -0,0 +1,32 @@
1
+ module Ruy
2
+ module Conditions
3
+
4
+ # Iterates over an Enumerable evaluating that some value
5
+ # matches the set of sub-conditions
6
+ #
7
+ class Some < CompoundCondition
8
+
9
+ # @param key
10
+ # @example check that at least a value from :key matches the sub-conditions
11
+ # Some.new(:key)
12
+ def initialize(*key)
13
+ super
14
+ @key = key.first if key.any?
15
+ end
16
+
17
+ # @see CompoundCondition#evaluate
18
+ def evaluate(enum)
19
+ enum.any? do |ctx|
20
+ new_ctx = Ruy::Context.new(ctx)
21
+
22
+ Ruy::Utils::Rules.evaluate_conditions(conditions, new_ctx)
23
+ end
24
+ end
25
+
26
+ def ==(o)
27
+ super &&
28
+ o.key == @key
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,67 +1,59 @@
1
1
  module Ruy
2
2
  module Conditions
3
3
 
4
- # Evaluates a block using comparison matchers that receive time-related objects with time zone awareness.
4
+ # Evaluates a block using comparison matchers
5
+ # that receive time-related objects with time zone awareness
5
6
  #
6
7
  # @note Does not support time zone-aware matchers inside sub-blocks.
7
- # A workaround for this is always surrounding your time zone-aware matchers by a 'tz' block even in sub-blocks
8
- # already surrounded by one.
8
+ # A workaround for this is always surrounding your
9
+ # time zone-aware matchers by a 'tz' block even in sub-blocks
10
+ # already surrounded by one.
9
11
  class TZ < CompoundCondition
10
- # @param [String] tz_identifier String representing IANA's time zone identifier.
12
+
13
+ # @param tz_identifier [String]
14
+ # String representing IANA's time zone identifier.
15
+ # @example evaluate all the sub-conditions under CST
16
+ # TZ.new('CST')
11
17
  def initialize(tz_identifier)
12
18
  super
13
19
  @tz_identifier = tz_identifier
14
20
  end
15
21
 
16
- # @param [Ruy::VariableContext] ctx
17
- def call(ctx)
18
- conditions.all? do |condition|
19
- condition.call(ctx)
20
- end
22
+ # @see Ruy::Conditions::DayOfWeek#initialize
23
+ def day_of_week(dow, key)
24
+ conditions << DayOfWeek.new(dow, key, @tz_identifier)
21
25
  end
22
26
 
23
- # Adds a DayOfWeek condition
24
- #
25
- # @param (see Ruy::Conditions::DayOfWeek#initialize)
26
- def day_of_week(dow, attr)
27
- conditions << DayOfWeek.new(dow, attr, @tz_identifier)
27
+ # @see Ruy::Conditions::Eq#initialize
28
+ def eq(pattern, *key)
29
+ super(TimePattern.new(pattern, @tz_identifier), *key)
28
30
  end
29
31
 
30
- # Intercepts an 'eq' call to the superclass and enhances its arguments
31
- #
32
- # @param (see Ruy::Conditions::Eq#initialize)
33
- def eq(pattern, attr, &block)
34
- super(TimePattern.new(pattern, @tz_identifier), attr, &block)
35
- end
36
32
 
37
- # TODO Add Greater condition call here
33
+ # @see Ruy::Conditions::GreaterThanOrEqual#initialize
34
+ def greater_than(pattern, *key)
35
+ super(TimePattern.new(pattern, @tz_identifier), *key)
36
+ end
38
37
 
39
- # Intercepts a 'greater_than_or_equal' call to the superclass and enhances its arguments
40
- #
41
- # @param (see Ruy::Conditions::GreaterThanOrEqual#initialize)
42
- def greater_than_or_equal(pattern, attr, &block)
43
- super(TimePattern.new(pattern, @tz_identifier), attr, &block)
38
+ # @see Ruy::Conditions::GreaterThanOrEqual#initialize
39
+ def greater_than_or_equal(pattern, *key)
40
+ super(TimePattern.new(pattern, @tz_identifier), *key)
44
41
  end
45
42
 
46
- # Intercepts a 'less_than' call to the superclass and enhances its arguments
47
- #
48
- # @param (see Ruy::Conditions::LessThan#initialize)
49
- def less_than(pattern, attr, &block)
50
- super(TimePattern.new(pattern, @tz_identifier), attr, &block)
43
+ # @see Ruy::Conditions::LessThan#initialize
44
+ def less_than(pattern, *key)
45
+ super(TimePattern.new(pattern, @tz_identifier), *key)
51
46
  end
52
47
 
53
- # Intercepts a 'less_than_or_equal' call to the superclass and enhances its arguments
54
- #
55
- # @param (see Ruy::Conditions::LessThanOrEqual#initialize)
56
- def less_than_or_equal(pattern, attr, &block)
57
- super(TimePattern.new(pattern, @tz_identifier), attr, &block)
48
+ # @see Ruy::Conditions::LessThanOrEqual#initialize
49
+ def less_than_or_equal(pattern, *key)
50
+ super(TimePattern.new(pattern, @tz_identifier), *key)
58
51
  end
59
52
 
60
- # Intercepts a 'between' call to the superclass and enhances its arguments
61
- #
62
- # @param (see Ruy::Conditions::Between#initialize)
63
- def between(from, to, attr, &block)
64
- super(TimePattern.new(from, @tz_identifier), TimePattern.new(to, @tz_identifier), attr, &block)
53
+ # @see Ruy::Conditions::Between#initialize)
54
+ def between(from, to, *key)
55
+ super(TimePattern.new(from, @tz_identifier),
56
+ TimePattern.new(to, @tz_identifier), *key)
65
57
  end
66
58
 
67
59
  end
data/lib/ruy/context.rb CHANGED
@@ -1,28 +1,57 @@
1
1
  module Ruy
2
- # Context that resolves lets and values access.
2
+ class UnexpectedObjectError < StandardError; end
3
+
4
+ # Wraps an evaluation object and stores lazy variables.
5
+ # It supports hierarchical structures that can be navigated
6
+ # using the #[] method.
7
+ #
3
8
  class Context
4
9
 
5
- # @param [Hash] ctx
6
- # @param [Array] lets Names of the 'let' variables
7
- def initialize(ctx, lets = [])
8
- @ctx = ctx
9
- @lets = lets
10
+ attr_reader :obj
11
+ alias_method :object, :obj
10
12
 
13
+ # @param obj
14
+ # @param lets [Array<Symbol>]
15
+ # @example a Hash context
16
+ # Context.new({key: 5})
17
+ # @example a Hash context and expected lazy variables
18
+ # Context.new({key: 5}, [:lazy_a, :lazy_b])
19
+ # @example a Fixnum context
20
+ # Context.new(5)
21
+ def initialize(obj, lets = [])
22
+ @obj = obj
23
+ @lets = lets
11
24
  @resolved_lets = {}
12
25
  end
13
26
 
14
- # Resolve the given attr from the lets or the context.
27
+ # Returns true if the given key can be resolved in the current context
28
+ #
29
+ # @param key
30
+ #
31
+ # @return [Boolean]
32
+ def include?(key)
33
+ @obj.kind_of?(Hash) && @obj.include?(key)
34
+ end
35
+
36
+ # Resolves an attribute key to its corresponding value
15
37
  #
16
- # @param [Symbol] attr
38
+ # @param key
17
39
  #
18
- # @return [Object]
19
- # @return [nil] when attribute cannot be resolved
20
- def resolve(attr)
21
- if @lets.include?(attr)
22
- @resolved_lets[attr] ||= @ctx.instance_exec(&@ctx[attr])
40
+ # @return the value
41
+ # @return [nil] if key cannot be resolved
42
+ # @raise UnexpectedObjectError if object in context is not a Hash
43
+ def resolve(key)
44
+ if !@obj.kind_of?(Hash)
45
+ raise UnexpectedObjectError, 'Object in context is not a Hash instance'
46
+ end
47
+
48
+ # Lazy variables have precedence over context attributes
49
+ if @lets.include?(key)
50
+ @resolved_lets[key] ||= @obj.instance_exec(&@obj[key])
23
51
  else
24
- @ctx.fetch(attr) { |key| @ctx[key.to_s] }
52
+ @obj[key]
25
53
  end
26
54
  end
55
+
27
56
  end
28
57
  end
data/lib/ruy/dsl.rb CHANGED
@@ -24,29 +24,51 @@ module Ruy
24
24
  # Adds an Assert condition.
25
25
  #
26
26
  # @param (see Conditions::Assert#initialize)
27
- def assert(attr)
28
- self.conditions << Conditions::Assert.new(attr)
27
+ def assert(*attrs)
28
+ self.conditions << Conditions::Assert.new(*attrs)
29
29
  end
30
30
 
31
31
  # Adds a Between condition.
32
32
  #
33
33
  # @param (see Conditions::Between#initialize)
34
- def between(from, to, attr, &block)
35
- self.conditions << Conditions::Between.new(from, to, attr, &block)
34
+ def between(from, to, *attrs)
35
+ self.conditions << Conditions::Between.new(from, to, *attrs)
36
+ end
37
+
38
+ # Adds a Cond condition.
39
+ #
40
+ # @yield Evaluates the given block in the context of the new rule
41
+ def cond(&block)
42
+ cond = Conditions::Cond.new
43
+ cond.instance_exec(&block)
44
+
45
+ self.conditions << cond
46
+ end
47
+
48
+ # Adds a Dig condition.
49
+ #
50
+ # @param (see Conditions::Dig#initialize)
51
+ #
52
+ # @yield Evaluates the given block in the context of the new rule
53
+ def dig(*attrs, &block)
54
+ condition = Conditions::Dig.new(*attrs)
55
+ condition.instance_exec(&block)
56
+
57
+ self.conditions << condition
36
58
  end
37
59
 
38
60
  # Adds an Eq condition.
39
61
  #
40
62
  # @param (see Conditions::Eq#initialize)
41
- def eq(value, attr, &block)
42
- self.conditions << Conditions::Eq.new(value, attr, &block)
63
+ def eq(value, *attrs)
64
+ self.conditions << Conditions::Eq.new(value, *attrs)
43
65
  end
44
66
 
45
- # Adds a Cond condition.
67
+ # Adds an Every condition.
46
68
  #
47
- # @yield Evaluates the given block in the context of the new rule
48
- def cond(&block)
49
- cond = Conditions::Cond.new
69
+ # @param (see Conditions::Every#initialize)
70
+ def every(*attrs, &block)
71
+ cond = Conditions::Every.new(*attrs)
50
72
  cond.instance_exec(&block)
51
73
 
52
74
  self.conditions << cond
@@ -62,55 +84,65 @@ module Ruy
62
84
  # Adds a GreaterThan condition.
63
85
  #
64
86
  # @param (see Conditions::GreaterThanOrEqual#initialize)
65
- def greater_than(value, attr)
66
- self.conditions << Conditions::GreaterThan.new(value, attr)
87
+ def greater_than(value, *attrs)
88
+ self.conditions << Conditions::GreaterThan.new(value, *attrs)
67
89
  end
68
90
 
69
91
  # Adds a GreaterThanOrEqual condition.
70
92
  #
71
93
  # @param (see Conditions::GreaterThanOrEqual#initialize)
72
- def greater_than_or_equal(value, attr)
73
- self.conditions << Conditions::GreaterThanOrEqual.new(value, attr)
94
+ def greater_than_or_equal(value, *attrs)
95
+ self.conditions << Conditions::GreaterThanOrEqual.new(value, *attrs)
74
96
  end
75
97
 
76
98
  # Adds an In condition.
77
99
  #
78
100
  # @param (see Conditions::Include#initialize)
79
- def in(values, attr, &block)
80
- self.conditions << Conditions::In.new(values, attr, &block)
101
+ def in(values, *attrs)
102
+ self.conditions << Conditions::In.new(values, *attrs)
81
103
  end
82
104
 
83
105
  # Adds a InCyclicOrder condition.
84
106
  #
85
107
  # @param (see Conditions::InCyclicOrder#initialize)
86
- def in_cyclic_order(from, to, attr, &block)
87
- self.conditions << Conditions::InCyclicOrder.new(from, to, attr, &block)
108
+ def in_cyclic_order(from, to, *attrs)
109
+ self.conditions << Conditions::InCyclicOrder.new(from, to, *attrs)
88
110
  end
89
111
 
90
112
  # Adds an Include condition.
91
113
  #
92
114
  # @param (see Conditions::Included#initialize)
93
- def include(value, attr, &block)
94
- self.conditions << Conditions::Include.new(value, attr, &block)
115
+ def include(value, *attrs)
116
+ self.conditions << Conditions::Include.new(value, *attrs)
117
+ end
118
+
119
+ # Adds a LessThan condition.
120
+ #
121
+ # @param (see Conditions::LessThan#initialize)
122
+ def less_than(value, *attrs)
123
+ self.conditions << Conditions::LessThan.new(value, *attrs)
95
124
  end
96
125
 
97
126
  # Adds a LessOrEqualThan condition.
98
127
  #
99
128
  # @param (see Conditions::LessOrEqualThan#initialize)
100
- def less_than_or_equal(value, attr, &block)
101
- self.conditions << Conditions::LessThanOrEqual.new(value, attr)
129
+ def less_than_or_equal(value, *attrs)
130
+ self.conditions << Conditions::LessThanOrEqual.new(value, *attrs)
102
131
  end
103
132
 
104
- # Adds a LessThan condition.
133
+ # Adds a Some condition.
105
134
  #
106
- # @param (see Conditions::LessThan#initialize)
107
- def less_than(value, attr)
108
- self.conditions << Conditions::LessThan.new(value, attr)
135
+ # @param (see Conditions::Some#initialize)
136
+ def some(*attrs, &block)
137
+ cond = Conditions::Some.new(*attrs)
138
+ cond.instance_exec(&block)
139
+
140
+ self.conditions << cond
109
141
  end
110
142
 
111
143
  # Adds a TZ condition block
112
144
  #
113
- # @param [String] tz_identifier String representing IANA's
145
+ # @param tz_identifier [String] String representing IANA's
114
146
  # time zone identifier. Defaults to UTC if none passed.
115
147
  # @yield Evaluates the given block in the context of the TZ rule
116
148
  def tz(tz_identifier = 'UTC', &block)
@@ -120,7 +152,7 @@ module Ruy
120
152
  self.conditions << cond
121
153
  end
122
154
 
123
- # @param [Integer] indentation Indentation level
155
+ # @param indentation [Integer] Indentation level
124
156
  def to_s(indentation = 0)
125
157
  rule_name = Ruy::Utils::Naming.rule_name(self)
126
158
 
data/lib/ruy/outcome.rb CHANGED
@@ -1,31 +1,31 @@
1
1
  module Ruy
2
2
  # Represents a possible result (outcome) of evaluating a rule set
3
+ # Additionally, an outcome can be composed of rules. In that case,
4
+ # the outcome will evaluate to its value
5
+ # if all of them are evaluated successfully.
3
6
  #
4
- # Additionally, an outcome can be composed of rules. In that case, the outcome will evaluate to
5
- # its value if all of them are evaluated successfully.
6
7
  class Outcome
7
8
  include DSL
8
9
 
9
10
  attr_reader :value
10
11
 
11
- # @param value The value of this outcome
12
+ # @param value
13
+ # @example will return true if evaluation passes
14
+ # Outcome.new(true)
12
15
  def initialize(value)
13
16
  @value = value
14
17
  @root_condition = Ruy::Conditions::All.new
15
18
  end
16
19
 
17
- # Returns the value of this outcome is all of the conditions succeed.
18
- #
20
+ # Returns the value of this outcome if all of the conditions succeed.
19
21
  # It also returns the value when there's no conditions.
20
22
  #
21
- # @param [Ruy::Context] ctx
23
+ # @param ctx [Ruy::Context]
22
24
  #
23
- # @return [Object]
24
- # @return [nil] If some of the conditions does not succeeds
25
+ # @return the value
26
+ # @return [nil] if conditions are not met
25
27
  def call(ctx)
26
- if @root_condition.call(ctx)
27
- @value
28
- end
28
+ @value if @root_condition.call(ctx)
29
29
  end
30
30
 
31
31
  # @return [Array<Ruy::Condition>]