Module: Familia::Features

Includes:
Autoloader
Included in:
DataType
Defined in:
lib/familia/features.rb,
lib/familia/features/safe_dump.rb,
lib/familia/features/autoloader.rb,
lib/familia/features/expiration.rb,
lib/familia/features/quantization.rb,
lib/familia/features/relationships.rb,
lib/familia/features/encrypted_fields.rb,
lib/familia/features/transient_fields.rb,
lib/familia/features/object_identifier.rb,
lib/familia/features/schema_validation.rb,
lib/familia/features/external_identifier.rb,
lib/familia/features/relationships/indexing.rb,
lib/familia/features/relationships/participation.rb,
lib/familia/features/relationships/score_encoding.rb,
lib/familia/features/relationships/collection_operations.rb,
lib/familia/features/relationships/indexing_relationship.rb,
lib/familia/features/relationships/participation_membership.rb,
lib/familia/features/relationships/participation_relationship.rb,
lib/familia/features/relationships/indexing/rebuild_strategies.rb,
lib/familia/features/relationships/participation/target_methods.rb,
lib/familia/features/relationships/indexing/multi_index_generators.rb,
lib/familia/features/relationships/indexing/unique_index_generators.rb,
lib/familia/features/relationships/participation/participant_methods.rb,
lib/familia/features/relationships/participation/through_model_operations.rb

Overview

rubocop:disable Style/ClassAndModuleChildren

Defined Under Namespace

Modules: Autoloader, EncryptedFields, Expiration, ExternalIdentifier, ObjectIdentifier, Quantization, Relationships, SafeDump, SchemaValidation, TransientFields

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Autoloader

autoload_files, included, normalize_to_config_name

Instance Attribute Details

#features_enabledObject (readonly)

Returns the value of attribute features_enabled.



82
83
84
# File 'lib/familia/features.rb', line 82

def features_enabled
  @features_enabled
end

Instance Method Details

#feature(feature_name = nil, **options) ⇒ Array?

Enables a feature for the current class with optional configuration.

Features are modular capabilities that can be mixed into Familia::Horreum classes. Each feature can be configured with options that are stored per-class, ensuring complete isolation between different models.

Examples:

Enable feature without options

class User < Familia::Horreum
  feature :expiration
end

Enable feature with options (per-class storage)

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

class Session < Familia::Horreum
  feature :object_identifier, generator: :hex  # Different options
end

# Each class maintains separate options:
User.feature_options(:object_identifier)    #=> {generator: :uuid_v4}
Session.feature_options(:object_identifier) #=> {generator: :hex}

Parameters:

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

    the name of the feature to enable. If nil, returns the list of currently enabled features.

  • options (Hash)

    configuration options for the feature. These are stored per-class and do not interfere with other models' configurations.

Returns:

  • (Array, nil)

    the list of enabled features if feature_name is nil, otherwise nil

Raises:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
# File 'lib/familia/features.rb', line 117

def feature(feature_name = nil, **options)
  @features_enabled ||= []

  return features_enabled if feature_name.nil?

  # If there's a value provided check that it's a valid feature
  feature_name = feature_name.to_sym
  feature_module = Familia::Base.find_feature(feature_name, self)
  raise Familia::Problem, "Unsupported feature: #{feature_name}" unless feature_module

  # If the feature is already available, do nothing but log about it
  if features_enabled.member?(feature_name)
    Familia.warn "[#{self.class}] feature already available: #{feature_name}"
    return
  end

  Familia.trace :FEATURE, nil, "#{self} includes #{feature_name.inspect}" if Familia.debug?

  # Check dependencies and raise error if missing
  feature_def = Familia::Base.feature_definitions[feature_name]
  if feature_def&.depends_on&.any?
    missing = feature_def.depends_on - features_enabled
    if missing.any?
      raise Familia::Problem,
            "Feature #{feature_name} requires missing dependencies: #{missing.join(', ')}"
    end
  end

  # Add it to the list available features_enabled for Familia::Base classes.
  features_enabled << feature_name

  # Always capture and store the calling location for every feature
  calling_location = caller_locations(1, 1)&.first
  options[:calling_location] = calling_location&.path

  # Initialize field group if feature declares one
  if feature_def&.field_group && respond_to?(:field_group)
    field_group(feature_def.field_group)
  end

  # Add feature options if the class supports them (Horreum classes)
  add_feature_options(feature_name, **options) if respond_to?(:add_feature_options)

  # Extend the Familia::Base subclass (e.g. Customer) with the feature module
  include feature_module

  # NOTE: Do we want to extend Familia::DataType here? That would make it
  # possible to call safe_dump on relations fields (e.g. list, zset, hashkey).
  #
  # The challenge is that DataType classes (List, UnsortedSet, etc.) are shared across
  # all Horreum models. If Customer extends DataType with safe_dump, then
  # Session's lists would also have it. Not ideal. If that's all we wanted
  # then we can do that by looping through every DataType class here.
  #
  # We'd need to extend the DataType instances for each Horreum subclass. That
  # avoids it getting included multiple times per DataType
end