Module: Familia::Features::ExternalIdentifier

Defined in:
lib/familia/features/external_identifier.rb

Overview

Familia::Features::ExternalIdentifier

Defined Under Namespace

Modules: ModelClassMethods, ModelInstanceMethods Classes: ExternalIdentifierError, ExternalIdentifierFieldType

Instance Method Summary collapse

Instance Method Details

#derive_external_identifierString?

Derives a deterministic, public-facing external identifier from the object's internal objid.

This method uses the objid's high-quality randomness to seed a pseudorandom number generator (PRNG). The PRNG then acts as a complex, deterministic function to produce a new identifier that has no discernible mathematical correlation to the objid. This is a security measure to prevent leaking information (like timestamps from UUIDv7) from the internal identifier to the public one.

The resulting identifier is always deterministic: the same objid will always produce the same extid, which is crucial for lookups.

Returns:

  • (String, nil)

    A prefixed, base36-encoded external identifier, or nil if the objid is not present.

Raises:



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/familia/features/external_identifier.rb', line 274

def derive_external_identifier
  raise ExternalIdentifierError, 'Missing objid field' unless respond_to?(:objid)

  current_objid = objid
  return nil if current_objid.nil? || current_objid.to_s.empty?

  # Validate objid provenance for security guarantees
  validate_objid_provenance!

  # Normalize the objid to a consistent hex representation first.
  normalized_hex = normalize_objid_to_hex(current_objid)

  # Use the objid's randomness to create a deterministic, yet secure,
  # external identifier. We do not use SecureRandom here because the output
  # must be deterministic.
  #
  # The process is as follows:
  # 1. The objid (a high-entropy value) is hashed to create a uniform seed.
  # 2. The seed initializes a standard PRNG (Random.new).
  # 3. The PRNG acts as a deterministic function to generate a sequence of
  #    bytes that appears random, obscuring the original objid.

  # 1. Create a high-quality, uniform seed from the objid's entropy.
  seed = Digest::SHA256.digest(normalized_hex)

  # 2. Initialize a PRNG with the seed. The same seed will always produce
  #    the same sequence of "random" numbers.
  prng = Random.new(seed.unpack1('Q>'))

  # 3. Generate 16 bytes (128 bits) of deterministic output.
  random_bytes = prng.bytes(16)

  # Encode as a base36 string for a compact, URL-safe identifier.
  # 128 bits is approximately 25 characters in base36.
  external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')

  # Get format from feature options and interpolate the ID
  options = self.class.feature_options(:external_identifier)
  format = options[:format] || 'ext_%{id}'

  format % { id: external_part }
end

#destroy!Object



333
334
335
336
337
338
339
# File 'lib/familia/features/external_identifier.rb', line 333

def destroy!
  # Clean up extid mapping when object is destroyed
  current_extid = instance_variable_get(:@extid)
  self.class.extid_lookup.remove_field(current_extid) if current_extid

  super if defined?(super)
end

#external_identifierString

Full-length alias for extid for clarity when needed

Returns:

  • (String)

    The external identifier



321
322
323
# File 'lib/familia/features/external_identifier.rb', line 321

def external_identifier
  extid
end

#external_identifier=(value) ⇒ Object

Full-length alias setter for extid

Parameters:

  • value (String)

    The external identifier to set



329
330
331
# File 'lib/familia/features/external_identifier.rb', line 329

def external_identifier=(value)
  self.extid = value
end