Relationship Methods Reference

Complete reference for methods generated by Familia's relationships feature. Each relationship declaration creates predictable, consistently-named methods on both participating classes.

Quick Reference

Basic Participation

When Domain declares participates_in Customer, :domains:

# Target class (Customer) gets:
customer.domains                          # Collection accessor
customer.add_domains_instance(domain)     # Add single
customer.add_domains([d1, d2])           # Add multiple
customer.remove_domains_instance(domain)  # Remove

# Participant class (Domain) gets:
domain.add_to_customer_domains(customer)     # Add self
domain.remove_from_customer_domains(customer) # Remove self
domain.in_customer_domains?(customer)        # Check membership
domain.score_in_customer_domains(customer)   # Get score (sorted_set)

# Reverse collection methods (Domain):
domain.customer_instances    # Load Customer objects
domain.customer_ids          # Just IDs
domain.customer?            # Has any?
domain.customer_count       # Count

Participation Methods

Target Class Methods

The target class (specified in participates_in) receives collection management methods.

class Domain < Familia::Horreum
  participates_in Customer, :domains, score: :priority
end
Method Description Example
domains Collection accessor customer.domains.size
add_domains_instance(item, score) Add single item customer.add_domains_instance(domain, 100)
add_domains([items]) Bulk add customer.add_domains([d1, d2, d3])
remove_domains_instance(item) Remove item customer.remove_domains_instance(domain)

Participant Class Methods

The participant class (calling participates_in) receives membership management methods.

Method Description Example
add_to_customer_domains(target, score) Add to target's collection domain.add_to_customer_domains(customer)
remove_from_customer_domains(target) Remove from target's collection domain.remove_from_customer_domains(customer)
in_customer_domains?(target) Check membership domain.in_customer_domains?(customer)
score_in_customer_domains(target) Get score (sorted_set only) domain.score_in_customer_domains(customer)
position_in_customer_domains(target) Get position (list only) domain.position_in_customer_domains(customer)

Reverse Collection Methods

Participants also get methods to query all relationships of a given type.

Method Description Returns
customer_instances Load all related objects Array<Customer>
customer_ids Get all related IDs Array<String>
customer? Check if has any relationships Boolean
customer_count Count relationships Integer

Class-Level Participation

Declaration

class User < Familia::Horreum
  class_participates_in :all_users, score: :created_at
  class_participates_in :active_users,
    score: ->(u) { u.active? ? u.last_activity : 0 }
end

Generated Methods

Level Method Description
Class User.all_users Collection accessor
User.add_to_all_users(user, score) Manual add
User.remove_from_all_users(user) Manual remove
Instance user.add_to_class_all_users(score) Add self
user.remove_from_class_all_users Remove self
user.in_class_all_users? Check membership
user.score_in_class_all_users Get score

Indexing Methods

Class-Level Unique Index

class User < Familia::Horreum
  unique_index :email, :email_lookup
  unique_index :api_key, :api_key_lookup, query: false
end
Method Generated When Description
User.find_by_email(email) query: true (default) O(1) lookup
User.index_email_for(user) Always Manual index
User.unindex_email_for(user) Always Remove from index
User.reindex_email_for(user) Always Update index

Instance-Scoped Unique Index

class Employee < Familia::Horreum
  unique_index :badge_number, :badge_index, within: Company
end

Methods on scope class (Company): | Method | Description | |--------|-------------| | company.find_by_badge_number(badge) | Find employee | | company.index_badge_number_for(employee) | Add to index | | company.unindex_badge_number_for(employee) | Remove from index |

Methods on indexed class (Employee): | Method | Description | |--------|-------------| | employee.add_to_company_badge_index(company) | Add to company's index | | employee.remove_from_company_badge_index(company) | Remove from index | | employee.in_company_badge_index?(company) | Check if indexed |

Class-Level Multi-Value Index

class Customer < Familia::Horreum
  multi_index :role, :role_index  # within: :class is the default
end

Class methods on indexed class (Customer): | Method | Description | |--------|-------------| | Customer.role_index_for(value) | Factory returning UnsortedSet for value | | Customer.find_all_by_role(role) | Find all with that role | | Customer.sample_from_role(role, count) | Random sample | | Customer.rebuild_role_index | Rebuild index from instances |

Instance methods on indexed class (Customer): | Method | Description | |--------|-------------| | customer.add_to_class_role_index | Add to index | | customer.remove_from_class_role_index | Remove from index | | customer.update_in_class_role_index(old_value) | Move between indexes |

