Module: Familia::Utils

Included in:
Familia
Defined in:
lib/familia/utils.rb

Constant Summary collapse

LENGTH_256_BIT =
[nil, nil] + (2..36).map { |b| calc_length.call(256, b) }
LENGTH_128_BIT =
[nil, nil] + (2..36).map { |b| calc_length.call(128, b) }
LENGTH_64_BIT =
[nil, nil] + (2..36).map { |b| calc_length.call(64, b) }

Instance Method Summary collapse

Instance Method Details

#dbkey(*val) ⇒ String

Creates a dbkey from given values

Parameters:

  • val (Array)

    elements to join for the key

Returns:



108
109
110
# File 'lib/familia/utils.rb', line 108

def dbkey(*val)
  join(*val)
end

#distinguisher(value_to_distinguish, strict_values: true) ⇒ String?

This method determines the appropriate transformation to apply based on the class of the input argument.

The method uses a case statement to handle different classes: - For Symbol, String, Integer, and Float classes, it traces the operation and converts the value to a string. - For Familia::Horreum class, it traces the operation and returns the identifier of the value. - For TrueClass, FalseClass, and NilClass, it traces the operation and converts the value to a string (“true”, “false”, or “”). - For any other class, it traces the operation and returns nil.

Alternative names for value_to_distinguish could be input_value, value, or object.

Parameters:

  • value_to_distinguish (Object)

    The value to be processed. Keep in mind that all data is stored as a string so whatever the type of the value, it will be converted to a string.

  • strict_values (Boolean) (defaults to: true)

    Whether to enforce strict value handling. Defaults to true.

Returns:

  • (String, nil)

    The processed value as a string or nil for unsupported classes.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/familia/utils.rb', line 179

def distinguisher(value_to_distinguish, strict_values: true)
  case value_to_distinguish
  when ::Symbol, ::String, ::Integer, ::Float
    Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "string", caller(1..1) if Familia.debug?

    # Symbols and numerics are naturally serializable to strings
    # so it's a relatively low risk operation.
    value_to_distinguish.to_s

  when ::TrueClass, ::FalseClass, ::NilClass
    Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "true/false/nil", caller(1..1) if Familia.debug?

    # TrueClass, FalseClass, and NilClass are considered high risk because their
    # original types cannot be reliably determined from their serialized string
    # representations. This can lead to unexpected behavior during deserialization.
    # For instance, a TrueClass value serialized as "true" might be deserialized as
    # a String, causing application errors. Even more problematic, a NilClass value
    # serialized as an empty string makes it impossible to distinguish between a
    # nil value and an empty string upon deserialization. Such scenarios can result
    # in subtle, hard-to-diagnose bugs. To mitigate these risks, we raise an
    # exception when encountering these types unless the strict_values option is
    # explicitly set to false.
    #
    raise Familia::HighRiskFactor, value_to_distinguish if strict_values
    value_to_distinguish.to_s #=> "true", "false", ""

  when Familia::Base, Class
    Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "base", caller(1..1) if Familia.debug?

    # When called with a class we simply transform it to its name. For
    # instances of Familia class, we store the identifier.
    if value_to_distinguish.is_a?(Class)
      value_to_distinguish.name
    else
      value_to_distinguish.identifier
    end

  else
     Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "else1 #{strict_values}", caller(1..1) if Familia.debug?

    if value_to_distinguish.class.ancestors.member?(Familia::Base)
      Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "isabase", caller(1..1) if Familia.debug?

      value_to_distinguish.identifier

    else
      Familia.trace :TOREDIS_DISTINGUISHER, dbclient, "else2 #{strict_values}", caller(1..1) if Familia.debug?
      raise Familia::HighRiskFactor, value_to_distinguish if strict_values
      nil
    end
  end
end

#generate_hex_idString

Generates a 256-bit cryptographically secure hexadecimal identifier.

Returns:

  • (String)

    A 64-character hex string representing 256 bits of entropy.



14
15
16
# File 'lib/familia/utils.rb', line 14

def generate_hex_id
  SecureRandom.hex(32)
end

#generate_hex_trace_idString

Note:

64 bits provides ~18 quintillion values, sufficient for request tracing.

Generates a 64-bit cryptographically secure hexadecimal trace identifier.

Returns:

  • (String)

    A 16-character hex string representing 64 bits of entropy.



39
40
41
# File 'lib/familia/utils.rb', line 39

def generate_hex_trace_id
  SecureRandom.hex(8)
end

#generate_id(base = 36) ⇒ String

Generates a cryptographically secure identifier, encoded in the specified base. By default, this creates a compact, URL-safe base-36 string.

Examples:

Generate a 256-bit ID in base-36 (default)

generate_id # => "25nkfebno45yy36z47ffxef2a7vpg4qk06ylgxzwgpnz4q3os4"

Generate a 256-bit ID in base-16 (hexadecimal)

generate_id(16) # => "568bdb582bc5042bf435d3f126cf71593981067463709c880c91df1ad9777a34"

Parameters:

  • base (Integer) (defaults to: 36)

    The base for encoding the output string (2-36, default: 36).

Returns:

  • (String)

    A secure identifier.



30
31
32
33
# File 'lib/familia/utils.rb', line 30

def generate_id(base = 36)
  target_length = LENGTH_256_BIT[base]
  generate_hex_id.to_i(16).to_s(base).rjust(target_length, '0')
