Class: SOF::Cycle

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/sof/cycle.rb,
lib/sof/cycle/version.rb

Defined Under Namespace

Classes: InvalidInput, InvalidKind, InvalidPeriod

Constant Summary collapse

VERSION =
"0.1.6"

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(notation, parser: Parser.new(notation)) ⇒ Cycle

Returns a new instance of Cycle.

Raises:



132
133
134
135
136
137
138
139
140
# File 'lib/sof/cycle.rb', line 132

def initialize(notation, parser: Parser.new(notation))
  @notation = notation
  @parser = parser
  validate_period

  return if @parser.valid?

  raise InvalidInput, "'#{notation}' is not a valid input"
end

Class Attribute Details

.kindObject (readonly)

Returns the value of attribute kind.



115
116
117
# File 'lib/sof/cycle.rb', line 115

def kind
  @kind
end

.notation_idObject (readonly)

Returns the value of attribute notation_id.



115
116
117
# File 'lib/sof/cycle.rb', line 115

def notation_id
  @notation_id
end

.valid_periodsObject (readonly)

Returns the value of attribute valid_periods.



115
116
117
# File 'lib/sof/cycle.rb', line 115

def valid_periods
  @valid_periods
end

Instance Attribute Details

#parserObject (readonly)

Returns the value of attribute parser.



142
143
144
# File 'lib/sof/cycle.rb', line 142

def parser
  @parser
end

Class Method Details

.class_for_kind(sym) ⇒ Object

Return the class handling the kind

Examples:

class_for_kind(:lookback)

Parameters:

  • sym (Symbol)

    symbol matching the kind of Cycle class



96
97
98
99
100
# File 'lib/sof/cycle.rb', line 96

def class_for_kind(sym)
  Cycle.cycle_handlers.find do |klass|
    klass.handles?(sym)
  end || raise(InvalidKind, "':#{sym}' is not a valid kind of Cycle")
end

.class_for_notation_id(notation_id) ⇒ Object

Return the appropriate class for the give notation id

Examples:

class_for_notation_id('L')

Parameters:

  • notation (String)

    notation id matching the kind of Cycle class



85
86
87
88
89
# File 'lib/sof/cycle.rb', line 85

def class_for_notation_id(notation_id)
  cycle_handlers.find do |klass|
    klass.notation_id == notation_id
  end || raise(InvalidKind, "'#{notation_id}' is not a valid kind of #{name}")
end

.cycle_handlersObject



102
# File 'lib/sof/cycle.rb', line 102

def cycle_handlers = @cycle_handlers ||= Set.new

.dump(cycle_or_string) ⇒ Object

Turn a cycle or notation string into a hash



16
17
18
19
20
21
22
# File 'lib/sof/cycle.rb', line 16

def dump(cycle_or_string)
  if cycle_or_string.is_a? Cycle
    cycle_or_string
  else
    Cycle.for(cycle_or_string)
  end.to_h
end

.for(notation) ⇒ Cycle

Return a Cycle object from a notation string

Examples:

Cycle.for('V2C1Y)

Parameters:

  • notation (String)

    a string notation representing a Cycle

Returns:

  • (Cycle)

    a Cycle object representing the provide string notation



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/sof/cycle.rb', line 63

def for(notation)
  return notation if notation.is_a? Cycle
  return notation if notation.is_a? Cycles::Dormant
  parser = Parser.new(notation)
  unless parser.valid?
    raise InvalidInput, "'#{notation}' is not a valid input"
  end

  cycle = cycle_handlers.find do |klass|
    parser.parses?(klass.notation_id)
  end.new(notation, parser:)
  return cycle if parser.active?

  Cycles::Dormant.new(cycle, parser:)
end

.handles?(sym) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/sof/cycle.rb', line 106

def handles?(sym)
  sym && kind == sym.to_sym
end

.inherited(klass) ⇒ Object



104
# File 'lib/sof/cycle.rb', line 104

def inherited(klass) = cycle_handlers << klass

.load(hash) ⇒ Object

Return a Cycle object from a hash



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/sof/cycle.rb', line 25

def load(hash)
  symbolized_hash = hash.symbolize_keys
  cycle_class = class_for_kind(symbolized_hash[:kind])

  unless cycle_class.valid_periods.empty?
    cycle_class.validate_period(
      TimeSpan.notation_id_from_name(symbolized_hash[:period])
    )
  end

  Cycle.for notation(symbolized_hash)
rescue TimeSpan::InvalidPeriod => exc
  raise InvalidPeriod, exc.message
end

.notation(hash) ⇒ String

Retun a notation string from a hash

Parameters:

  • hash (Hash)

    hash of data for a valid Cycle

Returns:

  • (String)

    string representation of a Cycle



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/sof/cycle.rb', line 44

def notation(hash)
  volume_notation = "V#{hash.fetch(:volume) { 1 }}"
  return volume_notation if hash[:kind].nil? || hash[:kind].to_sym == :volume_only

  cycle_class = class_for_kind(hash[:kind].to_sym)
  [
    volume_notation,
    cycle_class.notation_id,
    TimeSpan.notation(hash.slice(:period, :period_count)),
    hash.fetch(:from, nil)
  ].compact.join
end

.recurring?Boolean

Returns:

  • (Boolean)


118
# File 'lib/sof/cycle.rb', line 118

def recurring? = raise "#{name} must implement #{__method__}"

.validate_period(period) ⇒ Object

Raises an error if the given period isn’t in the list of valid periods.

Parameters:

  • period (String)

    period matching the class valid periods

Raises:



124
125
126
127
128
129
# File 'lib/sof/cycle.rb', line 124

def validate_period(period)
  raise InvalidPeriod, <<~ERR.squish unless valid_periods.include?(period)
    Invalid period value of '#{period}' provided. Valid periods are:
    #{valid_periods.join(", ")}
  ERR
end

.volume_only?Boolean

Returns:

  • (Boolean)


116
# File 'lib/sof/cycle.rb', line 116

def volume_only? = @volume_only

Instance Method Details

#==(other) ⇒ Object

Cycles are considered equal if their hash representations are equal



163
# File 'lib/sof/cycle.rb', line 163

def ==(other) = to_h == other.to_h

#as_jsonObject



215
# File 'lib/sof/cycle.rb', line 215

def as_json(...) = notation

#cover?(date, anchor: Date.current) ⇒ Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/sof/cycle.rb', line 184

def cover?(date, anchor: Date.current)
  range(anchor).cover?(date)
end

#covered_dates(dates, anchor: Date.current) ⇒ Object



178
179
180
181
182
# File 'lib/sof/cycle.rb', line 178

def covered_dates(dates, anchor: Date.current)
  dates.select do |date|
    cover?(date, anchor:)
  end
end

#expiration_of(_completion_dates, anchor: Date.current) ⇒ Object



195
# File 'lib/sof/cycle.rb', line 195

def expiration_of(_completion_dates, anchor: Date.current) = nil

#extend_period(_ = nil) ⇒ Object



168
# File 'lib/sof/cycle.rb', line 168

def extend_period(_ = nil) = self

#final_date(_anchor) ⇒ Object

Return the final date of the cycle



193
# File 'lib/sof/cycle.rb', line 193

def final_date(_anchor) = nil

#from_dataObject



209
210
211
212
213
# File 'lib/sof/cycle.rb', line 209

def from_data
  return {} unless from

  {from: from}
end

#humanized_spanObject



190
# File 'lib/sof/cycle.rb', line 190

def humanized_span = [period_count, humanized_period].join(" ")

#kind_inquiryObject



151
# File 'lib/sof/cycle.rb', line 151

def kind_inquiry = ActiveSupport::StringInquirer.new(kind.to_s)

#last_completed(dates) ⇒ Object

Return the most recent completion date from the supplied array of dates



166
# File 'lib/sof/cycle.rb', line 166

def last_completed(dates) = dates.compact.map(&:to_date).max

#notationObject

Return the cycle representation as a notation string



160
# File 'lib/sof/cycle.rb', line 160

def notation = self.class.notation(to_h)

#range(anchor) ⇒ Object



188
# File 'lib/sof/cycle.rb', line 188

def range(anchor) = start_date(anchor)..final_date(anchor)

#satisfied_by?(completion_dates, anchor: Date.current) ⇒ Boolean

From the supplied anchor date, are there enough in-window completions to satisfy the cycle?

Returns:

  • (Boolean)

    true if the cycle is satisfied, false otherwise



174
175
176
# File 'lib/sof/cycle.rb', line 174

def satisfied_by?(completion_dates, anchor: Date.current)
  covered_dates(completion_dates, anchor:).size >= volume
end

#to_hObject



199
200
201
202
203
204
205
206
207
# File 'lib/sof/cycle.rb', line 199

def to_h
  {
    kind:,
    volume:,
    period:,
    period_count:,
    **from_data
  }
end

#validate_periodObject



153
154
155
156
157
# File 'lib/sof/cycle.rb', line 153

def validate_period
  return if valid_periods.empty?

  self.class.validate_period(period_key)
end

#volume_to_delay_expiration(_completion_dates, anchor:) ⇒ Object



197
# File 'lib/sof/cycle.rb', line 197

def volume_to_delay_expiration(_completion_dates, anchor:) = 0