Module: Familia::Features::Relationships::TargetMethods::Builder

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

Overview

Visual Guide for methods added to TARGET instances:

When Domain calls: participates_in Customer, :domains

Customer instances (TARGET) get these methods: ├── domains # Get the domains collection ├── add_domain(domain, score) # Add a domain to my collection ├── remove_domain(domain) # Remove a domain from my collection ├── add_domains([...]) # Bulk add domains └── domains_with_permission(level) # Query with score filtering (sorted_set 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(target_class, collection_name, type) ⇒ Object

Build all target methods for a participation relationship

Parameters:

  • target_class (Class)

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

  • collection_name (Symbol)

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

  • type (Symbol)

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



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 35

def self.build(target_class, collection_name, type)
  # FIRST: Ensure the DataType field is defined on the target class
  TargetMethods::Builder.ensure_collection_field(target_class, collection_name, type)

  # Core target methods
  build_collection_getter(target_class, collection_name, type)
  build_add_item(target_class, collection_name, type)
  build_remove_item(target_class, collection_name, type)
  build_bulk_add(target_class, collection_name, type)

  # Type-specific methods
  return unless type == :sorted_set

  build_permission_query(target_class, collection_name)
end

.build_add_item(target_class, collection_name, type) ⇒ Object

Build method to add an item to the collection Creates: customer.add_domain(domain, score)



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
105
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 76

def self.build_add_item(target_class, collection_name, type)
  singular_name = collection_name.to_s.singularize
  method_name = "add_#{singular_name}"

  target_class.define_method(method_name) do |item, score = nil|
    collection = send(collection_name)

    # Calculate score if needed and not provided
    if type == :sorted_set && score.nil? && item.respond_to?(:calculate_participation_score)
      score = item.calculate_participation_score(self.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)
    transaction do |_tx|
      # Add to collection using DataType method (ZADD/SADD/RPUSH)
      TargetMethods::Builder.add_to_collection(
        collection,
        item,
        score: score,
        type: type,
        target_class: self.class,
        collection_name: collection_name,
      )

      # Track participation in reverse index using DataType method (SADD)
      item.track_participation_in(collection.dbkey) if item.respond_to?(:track_participation_in)
    end
  end
end

.build_bulk_add(target_class, collection_name, type) ⇒ Object

Build method for bulk adding items Creates: customer.add_domains([domain1, domain2, ...])



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 130

def self.build_bulk_add(target_class, collection_name, type)
  method_name = "add_#{collection_name}"

  target_class.define_method(method_name) do |items|
    return if items.empty?

    collection = send(collection_name)

    # Use transaction for atomicity across all bulk additions and reverse index tracking
    # All operations use Horreum's DataType methods (not direct Redis calls)
    transaction do |_tx|
      # Bulk add to collection using DataType methods (multiple ZADD/SADD/RPUSH)
      TargetMethods::Builder.bulk_add_to_collection(collection, items, type: type, target_class: self.class,
collection_name: collection_name)

      # Track all participations using DataType methods (multiple SADD)
      items.each do |item|
        item.track_participation_in(collection.dbkey) if item.respond_to?(:track_participation_in)
      end
    end
  end
end

.build_class_add_method(target_class, collection_name, type) ⇒ Object

Build class-level add method Creates: User.add_to_all_users(user, score)



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 183

def self.build_class_add_method(target_class, collection_name, type)
  method_name = "add_to_#{collection_name}"

  target_class.define_singleton_method(method_name) do |item, score = nil|
    collection = send(collection_name.to_s)

    # Calculate score if needed
    if type == :sorted_set && score.nil?
      score = if item.respond_to?(:calculate_participation_score)
        item.calculate_participation_score('class', collection_name)
      elsif item.respond_to?(:current_score)
        item.current_score
      else
        Familia.now.to_f
      end
    end

    TargetMethods::Builder.add_to_collection(
      collection,
      item,
      score: score,
      type: type,
      target_class: self.class,
      collection_name: collection_name,
    )
  end
end

.build_class_collection_getter(target_class, collection_name, type) ⇒ Object

Build class-level collection getter Creates: User.all_users (class method)



174
175
176
177
178
179
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 174

def self.build_class_collection_getter(target_class, collection_name, type)
  # No need to define the method - Horreum automatically creates it
  # when we call class_#{type} above. This method is kept for
  # backwards compatibility but now does nothing.
  # The field definition (class_sorted_set :all_users) creates the accessor automatically.
end

.build_class_level(target_class, collection_name, type) ⇒ Object

Build class-level collection methods (for class_participates_in)

Parameters:

  • target_class (Class)

    The class receiving these methods

  • collection_name (Symbol)

    Name of the collection

  • type (Symbol)

    Collection type



55
56
57
58
59
60
61
62
63
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 55

def self.build_class_level(target_class, collection_name, type)
  # FIRST: Ensure the class-level DataType field is defined
  target_class.send("class_#{type}", collection_name)

  # Class-level collection getter (e.g., User.all_users)
  build_class_collection_getter(target_class, collection_name, type)
  build_class_add_method(target_class, collection_name, type)
  build_class_remove_method(target_class, collection_name)
end

.build_class_remove_method(target_class, collection_name) ⇒ Object

Build class-level remove method Creates: User.remove_from_all_users(user)



213
214
215
216
217
218
219
220
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 213

def self.build_class_remove_method(target_class, collection_name)
  method_name = "remove_from_#{collection_name}"

  target_class.define_singleton_method(method_name) do |item|
    collection = send(collection_name.to_s)
    TargetMethods::Builder.remove_from_collection(collection, item)
  end
end

.build_collection_getter(target_class, collection_name, type) ⇒ Object

Build method to get the collection Creates: customer.domains



67
68
69
70
71
72
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 67

def self.build_collection_getter(target_class, collection_name, type)
  # No need to define the method - Horreum automatically creates it
  # when we call ensure_collection_field above. This method is
  # kept for backwards compatibility but now does nothing.
  # The field definition (sorted_set :domains) creates the accessor automatically.
end

.build_permission_query(target_class, collection_name) ⇒ Object

Build permission query for sorted sets Creates: customer.domains_with_permission(min_level)



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/familia/features/relationships/participation/target_methods.rb', line 155

def self.build_permission_query(target_class, collection_name)
  method_name = "#{collection_name}_with_permission"

  target_class.define_method(method_name) do |min_permission = :read|
    collection = send(collection_name)

    # Assumes ScoreEncoding module is available
    if defined?(ScoreEncoding)
      permission_score = ScoreEncoding.permission_encode(0, min_permission)
      collection.zrangebyscore(permission_score, '+inf', with_scores: true)
    else
      # Fallback to all members if ScoreEncoding not available
      collection.members(with_scores: true)
    end
  end
end

.build_remove_item(target_class, collection_name, type) ⇒ Object

Build method to remove an item from the collection Creates: customer.remove_domain(domain)



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

def self.build_remove_item(target_class, collection_name, type)
  singular_name = collection_name.to_s.singularize
  method_name = "remove_#{singular_name}"

  target_class.define_method(method_name) do |item|
    collection = 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)
    transaction do |_tx|
      # Remove from collection using DataType method (ZREM/SREM/LREM)
      TargetMethods::Builder.remove_from_collection(collection, item, type: type)

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