end

#generate_trace_id(base = 36) ⇒ String

Generates a short, secure trace identifier, encoded in the specified base. Suitable for tracing, logging, and other ephemeral use cases.

Examples:

Generate a 64-bit short ID in base-36 (default)

generate_trace_id # => "lh7uap704unf"

Generate a 64-bit short ID in base-16 (hexadecimal)

generate_trace_id(16) # => "94cf9f8cfb0eb692"

Parameters:

  • base (Integer) (defaults to: 36)

    The base for encoding the output string (2-36, default: 36).

Returns:

  • (String)

    A secure short identifier.



55
56
57
58
# File 'lib/familia/utils.rb', line 55

def generate_trace_id(base = 36)
  target_length = LENGTH_64_BIT[base]
  generate_hex_trace_id.to_i(16).to_s(base).rjust(target_length, '0')
end

#join(*val) ⇒ String

Joins array elements with Familia delimiter

Parameters:

  • val (Array)

    elements to join

Returns:



94
95
96
# File 'lib/familia/utils.rb', line 94

def join(*val)
  val.compact.join(Familia.delim)
end

#now(name = Time.now) ⇒ Float

Returns current time in UTC as a float

Parameters:

  • name (Time) (defaults to: Time.now)

    time object (default: current time)

Returns:

  • (Float)

    time in seconds since epoch



123
124
125
# File 'lib/familia/utils.rb', line 123

def now(name = Time.now)
  name.utc.to_f
end

#qstamp(quantum = 10.minutes, pattern: nil, time: nil) ⇒ Integer, String

A quantized timestamp

Examples:

Familia.qstamp  # Returns an integer timestamp rounded to the nearest 10 minutes
Familia.qstamp(1.hour)  # Uses 1 hour quantum
Familia.qstamp(10.minutes, pattern: '%H:%M')  # Returns a formatted string like "12:30"
Familia.qstamp(10.minutes, time: 1302468980)  # Quantizes the given Unix timestamp
Familia.qstamp(10.minutes, time: Time.now)  # Quantizes the given Time object
Familia.qstamp(10.minutes, pattern: '%H:%M', time: 1302468980)  # Formats a specific time

Parameters:

  • quantum (Integer) (defaults to: 10.minutes)

    The time quantum in seconds (default: 10 minutes).

  • pattern (String, nil) (defaults to: nil)

    The strftime pattern to format the timestamp.

  • time (Integer, Float, Time, nil) (defaults to: nil)

    A specific time to quantize (default: current time).

Returns:

  • (Integer, String)

    A unix timestamp or formatted timestamp string.



142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/familia/utils.rb', line 142

def qstamp(quantum = 10.minutes, pattern: nil, time: nil)
  time ||= Familia.now
  time = time.to_f if time.is_a?(Time)

  rounded = time - (time % quantum)

  if pattern
    Time.at(rounded).utc.strftime(pattern)
  else
    Time.at(rounded).utc.to_i
  end
end

#serverid(uri) ⇒ Object

Gets server ID without DB component for pool identification



113
114
115
116
117
118
# File 'lib/familia/utils.rb', line 113

def serverid(uri)
  # Create a copy of URI without DB for server identification
  uri = uri.dup
  uri.db = nil
  uri.serverid
end

#shorten_to_external_id(hex_id, base: 36) ⇒ String

Note:

This is useful for creating shorter, public-facing IDs from secure internal ones.

Truncates a 256-bit hexadecimal ID to 128 bits and encodes it in a given base. This function takes the most significant bits from the hex string to maintain randomness while creating a shorter, deterministic identifier.

Examples:

Create a shorter external ID from a full 256-bit internal ID

hex_id = generate_hex_id
external_id = shorten_to_external_id(hex_id)

Parameters:

  • hex_id (String)

    A 64-character hexadecimal string (representing 256 bits).

  • base (Integer) (defaults to: 36)

    The base for encoding the output string (2-36, default: 36).

Returns:

  • (String)

    A 128-bit identifier, encoded in the specified base.



74
75
76
77
78
# File 'lib/familia/utils.rb', line 74

def shorten_to_external_id(hex_id, base: 36)
  target_length = LENGTH_128_BIT[base]
  truncated = hex_id.to_i(16) >> (256 - 128)  # Always 128 bits
  truncated.to_s(base).rjust(target_length, '0')
end

#shorten_to_trace_id(hex_id, base: 36) ⇒ String

Truncates a 256-bit hexadecimal ID to 64 bits and encodes it in a given base.

Parameters:

  • hex_id (String)

    A 64-character hexadecimal string (representing 256 bits).

  • base (Integer) (defaults to: 36)

    The base for encoding the output string (2-36, default: 36).

Returns:

  • (String)

    A 64-bit identifier, encoded in the specified base.



85
86
87
88
89
# File 'lib/familia/utils.rb', line 85

def shorten_to_trace_id(hex_id, base: 36)
  target_length = LENGTH_64_BIT[base]
  truncated = hex_id.to_i(16) >> (256 - 64)   # Always 64 bits
  truncated.to_s(base).rjust(target_length, '0')
end

#split(val) ⇒ Array

Splits a string using Familia delimiter

Parameters:

  • val (String)

    string to split

Returns:

  • (Array)

    split elements



101
102
103
# File 'lib/familia/utils.rb', line 101

def split(val)
  val.split(Familia.delim)
end