Class: OnCalendar::Condition::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/on_calendar/condition/base.rb

Direct Known Subclasses

DayOfMonth, DayOfWeek, Hour, Minute, Month, Second, Year

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base: nil, step: nil, wildcard: false) ⇒ Base

Returns a new instance of Base.



8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/on_calendar/condition/base.rb', line 8

def initialize(base: nil, step: nil, wildcard: false)
  @base = base
  @step = step
  @wildcard = wildcard

  raise OnCalendar::Condition::Error, "Must supply base or wildcard=true" if
    base.nil? && !wildcard
  raise OnCalendar::Condition::Error, "Condition base value #{base} outside of allowed range #{range}" unless
    valid?
  raise OnCalendar::Condition::Error, "Condition step value #{step} must be > 0 and < than #{range.max}" if
    !step.nil? && (step == 0 || step > range.max)
end

Instance Attribute Details

#baseObject (readonly)

Returns the value of attribute base.



6
7
8
# File 'lib/on_calendar/condition/base.rb', line 6

def base
  @base
end

#stepObject (readonly)

Returns the value of attribute step.



6
7
8
# File 'lib/on_calendar/condition/base.rb', line 6

def step
  @step
end

#wildcardObject (readonly)

Returns the value of attribute wildcard.



6
7
8
# File 'lib/on_calendar/condition/base.rb', line 6

def wildcard
  @wildcard
end

Class Method Details

.range_boundsObject

Provide min / max of range for validation



27
28
29
# File 'lib/on_calendar/condition/base.rb', line 27

def self.range_bounds
  self.const_get(:RANGE).minmax
end

Instance Method Details

#distance_to_next(current, range_args: nil) ⇒ Object

Get next distance to valid base, if we rotate through range we get distance to min Note: We need to pass range_args becaue some subclasses need the context (ie: day_of_month)



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/on_calendar/condition/base.rb', line 71

def distance_to_next(current, range_args: nil)
  # If we have an invalid value no point continuing
  return nil unless valid?(value: current)
  # Wild card return +1
  return 1 if wildcard

  # Build array to find needle_index
  arr = range(clamp: range_args).to_a
  needle_index = arr.index(current)

  return nil if needle_index.nil?

  # Default to increment value by 1
  distance = 1

  if step.nil?
    # If we are dealing with a range and the current and current+1 within range
    if base.is_a?(Range)
      if base.cover?(current) &&
         base.cover?(arr.fetch(needle_index + 1, nil))
        # Set +1 index
        target_index = needle_index + 1
      else
        # Otherwise set the index of the minimum acceptable value
        target_index = arr.index(base.min)
      end
    else
      # Set index of base value
      target_index = arr.index(base)
    end
  # We have a step, we have to compare stepped array to get next distance otherwise min
  else
    # If base is range - we find the next value in base
    if base.is_a?(Range)
      stepped_arr = base.step(step).to_a
      next_value = stepped_arr.bsearch { |x| x > current } || base.min
    # Else we find next value in full RANGE
    else
      stepped_arr = (base..arr.max).step(step).to_a
      next_value = stepped_arr.bsearch { |x| x > current } || arr.min
    end
    target_index = arr.index(next_value)
  end

  # Lets work out distance between target_index and needle_index
  if !target_index.nil? && needle_index < target_index
    # If the needle is before target get how many steps we need to step
    distance = target_index - needle_index
  else
    # If is in front of us loop over until start of array
    # Note: This sounds counter intuitive - why not give the distance until the next value
    #       However this forces us to re-evaluate all other date parts otherwise we might jump forward too far
    distance = arr.length - needle_index
  end
  distance
end

#match?(part) ⇒ Boolean

Match this condition

  • If wild card return true

No step:

- If within range true
- Otherwise if base == argument

With step:

- Expand possible options to range.max
  does our argument match

Returns:

  • (Boolean)


39
40
41
42
43
44
45
46
47
48
49
# File 'lib/on_calendar/condition/base.rb', line 39

def match?(part)
  return true if wildcard

  if step.nil?
    return base.cover?(part) if base.is_a?(Range)

    base == part
  else
    (base.is_a?(Range) ? base : (base..range.max)).step(step).to_a.include?(part)
  end
end

#range(clamp: nil) ⇒ Object

Some subclasses need more context for RANGE



22
23
24
# File 'lib/on_calendar/condition/base.rb', line 22

def range(clamp: nil)
  self.class::RANGE
end

#valid?(value: nil) ⇒ Boolean

Validates whether value (if passed otherwise base) is acceptable Note: This is not context aware so you can pass it day 31 for a 30 day month and it will return true

Returns:

  • (Boolean)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/on_calendar/condition/base.rb', line 53

def valid?(value: nil)
  # Always yes for wildcard when value isn't supplied
  return true if wildcard && value.nil?

  value ||= base
  case value
  when Range
    # Check range is within RANGE
    return true if range.cover?(value)
  else
    # Check value is within RANGE
    return true if range.include?(value)
  end
  false
end