Module: Familia::Introspection

Included in:
Familia
Defined in:
lib/familia/index_descriptor.rb

Overview

Project-wide relationship introspection, extended onto Familia.

Per-class metadata already lives on each model (indexing_relationships, participation_relationships). These helpers aggregate it across the whole clan (Familia.members) and return descriptors that act without the caller knowing Familia's index method-naming or storage layout.

Instance Method Summary collapse

Instance Method Details

#assert_indexes_current!(sample: 100, owner: nil, on_stale: :raise) ⇒ Boolean

Boot guard / CI smoke test: ensure no class-level unique index holds stale-format data. Raises (default) or warns when drift is found. This is the safeguard that surfaces an un-rebuilt index before a lookup silently fails at runtime.

Examples:

Fail fast at boot

Familia.assert_indexes_current!

Non-fatal CI smoke test

Familia.assert_indexes_current!(on_stale: :warn)

Parameters:

  • sample (Integer) (defaults to: 100)

    raw values sampled per index

  • owner (Class, nil) (defaults to: nil)

    restrict the check to a single owning class

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

    :raise (default) or :warn

Returns:

  • (Boolean)

    true when all checked indexes are current

Raises:

  • (ArgumentError)

    when on_stale is not :raise or :warn

  • (Familia::Problem)

    when stale indexes are found and on_stale: :raise



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/familia/index_descriptor.rb', line 240

def assert_indexes_current!(sample: 100, owner: nil, on_stale: :raise)
  unless %i[raise warn].include?(on_stale)
    raise ArgumentError, "on_stale: must be :raise or :warn; got #{on_stale.inspect}"
  end

  stale = stale_indexes(sample: sample, owner: owner)
  return true if stale.empty?

  msg = "Stale unique indexes need rebuild: #{stale.map(&:coordinate).join(', ')}. " \
        'See docs/migrating/v2.10.md (Unique-index storage format).'
  raise Familia::Problem, msg unless on_stale == :warn

  Familia.warn "[familia] #{msg}"
  false
end

#index_descriptors(cardinality: nil, class_level: nil, owner: nil) ⇒ Array<Familia::IndexDescriptor>

All index descriptors across every loaded Horreum subclass.

Parameters:

  • cardinality (Symbol, nil) (defaults to: nil)

    filter by :unique or :multi

  • class_level (Boolean, nil) (defaults to: nil)

    filter class-level (true) vs scoped (false)

  • owner (Class, nil) (defaults to: nil)

    restrict to a single owning class

Returns:



160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/familia/index_descriptor.rb', line 160

def index_descriptors(cardinality: nil, class_level: nil, owner: nil)
  members.flat_map do |klass|
    next [] unless klass.respond_to?(:indexing_relationships)
    next [] if owner && klass != owner

    klass.indexing_relationships.filter_map do |rel|
      next if cardinality && rel.cardinality != cardinality
      next if !class_level.nil? && rel.class_level? != class_level

      IndexDescriptor.new(owner: klass, relationship: rel)
    end
  end
end

#multi_indexes(class_level: nil, owner: nil) ⇒ Array<Familia::IndexDescriptor>

Multi-value (1:many) index descriptors across the clan.

Parameters:

  • class_level (Boolean, nil) (defaults to: nil)

    class-level (true) vs instance-scoped (false)

  • owner (Class, nil) (defaults to: nil)

    restrict to a single owning class

Returns:



188
189
190
# File 'lib/familia/index_descriptor.rb', line 188

def multi_indexes(class_level: nil, owner: nil)
  index_descriptors(cardinality: :multi, class_level: class_level, owner: owner)
end

#participation_descriptors(owner: nil) ⇒ Array<Array(Class, ParticipationRelationship)>

Participation relationships across the clan, paired with their owners.

Parameters:

  • owner (Class, nil) (defaults to: nil)

    restrict to a single owning class

Returns:

  • (Array<Array(Class, ParticipationRelationship)>)


196
197
198
199
200
201
202
203
# File 'lib/familia/index_descriptor.rb', line 196

def participation_descriptors(owner: nil)
  members.flat_map do |klass|
    next [] unless klass.respond_to?(:participation_relationships)
    next [] if owner && klass != owner

    klass.participation_relationships.map { |rel| [klass, rel] }
  end
end

#stale_indexes(sample: 100, owner: nil) ⇒ Array<Familia::IndexDescriptor>

Class-level unique indexes whose stored data predates the current format (legacy JSON-encoded identifiers) and therefore need a rebuild.

Scoped to query: true indexes: those are the ones with a generated find_by_* that can silently miss on stale data (the failure this guards against), and the only ones with a generated rebuilder. A query: false index has no find_by_*, self-heals on read, and is migrated by re-saving records — so it is intentionally excluded here.

Parameters:

  • sample (Integer) (defaults to: 100)

    raw values sampled per index

  • owner (Class, nil) (defaults to: nil)

    restrict to a single owning class

Returns:



217
218
219
220
221
# File 'lib/familia/index_descriptor.rb', line 217

def stale_indexes(sample: 100, owner: nil)
  unique_indexes(class_level: true, owner: owner)
    .select(&:query?)
    .reject { |idx| idx.format_current?(sample: sample) }
end

#unique_indexes(class_level: nil, owner: nil) ⇒ Array<Familia::IndexDescriptor>

Unique (1:1) index descriptors across the clan.

Parameters:

  • class_level (Boolean, nil) (defaults to: nil)

    class-level (true) vs instance-scoped (false)

  • owner (Class, nil) (defaults to: nil)

    restrict to a single owning class

Returns:



179
180
181
# File 'lib/familia/index_descriptor.rb', line 179

def unique_indexes(class_level: nil, owner: nil)
  index_descriptors(cardinality: :unique, class_level: class_level, owner: owner)
end