Class: Familia::Features::ExternalIdentifier::ExternalIdentifierFieldType

Inherits:
Familia::FieldType
  • Object
show all
Defined in:
lib/familia/features/external_identifier.rb

Overview

ExternalIdentifierFieldType - Fields that derive deterministic external identifiers

External identifier fields derive shorter, public-facing identifiers that are deterministically derived from object identifiers. These IDs are safe for use in URLs, APIs, and other external contexts where shorter IDs are preferred.

Key characteristics:

  • Deterministic generation from objid ensures consistency
  • Shorter than objid (128-bit vs 256-bit) for external use
  • Base-36 encoding for URL-safe identifiers
  • 'ext_' prefix for clear identification as external IDs
  • Lazy generation preserves values from initialization

Examples:

Using external identifier fields

class User < Familia::Horreum
  feature :object_identifier
  feature :external_identifier
  field :email
end
user = User.new(email: 'user@example.com')
user.objid  # => "01234567-89ab-7def-8000-123456789abc"
user.extid  # => "ext_abc123def456ghi789" (deterministic from objid)
# Same objid always produces same extid
user2 = User.new(objid: user.objid, email: 'user@example.com')
user2.extid  # => "ext_abc123def456ghi789" (identical to user.extid)

Instance Method Summary collapse

Constructor Details

This class inherits a constructor from Familia::FieldType

Instance Method Details

#categorySymbol

Category for external identifier fields

Returns:

  • (Symbol)

    :external_identifier



127
128
129
# File 'lib/familia/features/external_identifier.rb', line 127

def category
  :external_identifier
end

#define_getter(klass) ⇒ Object

Override getter to provide lazy generation from objid

Derives the external identifier deterministically from the object's objid. This ensures consistency - the same objid will always produce the same extid. Only derives when objid is available.

Parameters:

  • klass (Class)

    The class to define the method on



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/familia/features/external_identifier.rb', line 62

def define_getter(klass)
  field_name = @name
  method_name = @method_name

  handle_method_conflict(klass, method_name) do
    klass.define_method method_name do
      # Check if we already have a value (from initialization or previous generation)
      existing_value = instance_variable_get(:"@#{field_name}")
      return existing_value unless existing_value.nil?

      # Derive external identifier from objid if available
      derived_extid = derive_external_identifier
      return unless derived_extid

      instance_variable_set(:"@#{field_name}", derived_extid)

      # Update mapping if we have an identifier (objid)
      self.class.extid_lookup[derived_extid] = identifier if respond_to?(:identifier) && identifier

      derived_extid
    end
  end
end

#define_setter(klass) ⇒ Object

Override setter to preserve values during initialization

This ensures that values passed during object initialization (e.g., when loading from Valkey/Redis) are preserved and not overwritten by the lazy generation logic.

Parameters:

  • klass (Class)

    The class to define the method on



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/familia/features/external_identifier.rb', line 94

def define_setter(klass)
  field_name = @name
  method_name = @method_name

  handle_method_conflict(klass, :"#{method_name}=") do
    klass.define_method :"#{method_name}=" do |value|
      # Remove old mapping if extid is changing
      old_value = instance_variable_get(:"@#{field_name}")
      self.class.extid_lookup.remove_field(old_value) if old_value && old_value != value

      # Set the new value
      instance_variable_set(:"@#{field_name}", value)

      # Update mapping if we have both extid and identifier
      return unless value && respond_to?(:identifier) && identifier

      self.class.extid_lookup[value] = identifier
    end
  end
end

#persistent?Boolean

External identifier fields are persisted to database

Returns:

  • (Boolean)

    true - external identifiers are always persisted



119
120
121
# File 'lib/familia/features/external_identifier.rb', line 119

def persistent?
  true
end