Module: Familia::Features::Relationships::Participation::ModelClassMethods

Defined in:
lib/familia/features/relationships/participation.rb

Overview

Class methods for defining participation relationships.

These methods are available on any class that includes the Participation module, allowing definition of both instance-level and class-level participation relationships.

Instance Method Summary collapse

Instance Method Details

#class_participates_in(collection_name, score: nil, type: :sorted_set, bidirectional: true) ⇒ Object

Define a class-level participation collection where all instances automatically participate.

Class-level participation creates a global collection containing all instances of the class, with automatic management of membership based on object lifecycle events. This is useful for maintaining global indexes, leaderboards, or categorical groupings.

The collection is created at the class level (e.g., User.all_users) rather than on individual instances, providing a centralized view of all objects matching the criteria.

=== Generated Methods

==== On the Class (Target Methods)

  • +ClassName.collection_name+ - Access the collection DataType
  • +ClassName.add_to_collection_name(instance)+ - Add instance to collection
  • +ClassName.remove_from_collection_name(instance)+ - Remove instance from collection

==== On Instances (Participant Methods, if bidirectional)

  • +instance.in_class_collection_name?+ - Check membership in class collection
  • +instance.add_to_class_collection_name+ - Add self to class collection
  • +instance.remove_from_class_collection_name+ - Remove self from class collection

Examples:

Simple priority-based global collection

class User < Familia::Horreum
  field :priority_level
  class_participates_in :all_users, score: :priority_level
end

User.all_users.first        # Highest priority user
user.in_class_all_users?    # true if user is in collection

Dynamic scoring based on status

class Customer < Familia::Horreum
  field :status
  field :last_purchase

  class_participates_in :active_customers, score: -> {
    status == 'active' ? last_purchase.to_i : 0
  }
end

Customer.active_customers.to_a  # All active customers, sorted by last purchase

Parameters:

  • collection_name (Symbol)

    Name of the class-level collection (e.g., +:all_users+, +:active_members+)

  • score (Symbol, Proc, Numeric, nil) (defaults to: nil)

    Scoring strategy for sorted collections:

    • +Symbol+: Field name or method name (e.g., +:priority_level+, +:created_at+)
    • +Proc+: Dynamic calculation in instance context (e.g., +-> { status == 'premium' ? 100 : 0 }+)
    • +Numeric+: Static score for all instances (e.g., +50.0+)
    • +nil+: Use +current_score+ method fallback
    • +:remove+: Remove from collection on destruction (default)
    • +:ignore+: Leave in collection when destroyed
  • type (Symbol) (defaults to: :sorted_set)

    Valkey/Redis collection type:

    • +:sorted_set+: Ordered by score (default)
    • +:set+: Unordered unique membership
    • +:list+: Ordered sequence allowing duplicates
  • bidirectional (Boolean) (defaults to: true)

    Whether to generate convenience methods on instances (default: +true+)

See Also:

Since Version:

  • 1.0.0



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/familia/features/relationships/participation.rb', line 159

def class_participates_in(collection_name, score: nil,
                          type: :sorted_set, bidirectional: true)
  # Store metadata for this participation relationship
  participation_relationships << ParticipationRelationship.new(
    target_class: self,
    collection_name: collection_name,
    score: score,

    type: type,
    bidirectional: bidirectional,
  )

  # STEP 1: Add collection management methods to the class itself
  # e.g., User.all_users, User.add_to_all_users(user)
  TargetMethods::Builder.build_class_level(self, collection_name, type)

  # STEP 2: Add participation methods to instances (if bidirectional)
  # e.g., user.in_class_all_users?, user.add_to_class_all_users
  return unless bidirectional

  ParticipantMethods::Builder.build(self, 'class', collection_name, type)
end

#participates_in(target_class, collection_name, score: nil, type: :sorted_set, bidirectional: true) ⇒ Object

Define an instance-level participation relationship between two classes.

This method creates a bidirectional relationship where instances of the calling class (participants) can join collections owned by instances of the target class. This enables flexible multi-membership scenarios where objects can belong to multiple collections simultaneously with different scoring and management strategies.

The relationship automatically handles reverse index tracking, allowing efficient lookup of all collections a participant belongs to via the +current_participations+ method.

=== Generated Methods

==== On Target Class (Collection Owner)

  • +target.collection_name+ - Access the collection DataType
  • +target.add_participant_class_name(participant)+ - Add participant to collection
  • +target.remove_participant_class_name(participant)+ - Remove participant from collection
  • +target.add_participant_class_names([participants])+ - Bulk add multiple participants

