Module: Familia::Horreum::DefinitionMethods

Includes:
RelatedFieldsManagement, Settings
Defined in:
lib/familia/horreum/definition.rb

Overview

DefinitionMethods - Class-level DSL methods for defining Horreum model structure

This module is extended into classes that include Familia::Horreum, providing class methods for defining model structure and configuration (e.g., Customer.field :name, Customer.identifier_field :custid).

Key features:

  • Defines DSL methods for field definitions (field, identifier_field)
  • Includes RelatedFieldsManagement for DataType field DSL (list, set, zset, etc.)
  • Provides class-level configuration (prefix, suffix, logical_database)
  • Manages field metadata and inheritance

Instance Attribute Summary

Attributes included from Settings

#current_key_version, #default_expiration, #delim, #encryption_keys, #encryption_personalization, #transaction_mode

Instance Method Summary collapse

Methods included from RelatedFieldsManagement

#attach_class_related_field, #attach_instance_related_field

Methods included from Settings

#configure, #default_suffix, #pipeline_mode, #pipeline_mode=

Instance Method Details

#add_feature_options(feature_name, **options) ⇒ Hash

Note:

This method only sets defaults for options that don't already exist, using the ||= operator to prevent overwrites.

Add feature options for a specific feature

This method provides a clean way for features to set their default options without worrying about initialization state. Similar to register_field_type for field types.

Feature options are stored at the class level using instance variables, ensuring complete isolation between different Familia::Horreum subclasses. Each class maintains its own @feature_options hash.

Examples:

Per-class storage behavior

class ModelA < Familia::Horreum
  # This stores options in ModelA's @feature_options
  add_feature_options(:my_feature, key: 'value_a')
end

class ModelB < Familia::Horreum
  # This stores options in ModelB's @feature_options (separate from ModelA)
  add_feature_options(:my_feature, key: 'value_b')
end

Parameters:

  • feature_name (Symbol)

    The feature name

  • options (Hash)

    The options to add/merge

Returns:

  • (Hash)

    The updated options for the feature



381
382
383
384
385
386
387
388
389
390
391
# File 'lib/familia/horreum/definition.rb', line 381

def add_feature_options(feature_name, **options)
  @feature_options ||= {}
  @feature_options[feature_name.to_sym] ||= {}

  # Only set defaults for options that don't already exist
  options.each do |key, value|
    @feature_options[feature_name.to_sym][key] ||= value
  end

  @feature_options[feature_name.to_sym]
end


245
246
247
248
# File 'lib/familia/horreum/definition.rb', line 245

def class_related_fields
  @class_related_fields ||= {}
  @class_related_fields
end

#dump_methodObject



259
260
261
# File 'lib/familia/horreum/definition.rb', line 259

def dump_method
  @dump_method || :to_json # Familia.dump_method
end

#feature_options(feature_name = nil) ⇒ Hash

Retrieves feature options for the current class.

Feature options are stored per-class in instance variables, ensuring complete isolation between different Familia::Horreum subclasses. Each class maintains its own @feature_options hash that does not interfere with other classes' configurations.

Examples:

Getting options for a specific feature

class MyModel < Familia::Horreum
  feature :object_identifier, generator: :uuid_v4
end

MyModel.feature_options(:object_identifier) #=> {generator: :uuid_v4}
MyModel.feature_options                     #=> {object_identifier: {generator: :uuid_v4}}

Per-class isolation

class UserModel < Familia::Horreum
  feature :object_identifier, generator: :uuid_v4
end

class SessionModel < Familia::Horreum
  feature :object_identifier, generator: :hex
end

UserModel.feature_options(:object_identifier)    #=> {generator: :uuid_v4}
SessionModel.feature_options(:object_identifier) #=> {generator: :hex}

Parameters:

  • feature_name (Symbol, String, nil) (defaults to: nil)

    the name of the feature to get options for. If nil, returns the entire feature options hash for this class.

Returns:

  • (Hash)

    the feature options hash, either for a specific feature or all features



346
347
348
349
350
351
# File 'lib/familia/horreum/definition.rb', line 346

def feature_options(feature_name = nil)
  @feature_options ||= {}
  return @feature_options if feature_name.nil?

  @feature_options[feature_name.to_sym] || {}
end

#field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, category: nil) ⇒ Object

