Module: Clickwrap::Identifier

Defined in:
lib/clickwrap/identifier.rb

Overview

Event identifiers.

Clickwrap events use ULIDs (https://github.com/ulid/spec): 26 Crockford base32 characters, the first ten encoding milliseconds since the Unix epoch. They sort lexicographically in creation order, which keeps evidence exports and index scans in a sensible sequence, and they carry no host database sequence a reader could use to count unrelated records.

The embedded timestamp is a convenience for ordering. It is not the evidentiary time: that is recorded_at_by_server on the event, recorded from the application server's clock and described as exactly that.

Constant Summary collapse

ENCODING =
"0123456789ABCDEFGHJKMNPQRSTVWXYZ"
ENCODING_LENGTH =
32
TIME_LENGTH =
10
RANDOM_LENGTH =
16
LENGTH =
TIME_LENGTH + RANDOM_LENGTH
PATTERN =
/\A[0-7][#{ENCODING}]{25}\z/

Class Method Summary collapse

Class Method Details

.generate(moment = Time.now) ⇒ Object

Returns a new ULID. moment is accepted so tests and importers can produce deterministic, correctly ordered identifiers.

Identifiers generated within the same millisecond increment instead of re-randomizing, so a batch of events captured together still sorts in the order it was written. That matters for exports and for reading a lifecycle chain by identifier alone.



37
38
39
# File 'lib/clickwrap/identifier.rb', line 37

def generate(moment = Time.now)
  MUTEX.synchronize { monotonic_ulid(encode_time(moment)) }
end

.time_from(value) ⇒ Object

Returns the Time encoded in the identifier, or nil when it is not a ULID. Callers must not treat this as the recorded server time.



47
48
49
50
51
52
53
54
55
# File 'lib/clickwrap/identifier.rb', line 47

def time_from(value)
  return nil unless valid?(value)

  milliseconds = value.to_s[0, TIME_LENGTH].each_char.reduce(0) do |total, char|
    (total * ENCODING_LENGTH) + ENCODING.index(char)
  end

  Time.at(milliseconds / 1000.0).utc
end

.valid?(value) ⇒ Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/clickwrap/identifier.rb', line 41

def valid?(value)
  PATTERN.match?(value.to_s)
end