Module: Familia::Features::Relationships::ParticipantMethods::Builder

Extended by:
CollectionOperations
Defined in:
lib/familia/features/relationships/participation/participant_methods.rb

Overview

Visual Guide for methods added to PARTICIPANT instances:

When Domain calls: participates_in Customer, :domains

Domain instances (PARTICIPANT) get these methods: ├── in_customer_domains?(customer) # Check if I'm in this customer's domains ├── add_to_customer_domains(customer, score) # Add myself to customer's domains ├── remove_from_customer_domains(customer) # Remove myself from customer's domains ├── score_in_customer_domains(customer) # Get my score (sorted_set only) ├── update_score_in_customer_domains(customer) # Update my score (sorted_set only) └── position_in_customer_domains(customer) # Get my position (list only)

Class Method Summary collapse

Methods included from CollectionOperations

add_to_collection, bulk_add_to_collection, ensure_collection_field, member_of_collection?, remove_from_collection

Class Method Details

.build(participant_class, target_class_name, collection_name, type) ⇒ Object

Build all participant methods for a participation relationship

Parameters:

  • participant_class (Class)

    The class receiving these methods (e.g., Domain)

  • target_class_name (String)

    Name of the target class (e.g., "Customer")

  • collection_name (Symbol)

    Name of the collection (e.g., :domains)

  • type (Symbol)

    Collection type (:sorted_set, :set, :list)



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 37

def self.build(participant_class, target_class_name, collection_name, type)
  # Convert to snake_case once for consistency (target_class_name is PascalCase)
  target_name = target_class_name.to_s.snake_case

  # Core participant methods
  build_membership_check(participant_class, target_name, collection_name, type)
  build_add_to_target(participant_class, target_name, collection_name, type)
  build_remove_from_target(participant_class, target_name, collection_name, type)

  # Type-specific methods
  case type
  when :sorted_set
    build_score_methods(participant_class, target_name, collection_name)
  when :list
    build_position_method(participant_class, target_name, collection_name)
  end
end

.build_add_to_target(participant_class, target_name, collection_name, type) ⇒ Object

Build method to add self to target's collection Creates: domain.add_to_customer_domains(customer, score)



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 71

def self.build_add_to_target(participant_class, target_name, collection_name, type)
  method_name = "add_to_#{target_name}_#{collection_name}"

  participant_class.define_method(method_name) do |target_instance, score = nil|
    return unless target_instance&.identifier

    # Use Horreum's DataType accessor instead of manual creation
    collection = target_instance.send(collection_name)

    # Calculate score if needed and not provided
    if type == :sorted_set && score.nil?
      score = calculate_participation_score(target_instance.class, collection_name)
    end

    # Use transaction for atomicity between collection add and reverse index tracking
    # All operations use Horreum's DataType methods (not direct Redis calls)
    target_instance.transaction do |_tx|
      # Add to collection using DataType method (ZADD/SADD/RPUSH)
      ParticipantMethods::Builder.add_to_collection(
        collection,
        self,
        score: score,
        type: type,
        target_class: target_instance.class,
        collection_name: collection_name,
      )

      # Track participation for efficient cleanup using DataType method (SADD)
      track_participation_in(collection.dbkey) if respond_to?(:track_participation_in)
    end
  end
end

.build_membership_check(participant_class, target_name, collection_name, _type) ⇒ Object

Build method to check membership in target's collection Creates: domain.in_customer_domains?(customer)



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 57

def self.build_membership_check(participant_class, target_name, collection_name, _type)
  method_name = "in_#{target_name}_#{collection_name}?"

  participant_class.define_method(method_name) do |target_instance|
    return false unless target_instance&.identifier

    # Use Horreum's DataType accessor instead of manual creation
    collection = target_instance.send(collection_name)
    ParticipantMethods::Builder.member_of_collection?(collection, self)
  end
end

.build_position_method(participant_class, target_name, collection_name) ⇒ Object

Build position method for lists Creates: domain.position_in_customer_domains(customer)



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 144

def self.build_position_method(participant_class, target_name, collection_name)
  method_name = "position_in_#{target_name}_#{collection_name}"

  participant_class.define_method(method_name) do |target_instance|
    return nil unless target_instance&.identifier

    # Use Horreum's DataType accessor instead of manual key construction
    collection = target_instance.send(collection_name)
    # Use DataType method to find position (index in list)
    collection.to_a.index(identifier)
  end
end

.build_remove_from_target(participant_class, target_name, collection_name, type) ⇒ Object

Build method to remove self from target's collection Creates: domain.remove_from_customer_domains(customer)



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 106

def self.build_remove_from_target(participant_class, target_name, collection_name, type)
  method_name = "remove_from_#{target_name}_#{collection_name}"

  participant_class.define_method(method_name) do |target_instance|
    return unless target_instance&.identifier

    # Use Horreum's DataType accessor instead of manual creation
    collection = target_instance.send(collection_name)

    # Use transaction for atomicity between collection remove and reverse index untracking
    # All operations use Horreum's DataType methods (not direct Redis calls)
    target_instance.transaction do |_tx|
      # Remove from collection using DataType method (ZREM/SREM/LREM)
      ParticipantMethods::Builder.remove_from_collection(collection, self, type: type)

      # Remove from participation tracking using DataType method (SREM)
      untrack_participation_in(collection.dbkey) if respond_to?(:untrack_participation_in)
    end
  end
end

.build_score_methods(participant_class, target_name, collection_name) ⇒ Object

Build score-related methods for sorted sets Creates: domain.score_in_customer_domains(customer) domain.update_score_in_customer_domains(customer, new_score)



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 130

def self.build_score_methods(participant_class, target_name, collection_name)
  # Get score method
  score_method = "score_in_#{target_name}_#{collection_name}"
  participant_class.define_method(score_method) do |target_instance|
    return nil unless target_instance&.identifier

    # Use Horreum's DataType accessor instead of manual key construction
    collection = target_instance.send(collection_name)
    collection.score(identifier)
  end
end