Module: Familia::Features::Relationships

Defined in:
lib/familia/features/relationships.rb,
lib/familia/features/relationships/indexing.rb,
lib/familia/features/relationships/participation.rb,
lib/familia/features/relationships/score_encoding.rb,
lib/familia/features/relationships/collection_operations.rb,
lib/familia/features/relationships/indexing_relationship.rb,
lib/familia/features/relationships/participation_membership.rb,
lib/familia/features/relationships/participation_relationship.rb,
lib/familia/features/relationships/indexing/rebuild_strategies.rb,
lib/familia/features/relationships/participation/target_methods.rb,
lib/familia/features/relationships/indexing/multi_index_generators.rb,
lib/familia/features/relationships/participation/staged_operations.rb,
lib/familia/features/relationships/indexing/unique_index_generators.rb,
lib/familia/features/relationships/participation/participant_methods.rb,
lib/familia/features/relationships/participation/through_model_operations.rb

Overview

Unified Relationships feature for Familia v2

This feature merges the functionality of relatable_objects and relationships into a single, Valkey/Redis-native implementation that embraces the "where does this appear?" philosophy rather than "who owns this?".

Examples:

Basic usage

class Domain < Familia::Horreum

  identifier_field :domain_id

  field :domain_id
  field :display_name
  field :created_at
  field :permission_bits

  feature :relationships

  # Multi-presence participation with score encoding
  participates_in Customer, :domains,
                  score: -> { permission_encode(created_at, permission_bits) }
  participates_in Team, :domains, score: :added_at
  participates_in Organization, :all_domains, score: :created_at

  # O(1) lookups with Valkey/Redis hashes
  indexed_by :display_name, :domain_index, target: Customer

  # Participation with bidirectional control (no method collisions)
  participates_in Customer, :domains
  participates_in Team, :domains, generate_participant_methods: false
  participates_in Organization, :domains, type: :set
end

Generated methods (collision-free)

# Participation methods (instance-level on the target)
customer.domains                              # => Familia::SortedSet
customer.add_domains_instance(domain, score)  # Add to customer's domains
domain.in_customer_domains?(customer)         # Check membership

# Indexing methods
Customer.find_by_display_name(name) # O(1) lookup

# Bidirectional methods (collision-free naming)
domain.add_to_customer_domains(customer)  # Specific collection
domain.add_to_team_domains(team)          # Different collection
domain.in_customer_domains?(customer)     # Check specific membership

Score encoding for permissions

# Encode permission in score
score = domain.permission_encode(Familia.now, :write)
# => 1704067200.004 (timestamp + permission bits)

# Decode permission from score
decoded = domain.permission_decode(score)
# => { timestamp: 1704067200, permissions: 4, permission_list: [:write] }

# Query with permission filtering (atomic flags only, never role names)
customer.domains_with_permission(:read)
customer.domains_with_permission(:read, limit: 50, offset: 50)  # bounded page (batch_size: kwarg too)
customer.each_domains_with_permission(:read) { |id| ... }       # ZSCAN stream, O(1) memory;
                                                                # Enumerator without a block

Multi-collection operations

# A participant carries a reverse index of every collection it is in,
# so cross-collection questions are answered from the participant.
domain.participating_ids_for_target(Customer)          # => ["123"]
domain.participating_ids_for_target(Team, ['domains']) # scoped to one collection
domain.participating_in_target?(Customer)              # => true

# Writes to several collections are grouped by the connection layer,
# not by a relationships-specific batch method.
Familia.transaction do
  customer.add_domains_instance(domain, domain.permission_encode(Familia.now, :read))
  team.add_domains_instance(domain, domain.permission_encode(Familia.now, :read))
end

Defined Under Namespace

Modules: CollectionOperations, Indexing, ModelClassMethods, ModelInstanceMethods, ParticipantMethods, Participation, ScoreEncoding, TargetMethods Classes: CascadeError, IndexingRelationship, InvalidIdentifierError, InvalidScoreError, ParticipationMembership, ParticipationRelationship, RelationshipError

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Feature initialization



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/familia/features/relationships.rb', line 92

def self.included(base)
  Familia.debug "[#{base}] Relationships included"
  base.extend ModelClassMethods
  base.include ModelInstanceMethods

  # Include all relationship submodules and their class methods
  base.include ScoreEncoding

  base.include Participation
  base.extend Participation::ModelClassMethods

  base.include Indexing
  base.extend Indexing::ModelClassMethods
end