Module: RubyReactor::Period

Defined in:
lib/ruby_reactor/period.rb

Overview

Calendar-aligned bucket helpers for ‘with_period` dedup gating.

A bucket is a deterministic string derived from the current UTC time and the configured period. Two calls in the same bucket dedup to the same Redis marker key; calls that cross a calendar boundary land in different buckets and run again.

Constant Summary collapse

SYMBOLIC_PERIODS =
{
  second: 1,
  minute: 60,
  hour: 60 * 60,
  day: 60 * 60 * 24,
  week: 60 * 60 * 24 * 7,
  month: 60 * 60 * 24 * 31,
  year: 60 * 60 * 24 * 366
}.freeze

Class Method Summary collapse

Class Method Details

.bucket_id(every, now: Time.now.utc) ⇒ Object

Build the bucket id for a given period at a given moment. UTC, calendar aligned for symbolic periods, index-based for integer seconds.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ruby_reactor/period.rb', line 23

def self.bucket_id(every, now: Time.now.utc)
  case every
  when :second then now.strftime("%Y-%m-%dT%H-%M-%S")
  when :minute then now.strftime("%Y-%m-%dT%H-%M")
  when :hour   then now.strftime("%Y-%m-%dT%H")
  when :day    then now.strftime("%Y-%m-%d")
  when :week   then now.strftime("%G-W%V")
  when :month  then now.strftime("%Y-%m")
  when :year   then now.strftime("%Y")
  when Integer
    raise ArgumentError, "Period seconds must be positive" unless every.positive?

    "i#{now.to_i / every}"
  else
    raise ArgumentError, "Unknown period: #{every.inspect}"
  end
end

.key(base, every, now: Time.now.utc) ⇒ Object



63
64
65
# File 'lib/ruby_reactor/period.rb', line 63

def self.key(base, every, now: Time.now.utc)
  "period:#{base}:#{bucket_id(every, now: now)}"
end

.period_seconds(every) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ruby_reactor/period.rb', line 48

def self.period_seconds(every)
  case every
  when Symbol
    SYMBOLIC_PERIODS.fetch(every) do
      raise ArgumentError, "Unknown period: #{every.inspect}"
    end
  when Integer
    raise ArgumentError, "Period seconds must be positive" unless every.positive?

    every
  else
    raise ArgumentError, "Unknown period: #{every.inspect}"
  end
end

.ttl_seconds(every) ⇒ Object

TTL for the marker. Twice the period length so the marker survives clock skew across the boundary and reliably dedups the very next attempt.



43
44
45
46
# File 'lib/ruby_reactor/period.rb', line 43

def self.ttl_seconds(every)
  base = period_seconds(every)
  base * 2
end