Skip to content

Instantly share code, notes, and snippets.

@aslam
Created September 5, 2012 19:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aslam/3642741 to your computer and use it in GitHub Desktop.
Save aslam/3642741 to your computer and use it in GitHub Desktop.
Retina India - Seshpath MatchMaker
class MatchMaker
# This method matches the volunteers with the request criteria and notifies them through
# email and sms. Volunteers can accept this request by clicking on a link sent in the email
# or sending an sms to <NO>.
#
# The student can freeze on a volunteer by clicking on a link provided next to the volunteer
# details and close this request.
#
# Then the actual searching starts by building the criteria for search. Various search scopes
# are merged together to form complete search criteria which is passed on to Oracle to do the
# searching. Oracle returns volunteers array, and matches are created and saved. Matched
# volunteers are notified about the request through email and sms.
#
# After volunteer notification, a new delayed job is created to run after <set time> which
# would repeat the process.
def match_and_notify(service_request)
rejects = ['id', 'student_id', 'service_provider_id', 'created_at', 'updated_at', 'status',
'pursuing', 'education', 'complete', 'from_date', 'to_date', 'timing',
'reading_at_a_different_place']
exam_fields = ['paper', 'exam_date', 'start_time', 'end_time']
# Start building search criteria
criteria = service_request.attributes
criteria.delete_if { |k, v| rejects.include?(k) }
exam_details = criteria.extract!(*exam_fields)
if service_request.is_a?(ScribeRequest)
streams = YAML.load service_request.streams
streams.delete_if { |s| s.blank? }
if streams.include?('Any')
criteria.delete('streams')
criteria.merge!({'stream' => 'any'})
else
criteria['streams'] = streams
end
locations = Location.of_volunteers.near( service_request.location.address, Configuration.service_requests['boundary'] )
criteria.merge!({ :located => locations.pluck(:id) })
criteria.merge!({ :not_engaged_on => service_request.exam_date })
elsif service_request.is_a?(ReaderRequest)
if service_request.for_date_range?
criteria.merge!({ :not_engaged_between => [service_request.from_date, service_request.to_date] })
else
criteria.merge!({ :not_engaged_on => service_request.from_date })
end
end
if service_request.matches.any?
ids = service_request.matches.pluck(:volunteer_id)
criteria.merge!({ :excluding => ids })
end
batch = Oracle.new.search('Volunteer', criteria)
matches = matchify(service_request, batch)
messager = Messager.new
# Job for Email notification
if service_request.is_a?(ScribeRequest)
job = ScribeRequestEmailNotificationJob.new(service_request, matches)
else
job = ReaderRequestEmailNotificationJob.new(service_request, matches)
end
Delayed::Job.enqueue(job)
# Job for SMS notification
Delayed::Job.enqueue(ServiceRequestSmsNotificationJob.new(service_request, matches, messager))
end
def matchify(service_request, batch)
service_request.update_status("notified")
batch.each do |m|
service_request.matches.build(:volunteer_id => m.id, :acceptance_token_expires_at => 1.days.from_now)
end
service_request.save!
service_request.matches
end
end
class Oracle
# Scopes
# Volunteer => [ * - 'q' is mandatory, # - 'q' is optional, ** - 's', 'e' are mandatory ]
# active - activation_state is 'active'
# scribes - type is 'scribe'
# readers - type is 'reader'
# *by_name - name like 'q'
# *by_email - email like 'q'
# educated - has education and title is not null
# any_stream - has education stream and is not null
# *streams - has education stream in 'q'
# #stream - has education stream like 'q'
# #education - has education and title like 'q'
# *level - education level (title) is 'q'
# *level_lt - education level (title) is < 'q'
# *level_lteq - education level (title) is <= 'q'
# *marks_lt - has education and marks obtained < 'q'
# *marks_lteq - has education and marks obtained <= 'q'
# *marks_gt - has education and marks obtained > 'q'
# *marks_gteq - has education and marks obtained >= 'q'
# *knows - knows language 'q'
# *reads - knows and reads language 'q'
# *writes - knows and writes language 'q'
# *not_engaged_on - doesn't have any volunteering engagements on 'q'
# **not_engaged_between - doesn't have any volunteering engagements betwee 'q' & 'q`'
def search(scope, criteria)
scope = scope.constantize.active.scoped
criteria, singular = singular(criteria)
singular.each do |k|
scope = scope.send(k.to_sym)
end
if criteria.has_key?(:not_engaged_between)
multi = criteria.select { |k, v| k.eql?(:not_engaged_between) }
criteria.delete(:not_engaged_between)
attrs = multi.values.flatten
scope = scope.send(:not_engaged_between, attrs[0], attrs[1])
end
build(criteria).each do |k, v|
scope = scope.send(k.to_sym, v)
end
scope.includes(:account).limit(Configuration.service_requests['limit']).all
end
def build(params)
rejects = ['utf8', 'authenticity_token', 'action']
required = ['by_name', 'by_email', 'streams', 'marks_lt', 'marks_lteq', 'marks_gt', 'marks_gteq',
'knows', 'reads', 'writes', 'not_engaged_on', 'located']
optional = ['stream', 'education']
params.delete_if { |k, v| rejects.include?(k) }
params.delete_if { |k, v| required.include?(k) && v.blank? }
params.each { |k, v| params.delete(k) if optional.include?(k.to_s) && v.blank? }
params
end
def singular(params)
singular = ['active', 'scribes', 'readers', 'educated', 'any_stream']
original = []
params.each { |k, v| original << k && params.delete(k) if singular.include?(k.to_s) }
[params, original]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment