Relationship Methods

Here are the methods automatically generated for each relationship type in the new clean API:

member_of Relationships

When you declare: ruby class Domain < Familia::Horreum member_of Customer, :domains end

Generated methods on Domain instances: - add_to_customer_domains(customer) - Add this domain to customer’s domains collection - remove_from_customer_domains(customer) - Remove this domain from customer’s domains collection - in_customer_domains?(customer) - Check if this domain is in customer’s domains collection

Collection « operator support: ruby customer.domains << domain # Clean Ruby-like syntax (equivalent to domain.add_to_customer_domains(customer))

The method names follow the pattern: {action}_to_{lowercase_class_name}_{collection_name}

tracked_in Relationships

Class-Level Tracking (class_tracked_in)

When you declare: ruby class Customer < Familia::Horreum class_tracked_in :all_customers, score: :created_at end

Generated class methods: - Customer.add_to_all_customers(customer) - Add customer to class-level tracking - Customer.remove_from_all_customers(customer) - Remove customer from class-level tracking - Customer.all_customers - Access the sorted set collection directly

Automatic behavior: - Objects are automatically added to class-level tracking collections when saved - No manual calls required for basic tracking

Relationship Tracking (tracked_in with parent class)

When you declare: ruby class User < Familia::Horreum tracked_in Team, :active_users, score: :last_seen end

Generated methods: - Team instance methods for managing the active_users collection - Automatic score calculation based on the provided lambda or field

indexed_by Relationships

The indexed_by method creates Redis hash-based indexes for O(1) field lookups with automatic management.

Class-Level Indexing (class_indexed_by)

When you declare: ruby class Customer < Familia::Horreum class_indexed_by :email, :email_lookup end

Generated methods: - Instance methods: customer.add_to_class_email_lookup, customer.remove_from_class_email_lookup - Class methods: Customer.email_lookup (returns hash), Customer.find_by_email(email)

Automatic behavior: - Objects are automatically added to class-level indexes when saved - Index updates happen transparently on field changes

Redis key pattern: customer:email_lookup

Relationship-Scoped Indexing (indexed_by with parent:)

When you declare: ruby class Domain < Familia::Horreum indexed_by :name, :domain_index, parent: Customer end

Generated class methods on Customer: - Customer#find_by_name(domain_name) - Find domain by name within this customer - Customer#find_all_by_name(domain_names) - Find multiple domains by names

Redis key pattern: domain:domain_index (all stored at class level for consistency)

When to Use Each Context

  • Class-level context (class_indexed_by): Use for system-wide lookups where the field value should be unique across all instances
    • Examples: email addresses, usernames, API keys
  • Relationship context (parent: parameter): Use for relationship-scoped lookups where the field value is unique within a specific context
    • Examples: domain names per customer, project names per team

Complete Example

From the relationships example file, you can see the new clean API in action:

```ruby # Domain declares membership in Customer collections class Domain < Familia::Horreum member_of Customer, :domains class_tracked_in :active_domains, score: -> { status == ‘active’ ? Time.now.to_i : 0 } end

class Customer < Familia::Horreum class_indexed_by :email, :email_lookup class_tracked_in :all_customers, score: :created_at end ```

Usage with automatic behavior: ```ruby # Create and save objects (automatic indexing and tracking) customer = Customer.new(email: “admin@acme.com”, name: “Acme Corp”) customer.save # Automatically added to email_lookup and all_customers

domain = Domain.new(name: “acme.com”, status: “active”) domain.save # Automatically added to active_domains

Clean relationship syntax

customer.domains « domain # Ruby-like collection syntax

Query relationships

domain.in_customer_domains?(customer) # => true customer.domains.member?(domain.identifier) # => true

O(1) lookups with automatic management

found_id = Customer.email_lookup.get(“admin@acme.com”) ```

Method Naming Conventions

