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
  • Customizable format template (default: 'ext_' prefix)
  • Lazy generation preserves values from initialization

Examples:

Using external identifier fields with default format

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)

Using custom format template with hyphen separator

class APIKey < Familia::Horreum
  feature :object_identifier
  feature :external_identifier, format: 'api-%{id}'
end
key = APIKey.new
key.extid  # => "api-abc123def456ghi789"

Using custom format template with custom prefix

class Customer < Familia::Horreum
  feature :object_identifier
  feature :external_identifier, format: 'cust_%{id}'
end
customer = Customer.new
customer.extid  # => "cust_abc123def456ghi789"

Using format template without prefix

class Resource < Familia::Horreum
  feature :object_identifier
  feature :external_identifier, format: 'v2/%{id}'
end
resource = Resource.new
resource.extid  # => "v2/abc123def456ghi789"

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



146
147
148
# File 'lib/familia/features/external_identifier.rb', line 146

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



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/familia/features/external_identifier.rb', line 89

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)

      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



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/familia/features/external_identifier.rb', line 118

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)
    end
  end
end

#persistent?Boolean

External identifier fields are persisted to database

Returns:

  • (Boolean)

    true - external identifiers are always persisted



138
139
140
# File 'lib/familia/features/external_identifier.rb', line 138

def persistent?
  true
end