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

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')

See Also