The relationship system uses consistent naming patterns: - member_of: {add_to|remove_from|in}_#{parent_class.downcase}_#{collection_name} - class_tracked_in: {add_to|remove_from}_#{collection_name} (class methods) - class_indexed_by: {add_to|remove_from}_class_#{index_name} (instance methods) - indexed_by with parent: {add_to|remove_from}_#{parent_class.downcase}_#{index_name} (instance methods)

Key Benefits

  • Automatic management: Save operations update indexes and tracking automatically
  • Ruby-idiomatic: Use << operator for natural collection syntax
  • Consistent storage: All indexes stored at class level for architectural simplicity
  • Clean API: Removed complex global vs parent conditionals for simpler method generation

Context Parameter Usage Patterns

The context parameter in indexed_by is a fundamental architectural decision that determines index scope and ownership. Here are practical patterns for when to use each approach:

Global Context Pattern

Use class_indexed_by when field values should be unique system-wide:

```ruby class User < Familia::Horreum feature :relationships

identifier_field :user_id field :user_id, :email, :username

# System-wide unique email lookup class_indexed_by :email, :email_lookup class_indexed_by :username, :username_lookup end

Usage:

user.add_to_global_email_lookup found_user_id = User.email_lookup.get(“john@example.com”) ```

Redis keys generated: global:email_lookup, global:username_lookup

Parent Context Pattern

Use parent: SomeClass when field values are unique within a specific parent context:

```ruby class Customer < Familia::Horreum feature :relationships

identifier_field :custid field :custid, :name sorted_set :domains end

class Domain < Familia::Horreum feature :relationships

identifier_field :domain_id field :domain_id, :name, :subdomain

# Domains are unique per customer (customer can’t have duplicate domain names) indexed_by :name, :domain_index, parent: Customer indexed_by :subdomain, :subdomain_index, parent: Customer end

Usage:

customer = Customer.new(custid: “cust_123”) customer.find_by_name(“example.com”) # Find domain within this customer customer.find_all_by_subdomain([“www”, “api”]) # Find multiple subdomains ```

Redis keys generated: customer:cust_123:domain_index, customer:cust_123:subdomain_index

Mixed Pattern Example

A real-world example showing both patterns:

```ruby class ApiKey < Familia::Horreum feature :relationships

identifier_field :key_id field :key_id, :key_hash, :name, :scope

# API key hashes must be globally unique class_indexed_by :key_hash, :global_key_lookup

# But key names can be reused across different customers indexed_by :name, :customer_key_lookup, parent: Customer indexed_by :scope, :scope_lookup, parent: Customer end

Usage examples:

# Global lookup (system-wide unique) ApiKey.key_lookup.get(“sha256:abc123…”)

Scoped lookup (unique per customer)

customer = Customer.new(custid: “cust_456”) customer.find_by_name(“production-api-key”) customer.find_all_by_scope([“read”, “write”]) ```

Migrating Guide

If you have existing code with old syntax, here’s how to update it:

```ruby # ❌ Old syntax (pre-refactoring) indexed_by :email_lookup, field: :email indexed_by :email, :email_lookup, context: :global tracked_in :global, :all_users, score: :created_at

✅ New syntax - Class-level scope

class_indexed_by :email, :email_lookup class_tracked_in :all_users, score: :created_at

✅ New syntax - Relationship scope

indexed_by :email, :customer_email_lookup, parent: Customer tracked_in Customer, :user_activity, score: :last_seen ```

Key Changes: 1. Class-level relationships: Use class_ prefix (class_tracked_in, class_indexed_by) 2. Relationship-scoped: Use parent: parameter instead of :global symbol 3. Automatic management: Objects automatically added to class-level collections on save 4. Clean syntax: Collections support << operator for Ruby-like relationship building 5. Simplified storage: All indexes stored at class level (parent is conceptual only)

Behavioral Changes: - Save operations now automatically update indexes and class-level tracking - No more manual add_to_* calls required for basic functionality - << operator works naturally with all collection types - Method generation simplified without complex global/parent conditionals