Class: SOF::Cycle
- Inherits:
-
Object
- Object
- SOF::Cycle
- Extended by:
- Forwardable
- Defined in:
- lib/sof/cycle.rb,
lib/sof/cycle/version.rb
Direct Known Subclasses
SOF::Cycles::Calendar, SOF::Cycles::EndOf, SOF::Cycles::Interval, SOF::Cycles::Lookback, SOF::Cycles::LookbackEndOf, SOF::Cycles::VolumeOnly, SOF::Cycles::Within
Defined Under Namespace
Classes: InvalidInput, InvalidKind, InvalidPeriod
Constant Summary collapse
- VERSION =
"0.1.14"
Class Attribute Summary collapse
-
.kind ⇒ Object
readonly
Returns the value of attribute kind.
-
.notation_id ⇒ Object
readonly
Returns the value of attribute notation_id.
-
.valid_periods ⇒ Object
readonly
Returns the value of attribute valid_periods.
Instance Attribute Summary collapse
-
#parser ⇒ Object
readonly
Returns the value of attribute parser.
Class Method Summary collapse
-
.class_for_kind(sym) ⇒ Object
Return the class handling the kind.
-
.class_for_notation_id(notation_id) ⇒ Object
Return the appropriate class for the give notation id.
- .cycle_handlers ⇒ Object
- .dormant_capable? ⇒ Boolean
-
.dump(cycle_or_string) ⇒ Object
Turn a cycle or notation string into a hash.
-
.for(notation) ⇒ Cycle
Return a Cycle object from a notation string.
- .handles?(sym) ⇒ Boolean
- .inherited(klass) ⇒ Object
-
.legend ⇒ Hash
Return a legend explaining all notation components.
-
.load(hash) ⇒ Object
Return a Cycle object from a hash.
-
.notation(hash) ⇒ String
Retun a notation string from a hash.
- .recurring? ⇒ Boolean
-
.validate_period(period) ⇒ Object
Raises an error if the given period isn’t in the list of valid periods.
- .volume_only? ⇒ Boolean
Instance Method Summary collapse
-
#==(other) ⇒ Object
Cycles are considered equal if their hash representations are equal.
- #as_json ⇒ Object
- #considered_dates(completion_dates, anchor: Date.current) ⇒ Object
- #cover?(date, anchor: Date.current) ⇒ Boolean
- #covered_dates(dates, anchor: Date.current) ⇒ Object
- #expiration_of(_completion_dates, anchor: Date.current) ⇒ Object
- #extend_period(_ = nil) ⇒ Object
-
#final_date(_anchor) ⇒ Object
Return the final date of the cycle.
- #from_data ⇒ Object
- #humanized_span ⇒ Object
-
#initialize(notation, parser: Parser.new(notation)) ⇒ Cycle
constructor
A new instance of Cycle.
- #kind_inquiry ⇒ Object
-
#last_completed(dates) ⇒ Object
Return the most recent completion date from the supplied array of dates.
-
#notation ⇒ Object
Return the cycle representation as a notation string.
- #range(anchor) ⇒ Object
-
#satisfied_by?(completion_dates, anchor: Date.current) ⇒ Boolean
From the supplied anchor date, are there enough in-window completions to satisfy the cycle?.
- #to_h ⇒ Object
- #validate_period ⇒ Object
- #volume_to_delay_expiration(_completion_dates, anchor:) ⇒ Object
Constructor Details
#initialize(notation, parser: Parser.new(notation)) ⇒ Cycle
Returns a new instance of Cycle.
209 210 211 212 213 214 215 216 217 |
# File 'lib/sof/cycle.rb', line 209 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
.kind ⇒ Object (readonly)
Returns the value of attribute kind.
131 132 133 |
# File 'lib/sof/cycle.rb', line 131 def kind @kind end |
.notation_id ⇒ Object (readonly)
Returns the value of attribute notation_id.
131 132 133 |
# File 'lib/sof/cycle.rb', line 131 def notation_id @notation_id end |
.valid_periods ⇒ Object (readonly)
Returns the value of attribute valid_periods.
131 132 133 |
# File 'lib/sof/cycle.rb', line 131 def valid_periods @valid_periods end |
Instance Attribute Details
#parser ⇒ Object (readonly)
Returns the value of attribute parser.
219 220 221 |
# File 'lib/sof/cycle.rb', line 219 def parser @parser end |
Class Method Details
.class_for_kind(sym) ⇒ Object
Return the class handling the kind
98 99 100 101 102 |
# File 'lib/sof/cycle.rb', line 98 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
87 88 89 90 91 |
# File 'lib/sof/cycle.rb', line 87 def class_for_notation_id(notation_id) Cycle.cycle_handlers.find do |klass| klass.notation_id == notation_id end || raise(InvalidKind, "'#{notation_id}' is not a valid kind of #{name}") end |
.cycle_handlers ⇒ Object
153 154 155 |
# File 'lib/sof/cycle.rb', line 153 def cycle_handlers @cycle_handlers ||= Set.new end |
.dormant_capable? ⇒ Boolean
134 |
# File 'lib/sof/cycle.rb', line 134 def dormant_capable? = false |
.dump(cycle_or_string) ⇒ Object
Turn a cycle or notation string into a hash
18 19 20 21 22 23 24 |
# File 'lib/sof/cycle.rb', line 18 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
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/sof/cycle.rb', line 65 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.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
149 150 151 |
# File 'lib/sof/cycle.rb', line 149 def handles?(sym) kind.to_s == sym.to_s end |
.inherited(klass) ⇒ Object
157 158 159 |
# File 'lib/sof/cycle.rb', line 157 def inherited(klass) Cycle.cycle_handlers << klass end |
.legend ⇒ Hash
Return a legend explaining all notation components
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/sof/cycle.rb', line 107 def legend { "quantity" => { "V" => { description: "Volume - the number of times something should occur", examples: ["V1L1D - once in the prior 1 day", "V3L3D - three times in the prior 3 days", "V10L10D - ten times in the prior 10 days"] } }, "kind" => build_kind_legend, "period" => build_period_legend, "date" => { "F" => { description: "From - specifies the anchor date for Within cycles", examples: ["F2024-01-01 - from January 1, 2024", "F2024-12-31 - from December 31, 2024"] } } } end |
.load(hash) ⇒ Object
Return a Cycle object from a hash
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/sof/cycle.rb', line 27 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. end |
.notation(hash) ⇒ String
Retun a notation string from a hash
46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/sof/cycle.rb', line 46 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
136 |
# File 'lib/sof/cycle.rb', line 136 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.
142 143 144 145 146 147 |
# File 'lib/sof/cycle.rb', line 142 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
132 |
# File 'lib/sof/cycle.rb', line 132 def volume_only? = @volume_only |
Instance Method Details
#==(other) ⇒ Object
Cycles are considered equal if their hash representations are equal
249 |
# File 'lib/sof/cycle.rb', line 249 def ==(other) = to_h == other.to_h |
#as_json ⇒ Object
305 |
# File 'lib/sof/cycle.rb', line 305 def as_json(...) = notation |
#considered_dates(completion_dates, anchor: Date.current) ⇒ Object
264 265 266 |
# File 'lib/sof/cycle.rb', line 264 def considered_dates(completion_dates, anchor: Date.current) covered_dates(completion_dates, anchor:).max_by(volume) { it } end |
#cover?(date, anchor: Date.current) ⇒ Boolean
274 275 276 |
# File 'lib/sof/cycle.rb', line 274 def cover?(date, anchor: Date.current) range(anchor).cover?(date) end |
#covered_dates(dates, anchor: Date.current) ⇒ Object
268 269 270 271 272 |
# File 'lib/sof/cycle.rb', line 268 def covered_dates(dates, anchor: Date.current) dates.select do |date| cover?(date, anchor:) end end |
#expiration_of(_completion_dates, anchor: Date.current) ⇒ Object
285 |
# File 'lib/sof/cycle.rb', line 285 def expiration_of(_completion_dates, anchor: Date.current) = nil |
#extend_period(_ = nil) ⇒ Object
254 |
# File 'lib/sof/cycle.rb', line 254 def extend_period(_ = nil) = self |
#final_date(_anchor) ⇒ Object
Return the final date of the cycle
283 |
# File 'lib/sof/cycle.rb', line 283 def final_date(_anchor) = nil |
#from_data ⇒ Object
299 300 301 302 303 |
# File 'lib/sof/cycle.rb', line 299 def from_data return {} unless from {from: from} end |
#humanized_span ⇒ Object
280 |
# File 'lib/sof/cycle.rb', line 280 def humanized_span = [period_count, humanized_period].join(" ") |
#kind_inquiry ⇒ Object
229 |
# File 'lib/sof/cycle.rb', line 229 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
252 |
# File 'lib/sof/cycle.rb', line 252 def last_completed(dates) = dates.compact.map(&:to_date).max |
#notation ⇒ Object
Return the cycle representation as a notation string
238 239 240 241 242 243 244 245 246 |
# File 'lib/sof/cycle.rb', line 238 def notation hash = to_h [ "V#{volume}", self.class.notation_id, time_span.notation, hash.fetch(:from, nil) ].compact.join end |
#range(anchor) ⇒ Object
278 |
# File 'lib/sof/cycle.rb', line 278 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?
260 261 262 |
# File 'lib/sof/cycle.rb', line 260 def satisfied_by?(completion_dates, anchor: Date.current) covered_dates(completion_dates, anchor:).size >= volume end |
#to_h ⇒ Object
289 290 291 292 293 294 295 296 297 |
# File 'lib/sof/cycle.rb', line 289 def to_h { kind:, volume:, period:, period_count:, **from_data } end |
#validate_period ⇒ Object
231 232 233 234 235 |
# File 'lib/sof/cycle.rb', line 231 def validate_period return if valid_periods.empty? self.class.validate_period(period_key) end |
#volume_to_delay_expiration(_completion_dates, anchor:) ⇒ Object
287 |
# File 'lib/sof/cycle.rb', line 287 def volume_to_delay_expiration(_completion_dates, anchor:) = 0 |