Instance-Scoped Multi-Value Index

class Employee < Familia::Horreum
  multi_index :department, :dept_index, within: Company
end

Methods on scope class (Company): | Method | Description | |--------|-------------| | company.dept_index_for(value) | Factory returning UnsortedSet for value | | company.find_all_by_department(dept) | Find all in department | | company.sample_from_department(dept, count) | Random sample | | company.rebuild_dept_index | Rebuild index from participation |

Methods on indexed class (Employee): | Method | Description | |--------|-------------| | employee.add_to_company_dept_index(company) | Add to index | | employee.remove_from_company_dept_index(company) | Remove from index | | employee.update_in_company_dept_index(company, old_dept) | Move between indexes |

Method Naming Patterns

Participation

  • Target methods: {collection}, add_{collection}_instance, remove_{collection}_instance
  • Participant methods: {action}_to_{target_config_name}_{collection}
  • Reverse methods: {target_config_name}_instances, {target_config_name}_ids

Indexing

  • Class unique: find_by_{field}, index_{field}_for, unindex_{field}_for
  • Class multi: {Class}.find_all_by_{field}, {Class}.sample_from_{field}, {item}.add_to_class_{index}
  • Scoped unique: {scope}.find_by_{field}, {item}.add_to_{scope}_{index}
  • Scoped multi: {scope}.find_all_by_{field}, {scope}.sample_from_{field}, {item}.add_to_{scope}_{index}

Common Usage Examples

Establishing Relationships

# Single addition with auto-calculated score
customer.add_domains_instance(domain)

# Single addition with explicit score
customer.add_domains_instance(domain, 100.0)

# Bulk addition
customer.add_domains([domain1, domain2, domain3])

# From participant side
domain.add_to_customer_domains(customer)

Querying Relationships

# Check membership
domain.in_customer_domains?(customer)  # => true/false

# Get all relationships
domain.customer_instances  # => [customer1, customer2]
domain.customer_ids        # => ["cust_123", "cust_456"]
domain.customer_count      # => 2

# Direct collection access
customer.domains.size      # Count
customer.domains.to_a      # All IDs
customer.domains.range(0, 9)  # First 10

# Iterate as loaded records (batched via load_multi, ghosts filtered)
customer.domains.each_record { |domain| domain.refresh! }

Working with Indexes

# Automatic class-level unique indexing
user = User.create(email: 'alice@example.com')
User.find_by_email('alice@example.com')  # => user

# Class-level multi-value indexing
customer.add_to_class_role_index
Customer.find_all_by_role('admin')       # => [customer, ...]
Customer.sample_from_role('admin', 2)    # => random sample

# Manual scoped unique indexing
employee.add_to_company_badge_index(company)
company.find_by_badge_number('12345')    # => employee

# Scoped multi-value indexing
employee.add_to_company_dept_index(company)
engineers = company.find_all_by_department('engineering')

Introspection Methods

Unlike the methods above, these are always present on any class with feature :relationships (and its instances) — they are not generated per declaration. They report what is declared and what an object currently belongs to.

Class-Level (configuration)

Method Returns Description
indexing_relationships Array<IndexingRelationship> All unique_index / multi_index declarations; .cardinality distinguishes :unique from :multi
participation_relationships Array<ParticipationRelationship> All participates_in / class_participates_in declarations

Instance-Level (current state)

Method Returns Description
current_indexings Array<Hash> Indexes this object currently appears in
indexed_in?(:index_name) Boolean Whether the object is in the named class-level index
current_participations Array<Hash> Participation collections this object belongs to
relationship_status Hash { identifier:, current_participations:, index_memberships: }

Project-Wide (every class)

Method Returns Description
Familia.index_descriptors(cardinality:, class_level:, owner:) Array<IndexDescriptor> Every index across the clan, filterable
Familia.unique_indexes / Familia.multi_indexes Array<IndexDescriptor> Cardinality-filtered convenience helpers
Familia.participation_descriptors(owner:) Array<[Class, ParticipationRelationship]> Every participation, paired with its owner
Familia.stale_indexes(sample:, owner:) Array<IndexDescriptor> Class-level unique indexes holding pre-2.10.0 data
Familia.assert_indexes_current!(on_stale:, owner:) Boolean Boot guard: raise/warn if any index is stale

Each IndexDescriptor exposes the relationship metadata plus coordinate, each_record(value:, scope:), rebuild!(scope:), and stale_format?. For the full breakdown and the audit/repair layer, see Introspection.

See Also