Defines a field for the class and creates accessor methods.

This method defines a new field for the class, creating getter and setter instance methods similar to attr_accessor. It also generates a fast writer method for immediate persistence to the database.

Parameters:

  • name (Symbol, String)

    the name of the field to define. If a method with the same name already exists, an error is raised.

  • as (Symbol, String, false, nil) (defaults to: name)

    as the name to use for the accessor method (defaults to name). If false or nil, no accessor methods are created.

  • fast_method (Symbol, false, nil) (defaults to: :"#{name}!")

    the name to use for the fast writer method (defaults to :"#{name}!"). If false or nil, no fast writer method is created.

  • on_conflict (Symbol) (defaults to: :raise)

    conflict resolution strategy when method already exists:

    • :raise - raise error if method exists (default)
    • :skip - skip definition if method exists
    • :warn - warn but proceed (may overwrite)
    • :ignore - proceed silently (may overwrite)
  • category (Symbol, nil) (defaults to: nil)

    field category for special handling:

    • nil - regular field (default)
    • :encrypted - field contains encrypted data
    • :transient - field is not persisted
    • Others, depending on features available


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/familia/horreum/definition.rb', line 180

def field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, category: nil)
  # Use field type system for consistency
  require_relative '../field_type'

  # Create appropriate field type based on category
  field_type = if category == :transient
    require_relative '../features/transient_fields/transient_field_type'
    TransientFieldType.new(name, as: as, fast_method: false, on_conflict: on_conflict)
  else
    # For regular fields and other categories, create custom field type with category override
    custom_field_type = Class.new(FieldType) do
      define_method :category do
        category || :field
      end
    end
    custom_field_type.new(name, as: as, fast_method: fast_method, on_conflict: on_conflict)
  end

  register_field_type(field_type)
end

#field_group(name) { ... } ⇒ Array<Symbol>

Defines a field group to organize related fields.

Field groups provide a way to categorize and query fields by purpose or feature. When a block is provided, fields defined within the block are automatically added to the group. Without a block, an empty group is initialized.

Examples:

Manual field grouping

class User < Familia::Horreum
  field_group :personal_info do
    field :name
    field :email
  end
end

User.personal_info  # => [:name, :email]

Initialize empty group

class User < Familia::Horreum
  field_group :placeholder
end

User.placeholder  # => []

Parameters:

  • name (Symbol, String)

    the name of the field group

Yields:

  • optional block for defining fields within the group

Returns:

  • (Array<Symbol>)

    the array of field names in the group

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/familia/horreum/definition.rb', line 83

def field_group(name, &block)

  # Prevent nested field groups
  if @current_field_group
    raise Familia::Problem,
      "Cannot define field group :#{name} while :#{@current_field_group} is being defined. " \
      "Nested field groups are not supported."
  end

  # Initialize group
  field_groups[name.to_sym] ||= []

  if block_given?
    @current_field_group = name.to_sym
    begin
      instance_eval(&block)
    ensure
      @current_field_group = nil
    end
  else
    Familia.ld "[field_group] Created field group :#{name} but no block given" if Familia.debug?
  end

  field_groups[name.to_sym]
end

#field_groupsArray<Symbol>

Returns the list of all field group names defined for the class.

Examples:

class User < Familia::Horreum
  field_group :personal_info do
    field :name
  end
  field_group :metadata do
    field :created_at
  end
end

User.field_groups  # => [
  :personal_info => [...],
  :metadata => [..]
]

Returns:

  • (Array<Symbol>)

    array of field group names



128
129
130
# File 'lib/familia/horreum/definition.rb', line 128

def field_groups
  @field_groups ||= {}
end

#field_method_mapObject

Returns a hash mapping field names to method names for backward compatibility



273
274
275
# File 'lib/familia/horreum/definition.rb', line 273

def field_method_map
  field_types.transform_values(&:method_name)
end

#field_typesObject

Storage for field type instances



268
269
270
# File 'lib/familia/horreum/definition.rb', line 268

def field_types
  @field_types ||= {}
end

#fieldsObject

Returns the list of field names defined for the class in the order that they were defined. i.e. field :a; field :b; fields => [:a, :b].



240
241
242
243
# File 'lib/familia/horreum/definition.rb', line 240

def fields
  @fields ||= []
  @fields
end

