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, #pipelined_mode, #pipelined_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



350
351
352
353
354
355
356
357
358
359
360
# File 'lib/familia/horreum/definition.rb', line 350

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


222
223
224
225
# File 'lib/familia/horreum/definition.rb', line 222

def class_related_fields
  @class_related_fields ||= {}
  @class_related_fields
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



315
316
317
318
319
320
# File 'lib/familia/horreum/definition.rb', line 315

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


173
174
175
176
# File 'lib/familia/horreum/definition.rb', line 173

def field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise)
  field_type = FieldType.new(name, as: as, fast_method: fast_method, on_conflict: on_conflict)
  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:



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

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



126
127
128
# File 'lib/familia/horreum/definition.rb', line 126

def field_groups
  @field_groups ||= {}
end

#field_method_mapObject

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



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

def field_method_map
  field_types.transform_values(&:method_name)
end

#field_typesObject

Storage for field type instances



237
238
239
# File 'lib/familia/horreum/definition.rb', line 237

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].



217
218
219
220
# File 'lib/familia/horreum/definition.rb', line 217

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.



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

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

#logical_database(num = nil) ⇒ Object



209
210
211
212
213
# File 'lib/familia/horreum/definition.rb', line 209

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)



247
248
249
250
251
# File 'lib/familia/horreum/definition.rb', line 247

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.



198
199
200
201
202
203
204
205
206
207
# File 'lib/familia/horreum/definition.rb', line 198

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



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/familia/horreum/definition.rb', line 268

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


227
228
229
230
# File 'lib/familia/horreum/definition.rb', line 227

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

#relations?Boolean

Returns:

  • (Boolean)


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

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.



184
185
186
187
# File 'lib/familia/horreum/definition.rb', line 184

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)



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

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