==== On Participant Class (if bidirectional)

  • +participant.in_target_collection_name?(target)+ - Check membership in target's collection
  • +participant.add_to_target_collection_name(target)+ - Add self to target's collection
  • +participant.remove_from_target_collection_name(target)+ - Remove self from target's collection

=== Reverse Index Tracking

Automatically creates a +:participations+ set field on the participant class to track all collections the instance belongs to. This enables efficient membership queries and cleanup operations without scanning all possible collections.

Examples:

Basic domain-customer relationship

class Domain < Familia::Horreum
  field :name
  field :created_at

  participates_in Customer, :domains, score: :created_at
end

# Usage:
domain.add_to_customer_domains(customer)  # Add domain to customer's collection
customer.domains.first                    # Most recent domain
domain.in_customer_domains?(customer)     # true
domain.current_participations             # All collections domain belongs to

Multi-collection participation with different types

class Employee < Familia::Horreum
  field :hire_date
  field :skill_level

  # Sorted by hire date in department
  participates_in Department, :members, score: :hire_date

  # Simple set membership in teams
  participates_in Team, :contributors, score: :skill_level, type: :set

  # Complex scoring for project assignments
  participates_in Project, :assignees, score: -> {
    base_score = skill_level * 100
    seniority = (Time.now - hire_date) / 1.year
    base_score + seniority * 10
  }
end

# Employee can belong to department, multiple teams, and projects
employee.add_to_department_members(engineering_dept)
employee.add_to_team_contributors(frontend_team)
employee.add_to_project_assignees(mobile_app_project)

Parameters:

  • target_class (Class, Symbol, String)

    The class that owns the collection. Can be:

    • +Class+ object (e.g., +Customer+)
    • +Symbol+ referencing class name (e.g., +:customer+, +:Customer+)
    • +String+ class name (e.g., +"Customer"+)
  • collection_name (Symbol)

    Name of the collection on the target class (e.g., +:domains+, +:members+)

  • score (Symbol, Proc, Numeric, nil) (defaults to: nil)

    Scoring strategy for sorted collections:

    • +Symbol+: Field name or method name (e.g., +:priority+, +:created_at+)
    • +Proc+: Dynamic calculation executed in participant instance context
    • +Numeric+: Static score applied to all participants
    • +nil+: Use +current_score+ method as fallback
    • +:remove+: Remove from all collections on destruction (default)
    • +:ignore+: Leave in collections when destroyed
  • type (Symbol) (defaults to: :sorted_set)

    Valkey/Redis collection type:

    • +:sorted_set+: Ordered by score, allows duplicates with different scores (default)
    • +:set+: Unordered unique membership
    • +:list+: Ordered sequence, allows duplicates
  • bidirectional (Boolean) (defaults to: true)

    Whether to generate convenience methods on participant class (default: +true+)

See Also:

Since Version:

  • 1.0.0



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/familia/features/relationships/participation.rb', line 271

def participates_in(target_class, collection_name, score: nil,
                    type: :sorted_set, bidirectional: true)
  # Handle class target using Familia.resolve_class
  resolved_class = Familia.resolve_class(target_class)

  # Store metadata for this participation relationship
  participation_relationships << ParticipationRelationship.new(
    target_class: target_class, # as passed to `participates_in`
    collection_name: collection_name,
    score: score,

    type: type,
    bidirectional: bidirectional,
  )

  # Resolve target class if it's a symbol/string
  actual_target_class = if target_class.is_a?(Class)
    target_class
  else
    Familia.member_by_config_name(target_class)
  end

  # STEP 0: Add participations tracking field to PARTICIPANT class (Domain)
  # This creates the proper key: "domain:123:participations" (not "domain:123:object:participations")
  set :participations unless method_defined?(:participations)

  # STEP 1: Add collection management methods to TARGET class (Customer)
  # Customer gets: domains, add_domain, remove_domain, etc.
  TargetMethods::Builder.build(actual_target_class, collection_name, type)

  # STEP 2: Add participation methods to PARTICIPANT class (Domain) - only if bidirectional
  # Domain gets: in_customer_domains?, add_to_customer_domains, etc.
  return unless bidirectional

  ParticipantMethods::Builder.build(self, resolved_class.familia_name, collection_name, type)
end

#participation_relationshipsArray<ParticipationRelationship>

Get all participation relationships defined for this class.

Returns an array of ParticipationRelationship objects containing metadata about each participation relationship, including target class, collection name, scoring strategy, and configuration options.

Returns:

Since Version:

  • 1.0.0



316
317
318
# File 'lib/familia/features/relationships/participation.rb', line 316

def participation_relationships
  @participation_relationships ||= []
end