Module: Familia::Horreum::DefinitionMethods

Includes:
RelatedFieldsManagement, Settings
Defined in:
lib/familia/horreum/subclass/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



294
295
296
297
298
299
300
301
302
303
304
# File 'lib/familia/horreum/subclass/definition.rb', line 294

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


164
165
166
167
# File 'lib/familia/horreum/subclass/definition.rb', line 164

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

#dump_methodObject



178
179
180
# File 'lib/familia/horreum/subclass/definition.rb', line 178

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



259
260
261
262
263
264
# File 'lib/familia/horreum/subclass/definition.rb', line 259

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


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/familia/horreum/subclass/definition.rb', line 99

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_method_mapObject

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



192
193
194
# File 'lib/familia/horreum/subclass/definition.rb', line 192

def field_method_map
  field_types.transform_values(&:method_name)
end

#field_typesObject

Storage for field type instances



187
188
189
# File 'lib/familia/horreum/subclass/definition.rb', line 187

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



159
160
161
162
# File 'lib/familia/horreum/subclass/definition.rb', line 159

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.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/familia/horreum/subclass/definition.rb', line 60

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



182
183
184
# File 'lib/familia/horreum/subclass/definition.rb', line 182

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

#logical_database(v = nil) ⇒ Object



151
152
153
154
155
# File 'lib/familia/horreum/subclass/definition.rb', line 151

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

#persistent_fieldsObject

Get fields for serialization (excludes transients)



197
198
199
200
201
# File 'lib/familia/horreum/subclass/definition.rb', line 197

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

#prefix(a = 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) (defaults to: nil)

    the prefix to set (optional).

Returns:

  • (String, Symbol)

    the current prefix.



140
141
142
143
144
145
146
147
148
149
# File 'lib/familia/horreum/subclass/definition.rb', line 140

def prefix(a = nil)
  @prefix = a if a
  @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



218
219
220
221
222
223
224
225
226
# File 'lib/familia/horreum/subclass/definition.rb', line 218

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
  # Freeze the field_type to ensure immutability (maintains Data class heritage)
  field_type.freeze
end


169
170
171
172
# File 'lib/familia/horreum/subclass/definition.rb', line 169

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

#relations?Boolean

Returns:

  • (Boolean)


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

def relations?
  @has_related_fields ||= false
end

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

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

Parameters:

  • a (String, Symbol, nil) (defaults to: 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.



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

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

#transient_field(name) ⇒ Object

Create and register a transient field type

Parameters:

  • name (Symbol)

    The field name



310
311
312
313
314
# File 'lib/familia/horreum/subclass/definition.rb', line 310

def transient_field(name, **)
  require_relative '../../features/transient_fields/transient_field_type'
  field_type = TransientFieldType.new(name, **, fast_method: false)
  register_field_type(field_type)
end

#transient_fieldsObject

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



204
205
206
207
208
# File 'lib/familia/horreum/subclass/definition.rb', line 204

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