127 lines
4.2 KiB
Ruby
127 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Generates potential matches when the matching settings restrict who can be
|
|
# matched with whom. Uses PromptBatches to speed up matching. Does not generate
|
|
# assignments.
|
|
#
|
|
# The runtime is asymptotically quadratic in the number of signups, but the
|
|
# batching keeps the constants relatively low.
|
|
class PotentialMatcherConstrained
|
|
attr_reader :collection, :settings, :batch_size
|
|
attr_reader :index_tag_type, :index_optional
|
|
|
|
def initialize(collection, index_tag_type = nil, batch_size = 100)
|
|
@collection = collection
|
|
@settings = collection.challenge.potential_match_settings
|
|
@batch_size = batch_size
|
|
|
|
@required_types = @settings.required_types
|
|
@index_tag_type = index_tag_type || @required_types.first
|
|
@index_optional = @settings.include_optional?(@index_tag_type)
|
|
|
|
# Set up a new progress object for recording our progress.
|
|
@progress = PotentialMatcherProgress.new(collection)
|
|
|
|
# Set up a structure for holding multiple PotentialMatchBuilders.
|
|
@builders = {}
|
|
end
|
|
|
|
private
|
|
|
|
# Makes a batch for the given set of signups.
|
|
# Passes @index_tag_type and @index_optional to the constructor, so that the
|
|
# prompt batch knows how to build its indices properly.
|
|
def make_batch(signups, prompt_type)
|
|
PromptBatch.new(signups, prompt_type, @index_tag_type, @index_optional)
|
|
end
|
|
|
|
# Try matching the two prompts.
|
|
def try_match_prompts(request, offer)
|
|
return unless request.matches?(offer, @settings)
|
|
|
|
request_signup = request.challenge_signup
|
|
offer_signup = offer.challenge_signup
|
|
pair_key = "#{request_signup.id}|#{offer_signup.id}"
|
|
|
|
@builders[pair_key] ||= PotentialMatchBuilder.new(
|
|
request_signup, offer_signup, @settings
|
|
)
|
|
|
|
# We've already checked that the request matches the offer, so we can use
|
|
# add_prompt_match instead of try_prompt_match (to avoid duplicating work).
|
|
@builders[pair_key].add_prompt_match(request, offer)
|
|
end
|
|
|
|
# Builds and saves all PotentialMatches using @builders, then clears out the
|
|
# @builders table for the next batch.
|
|
def save_potential_matches
|
|
return if @builders.empty?
|
|
|
|
PotentialMatch.transaction do
|
|
@builders.each_value do |builder|
|
|
match = builder.build_potential_match
|
|
match.save unless match.nil?
|
|
end
|
|
end
|
|
|
|
@builders.clear
|
|
end
|
|
|
|
# Tries to calculate all pairs of matching prompts between the given
|
|
# challenge signup (to be used as a request), and the batch of offers.
|
|
def build_matches_for_request(request_signup, offer_batch)
|
|
request_signup.requests.each do |request|
|
|
offer_candidates = offer_batch.candidates_for_matching(request)
|
|
|
|
offer_candidates.each do |offer|
|
|
try_match_prompts(request, offer)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Generates (and saves) all PotentialMatches for the given request batch and
|
|
# offer batch.
|
|
def make_batch_matches(request_batch, offer_batch)
|
|
@progress.start_subtask(request_batch.signups.size)
|
|
|
|
request_batch.signups.each do |request_signup|
|
|
build_matches_for_request(request_signup, offer_batch)
|
|
save_potential_matches
|
|
@progress.increment
|
|
end
|
|
|
|
@progress.end_subtask
|
|
end
|
|
|
|
public
|
|
|
|
# Generates all potential matches for the collection.
|
|
def generate
|
|
# These two lines won't trigger SQL queries (which is good, because that'd
|
|
# be an awful lot of data to load). They're just defining relations that we
|
|
# can call find_in_batches on.
|
|
offers = @collection.signups.with_offer_tags
|
|
requests = @collection.signups.with_request_tags
|
|
|
|
# We process a quadratic number of batch pairs.
|
|
batch_count = 1 + (@collection.signups.count - 1) / @batch_size
|
|
@progress.start_subtask(batch_count * batch_count)
|
|
|
|
offers.find_in_batches(batch_size: @batch_size) do |offer_signups|
|
|
break if PotentialMatch.canceled?(@collection)
|
|
|
|
offer_batch = make_batch(offer_signups, :offers)
|
|
|
|
requests.find_in_batches(batch_size: @batch_size) do |request_signups|
|
|
break if PotentialMatch.canceled?(@collection)
|
|
|
|
request_batch = make_batch(request_signups, :requests)
|
|
make_batch_matches(request_batch, offer_batch)
|
|
|
|
@progress.increment
|
|
end
|
|
end
|
|
|
|
@progress.end_subtask
|
|
end
|
|
end
|