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:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/familia/features/external_identifier.rb', line 219

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 prefix from feature options, default to "ext"
  options = self.class.feature_options(:external_identifier)
  prefix = options[:prefix] || 'ext'

  "#{prefix}_#{external_part}"
end

#destroy!Object



278
279
280
281
282
283
284
# File 'lib/familia/features/external_identifier.rb', line 278

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



266
267
268
# File 'lib/familia/features/external_identifier.rb', line 266

def external_identifier
  extid
end

#external_identifier=(value) ⇒ Object

Full-length alias setter for extid

Parameters:

  • value (String)

    The external identifier to set



274
275
276
# File 'lib/familia/features/external_identifier.rb', line 274

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