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) └── position_in_customer_domains(customer) # Get my position (list only)

Note: To update scores, use the DataType API directly: customer.domains.add(domain.identifier, new_score, xx: true)

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)



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

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)



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
103
104
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 73

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)



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

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)



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 148

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)



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

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)

Note: Score updates use DataType API directly: customer.domains.add(domain.identifier, new_score, xx: true)



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/familia/features/relationships/participation/participant_methods.rb', line 134

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