#identifier_field(val = nil) ⇒ Object

Sets or retrieves the unique identifier field for the class.

This method defines or returns the field or method that contains the unique identifier used to generate the dbkey for the object. If a value is provided, it sets the identifier field; otherwise, it returns the current identifier field.

Parameters:

  • val (Object) (defaults to: nil)

    the field name or method to set as the identifier field (optional).

Returns:

  • (Object)

    the current identifier field.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/familia/horreum/definition.rb', line 141

def identifier_field(val = nil)
  if val
    # Validate identifier field definition at class definition time
    case val
    when Symbol, String, Proc
      @identifier_field = val
    else
      raise Problem, <<~ERROR
        Invalid identifier field definition: #{val.inspect}.
        Use a field name (Symbol/String) or Proc.
      ERROR
    end
  end
  @identifier_field
end

#load_methodObject



263
264
265
# File 'lib/familia/horreum/definition.rb', line 263

def load_method
  @load_method || :from_json # Familia.load_method
end

#logical_database(num = nil) ⇒ Object



232
233
234
235
236
# File 'lib/familia/horreum/definition.rb', line 232

def logical_database(num = nil)
  Familia.trace :LOGICAL_DATABASE_DEF, "instvar:#{@logical_database}", num if Familia.debug?
  @logical_database = num unless num.nil?
  @logical_database || parent&.logical_database
end

#persistent_fieldsObject

Get fields for serialization (excludes transients)



278
279
280
281
282
# File 'lib/familia/horreum/definition.rb', line 278

def persistent_fields
  fields.select do |field|
    field_types[field]&.persistent?
  end
end

#prefix(val = nil) ⇒ String, Symbol

Sets or retrieves the prefix for generating Valkey/Redis keys.

The exception is only raised when both @prefix is nil/falsy AND name is nil, which typically occurs with anonymous classes that haven't had their prefix explicitly set.

Parameters:

  • a (String, Symbol, nil)

    the prefix to set (optional).

Returns:

  • (String, Symbol)

    the current prefix.



221
222
223
224
225
226
227
228
229
230
# File 'lib/familia/horreum/definition.rb', line 221

def prefix(val = nil)
  @prefix = val if val
  @prefix || begin
    if name.nil?
      raise Problem, 'Cannot generate prefix for anonymous class. ' \
                     'Use `prefix` method to set explicitly.'
    end
    config_name.to_sym
  end
end

#register_field_type(field_type) ⇒ Object

Register a field type instance with this class

This method installs the field type's methods and registers it for later reference. It maintains backward compatibility by creating FieldDefinition objects.

Parameters:

  • field_type (FieldType)

    The field type to register



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/familia/horreum/definition.rb', line 299

def register_field_type(field_type)
  fields << field_type.name
  field_type.install(self)
  # Complete the registration after installation. If we do this beforehand
  # we can run into issues where it looks like it's already installed.
  field_types[field_type.name] = field_type

  # Add to current field group if one is active
  if @current_field_group
    @field_groups[@current_field_group] << field_type.name
  end

  # Freeze the field_type to ensure immutability (maintains Data class heritage)
  field_type.freeze
end


250
251
252
253
# File 'lib/familia/horreum/definition.rb', line 250

def related_fields
  @related_fields ||= {}
  @related_fields
end

#relations?Boolean

Returns:

  • (Boolean)


255
256
257
# File 'lib/familia/horreum/definition.rb', line 255

def relations?
  @has_related_fields ||= false
end

#suffix(val = nil, &blk) ⇒ String, Symbol

Sets or retrieves the suffix for generating Valkey/Redis keys.

Parameters:

  • a (String, Symbol, nil)

    the suffix to set (optional).

  • blk (Proc)

    a block that returns the suffix (optional).

Returns:

  • (String, Symbol)

    the current suffix or Familia.default_suffix if none is set.



207
208
209
210
# File 'lib/familia/horreum/definition.rb', line 207

def suffix(val = nil, &blk)
  @suffix = val || blk if val || !blk.nil?
  @suffix || Familia.default_suffix
end

#transient_fieldsObject

Get fields that are not persisted to the database (transients)



285
286
287
288
289
# File 'lib/familia/horreum/definition.rb', line 285

def transient_fields
  fields.select do |field|
    field_types[field]&.transient?
  end
end