Class: Familia::IndexDescriptor

Inherits:
Data
  • Object
show all
Defined in:
lib/familia/index_descriptor.rb

Overview

IndexDescriptor pairs an owning Horreum class with one of its IndexingRelationship configs and exposes behavior that hides the index's method-naming and storage internals — iteration, rebuild, and format checks. The IndexingRelationship itself has no back-reference to its owner, so this wrapper supplies that pairing for project-wide use.

Obtain descriptors via the project-wide aggregators (Familia.unique_indexes, Familia.index_descriptors, ...) rather than constructing them directly.

Examples:

Rebuild every class-level unique index in the app (requires 2.10.1+)

Familia.unique_indexes(class_level: true).each(&:rebuild!)

Iterate the records behind a class-level unique index

idx = Familia.unique_indexes(owner: User).first
idx.each_record { |user| user.notify! }

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#ownerObject (readonly)

Returns the value of attribute owner

Returns:

  • (Object)

    the current value of owner



23
24
25
# File 'lib/familia/index_descriptor.rb', line 23

def owner
  @owner
end

#relationshipObject (readonly)

Returns the value of attribute relationship

Returns:

  • (Object)

    the current value of relationship



23
24
25
# File 'lib/familia/index_descriptor.rb', line 23

def relationship
  @relationship
end

Instance Method Details

#cardinalityObject



28
# File 'lib/familia/index_descriptor.rb', line 28

def cardinality  = relationship.cardinality

#class_level?Boolean

Returns:

  • (Boolean)


32
# File 'lib/familia/index_descriptor.rb', line 32

def class_level? = relationship.class_level?

#coordinateString

Stable "Owner:index_name" coordinate, e.g. "User:email_lookup".

Returns:

  • (String)


39
40
41
# File 'lib/familia/index_descriptor.rb', line 39

def coordinate
  Familia.join(owner.name, index_name)
end

#each_record(value: nil, scope: nil, **opts, &block) ⇒ Enumerator, Object

Iterate the indexed records, resolving the right backing collection so callers never touch +send(index_name)+ or the +_for+ factory:

  • class-level unique -> owner. (HashKey, reference)
  • class-level multi -> owner._for(value) (UnsortedSet) [value:]
  • instance-scoped -> requires scope: (the within instance)

Parameters:

  • value (Object, nil) (defaults to: nil)

    required for multi_index (selects the bucket)

  • scope (Familia::Horreum, nil) (defaults to: nil)

    required for instance-scoped indexes

  • opts (Hash)

    forwarded to the collection's #each_record

Returns:

  • (Enumerator, Object)

    Enumerator without a block, else the collection



54
55
56
# File 'lib/familia/index_descriptor.rb', line 54

def each_record(value: nil, scope: nil, **opts, &block)
  backing(value: value, scope: scope).each_record(**opts, &block)
end

#fieldObject

--- Delegated metadata (read-only views of the IndexingRelationship) ---



26
# File 'lib/familia/index_descriptor.rb', line 26

def field        = relationship.field

#format_current?(**opts) ⇒ Boolean

Convenience inverse of #stale_format?.

Returns:

  • (Boolean)


98
99
100
# File 'lib/familia/index_descriptor.rb', line 98

def format_current?(**opts)
  !stale_format?(**opts)
end

#index_nameObject



27
# File 'lib/familia/index_descriptor.rb', line 27

def index_name   = relationship.index_name

#multi?Boolean

Returns:

  • (Boolean)


34
# File 'lib/familia/index_descriptor.rb', line 34

def multi?       = cardinality == :multi

#query?Boolean

Returns:

  • (Boolean)


31
# File 'lib/familia/index_descriptor.rb', line 31

def query?       = relationship.query

#rebuild!(scope: nil, **opts) ⇒ Integer

Rebuild this index from the source of truth, delegating to the generated +rebuild_+ method. Class-level indexes rebuild on the owner; instance-scoped indexes require the scope instance.

Parameters:

  • scope (Familia::Horreum, nil) (defaults to: nil)

    required for instance-scoped indexes

  • opts (Hash)

    forwarded to the generated rebuilder (e.g. batch_size:)

Returns:

  • (Integer)

    count of indexed records

Raises:

  • (Familia::Problem)

    for instance-scoped indexes called without a scope, or for query: false indexes (which generate no rebuilder)



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/familia/index_descriptor.rb', line 67

def rebuild!(scope: nil, **opts)
  target = resolve_target(scope)
  rebuilder = :"rebuild_#{index_name}"
  unless target.respond_to?(rebuilder)
    raise Familia::Problem,
          "#{coordinate} has no generated rebuilder (declared query: false). " \
          'Migrate it by re-saving the affected records, or declare it query: true.'
  end

  target.public_send(rebuilder, **opts)
end

#scope_classObject



30
# File 'lib/familia/index_descriptor.rb', line 30

def scope_class  = relationship.scope_class

#stale_format?(sample: 100) ⇒ Boolean

Whether the index's stored data predates the current (raw) storage format — i.e. still holds legacy JSON-encoded identifiers from pre-2.10.0 writes. Samples raw values without deserializing, so no read-time warnings fire.

Only class-level unique indexes have a single backing key that can be sampled without a value/scope; multi and instance-scoped indexes return false here (check them per-bucket / per-scope with #each_record).

Parameters:

  • sample (Integer) (defaults to: 100)

    number of raw values to sample

Returns:

  • (Boolean)


89
90
91
92
93
# File 'lib/familia/index_descriptor.rb', line 89

def stale_format?(sample: 100)
  return false unless class_level? && unique?

  sample_raw_values(sample).any? { |v| Familia.legacy_json_encoded?(v) }
end

#unique?Boolean

Returns:

  • (Boolean)


33
# File 'lib/familia/index_descriptor.rb', line 33

def unique?      = cardinality == :unique

#withinObject



29
# File 'lib/familia/index_descriptor.rb', line 29

def within       = relationship.within