Class: Familia::Features::ObjectIdentifier::ObjectIdentifierFieldType

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

Overview

ObjectIdentifierFieldType - Generate a unique object identifier

Object identifier fields automatically generate unique identifiers when first accessed if not already set. The generation strategy is configurable via feature options. These fields preserve any values set during initialization to ensure data integrity when loading existing objects from the database.

The field type tracks the generator used for each objid to provide provenance information for security-sensitive operations like external identifier generation. This ensures that downstream features can validate the source and format of object identifiers without relying on string pattern matching, which cannot reliably distinguish between uuid7, uuid4, or hex formats in all cases.

Examples:

Using object identifier fields

class User < Familia::Horreum
  feature :object_identifier, generator: :uuid_v7
end

user = User.new
user.objid  # Generates UUID v7 on first access
user.objid_generator_used  # => :uuid_v7

# Loading existing object preserves ID but cannot determine original generator
user2 = User.new(objid: "existing-uuid")
user2.objid  # Returns "existing-uuid", not regenerated
user2.objid_generator_used  # => nil (unknown provenance)

Instance Method Summary collapse

Constructor Details

This class inherits a constructor from Familia::FieldType

Instance Method Details

#categorySymbol

Category for object identifier fields

Returns:

  • (Symbol)

    :object_identifier



222
223
224
# File 'lib/familia/features/object_identifier.rb', line 222

def category
  :object_identifier
end

#define_getter(klass) ⇒ Object

Override getter to provide lazy generation with configured strategy

Generates the identifier using the configured strategy if not already set. This preserves any values set during initialization while providing automatic generation for new objects.

Parameters:

  • klass (Class)

    The class to define the method on



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/familia/features/object_identifier.rb', line 147

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?

      # Generate new identifier using configured strategy
      generated_id = generate_object_identifier
      instance_variable_set(:"@#{field_name}", generated_id)

      # Track which generator was used for provenance
      options = self.class.feature_options(:object_identifier)
      generator = options[:generator] || DEFAULT_GENERATOR
      instance_variable_set(:"@#{field_name}_generator_used", generator)

      generated_id
    end
  end

  # Define getter for generator provenance tracking
  handle_method_conflict(klass, :"#{method_name}_generator_used") do
    klass.define_method :"#{method_name}_generator_used" do
      instance_variable_get(:"@#{field_name}_generator_used")
    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



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/familia/features/object_identifier.rb', line 186

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 objid is changing
      old_value = instance_variable_get(:"@#{field_name}")
      if old_value && old_value != value
        Familia.logger.info("Removing objid mapping for #{old_value}")
        self.class.objid_lookup.remove_field(old_value)
      end

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

      # When setting objid from external source (e.g., loading from Valkey/Redis),
      # infer the generator type from the format to restore provenance tracking.
      # This allows features like ExternalIdentifier to work correctly on loaded objects.
      inferred_generator = infer_objid_generator(value)
      instance_variable_set(:"@#{field_name}_generator_used", inferred_generator)
    end
  end
end

#persistent?Boolean

Object identifier fields are persisted to database

Returns:

  • (Boolean)

    true - An object identifier is always persisted



214
215
216
# File 'lib/familia/features/object_identifier.rb', line 214

def persistent?
  true
end