Migrating Guide: Relationships System (v2.0.0-pre7)

This guide covers adopting the comprehensive relationships system introduced in v2.0.0-pre7.

Relationships System Overview

The v2.0.0-pre7 release introduces three powerful relationship types:

  • tracked_in - Multi-presence tracking with score encoding
  • indexed_by - O(1) hash-based lookups
  • member_of - Bidirectional membership with collision-free naming

Migration Steps

1. Enable Relationships Feature

Add the relationships feature to your models:

class Customer < Familia::Horreum
  feature :relationships  # Required for all relationship types

  identifier_field :custid
  field :custid, :name, :email
end

2. Choose Appropriate Relationship Types

For Tracking Collections (sorted sets):

class Customer < Familia::Horreum
  feature :relationships

  # Global tracking with score-based sorting
  tracked_in :all_customers, type: :sorted_set, score: :created_at

  # Scoped tracking
  tracked_in :active_users, type: :sorted_set, score: :last_seen
end

For Fast Lookups (hash indexes):

class Customer < Familia::Horreum
  feature :relationships

  # Global email index for O(1) lookups
  indexed_by :email, :email_lookup, target: :global

  # Domain-specific indexes
  indexed_by :account_id, :account_lookup, target: Domain
end

For Bidirectional Membership:

class Domain < Familia::Horreum
  feature :relationships

  # Belongs to customer's domains collection
  participates_in Customer, :domains, type: :set
end

3. Implement Permission Systems

For access-controlled relationships:

class Document < Familia::Horreum
  feature :relationships
  include Familia::Features::Relationships::PermissionManagement

  # Permission-aware tracking
  tracked_in Customer, :documents, score: :created_at

  permission_tracking :user_permissions

  def permission_bits
    # Define permission levels (bit-encoded)
    @permission_bits || 1  # Default: read-only
  end
end

4. Update Queries

Before (manual db operations):

# Manual sorted set operations
redis.zadd("all_customers", customer.created_at, customer.custid)
customers = redis.zrange("all_customers", 0, -1)

After (relationship methods):

# Automatic method generation
Customer.add_to_all_customers(customer)
customers = Customer.all_customers.to_a

Relationship Method Patterns

tracked_in Methods

# Class methods generated:
Customer.add_to_all_customers(customer)
Customer.remove_from_all_customers(customer)
Customer.all_customers  # Access sorted set directly

indexed_by Methods

# Class methods generated:
Customer.add_to_email_lookup(customer)
Customer.remove_from_email_lookup(customer)
Customer.email_lookup.get(email)  # O(1) lookup

member_of Methods

# Instance methods generated:
domain.add_to_customer_domains(customer)
domain.remove_from_customer_domains(customer)
domain.in_customer_domains?(customer)

Permission System Migration

Basic Permission Encoding

# Permission levels (bit-encoded)
READ    = 1   # 001
WRITE   = 4   # 100
DELETE  = 32  # 100000
ADMIN   = 128 # 10000000

# Combine permissions
user_permissions = READ | WRITE  # Can read and write
admin_permissions = ADMIN        # All permissions via hierarchy

Time-Based Permission Scores

# Encode timestamp with permission bits
timestamp = Familia.now.to_i
permissions = READ | WRITE
score = Familia::Features::Relationships::ScoreEncoding.encode_score(timestamp, permissions)

# Query by permission level
documents_with_write_access = customer.documents.accessible_items(
  permissions: WRITE,
  collection_key: customer.documents.key
)

Performance Considerations

Relationship Type Selection

Use indexed_by for:

  • Unique field lookups (email, username, ID)
  • Fields that need O(1) access
  • Reference relationships

Use tracked_in for:

  • Time-ordered collections
  • Scored/ranked relationships
  • Permission-based access control

Use member_of for:

  • Simple membership tracking
  • Bidirectional relationships
  • UnsortedSet-based collections

Optimization Tips

Batch Operations:

# Instead of multiple individual operations
customers.each { |c| Customer.add_to_all_customers(c) }

# Use batch operations where available
Customer.all_customers.add_batch(customers.map(&:identifier),
                                 customers.map(&:created_at))

Permission Queries:

# Efficient permission filtering
accessible_docs = document.accessible_items(
  collection_key: customer.documents.key,
  minimum_permissions: READ
)

Breaking Changes

Method Name Changes

  • Relationship methods follow new naming patterns
  • Previous manual db operations need updating
  • Collection access methods standardized

Permission System

  • New bit-encoded permission system
  • Time-based scoring for temporal access
  • Permission hierarchy implementation required

Next Steps

  1. Analyze Existing Relationships: Review current db operations for optimization opportunities
  2. Choose Relationship Types: Select appropriate types based on usage patterns
  3. Implement Permissions: Add access control where needed
  4. Update Queries: Replace manual operations with generated relationship methods
  5. Test Performance: Benchmark relationship operations under load

Resources