Skip to content

Instantly share code, notes, and snippets.

@yknx4
Created July 6, 2016 23:26
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 yknx4/d0ab08205a1a1b442bf28d5f63b62e8b to your computer and use it in GitHub Desktop.
Save yknx4/d0ab08205a1a1b442bf28d5f63b62e8b to your computer and use it in GitHub Desktop.
troublesome code.rb
class ProfileBookEvaluator
OTHER_MATCH_COUNT = 10
def evaluate( profile_id )
profile = profile_id
# This check is to not to brake existing functionality, but it is better to pass a profile_id than the full object
if profile.is_a? Profile
# Ensure we have the most up-to-date information on this object
# Since users can potentially change their filters / address info
# In between the serialization of the object and the time it's run
profile.reload
else
profile = Profile.find(profile_id)
end
@profile = profile
@user = profile.user
@microsite = profile.microsite
# if this months profile has been locked by admin, skip the evaluation
return [] if profile.blank? || profile.is_book_match_approved?
# get rid of any existing matches,
# since we will recreate them if applicable
profile.profile_matches.destroy_all
# Start with all the books.
# Eliminate any that aren't tagged with the child's age (if child's DOB is provided)
not_to_send_book_ids = already_sent_books + saved_books + paused_books + logged_books
books = if profile_age_rounded.blank?
approved_books.where('books.id NOT IN (?)', not_to_send_book_ids.flatten)
else
approved_books.appropriate_for_age(profile_age_rounded).where('books.id NOT IN (?)', not_to_send_book_ids.flatten)
end
unless profile.customized_filters.blank?
# Eliminate any that are not in at least one of the selected languages (if languages are selected)
if profile.customized_filters['languages'].blank?
english = Language.find_by(name: 'English')
books = books.with_languages([english.id]) if english.present?
else
books = books.with_languages(profile.customized_filters['languages'])
end
# Eliminate any that don't match any of the selected Reading Levels (if reading levels are provided)
unless profile.customized_filters["reading_levels"].blank?
books = books.with_reading_levels(profile.customized_filters['reading_levels'])
end
end
# Calculate a relevance score for the remaining books,
# => where 'Background' filters are worth twice as much as 'Interest' and 'Genre' filters
if books.any?
related = {}
# Sometimes pluck is not available or refuses to work due to some bad sql generated, better safe than sorry....
begin
books_in_custom_collection = BookFormat.where(in_custom_collection: true,
book_id: books.pluck('books.id'))
.pluck(:book_id)
rescue
books_in_custom_collection = BookFormat.where(in_custom_collection: true,
book_id: books.collect(&:id))
.pluck(:book_id)
end
reading_weight_total = 0
background_weight_total = 0
interest_weight_total = 0
genre_weight_total = 0
books.all.map{ |book| related[book.id] = {
:book_id => book.id,
:profile_id => profile.id,
:matched_filters => {},
:in_custom_collection => books_in_custom_collection.include?(book.id),
:score => 0,
:interval => 0.0,
:has_tip => true } #We can assure all books have tip because we only did query for books with tip
}
unless profile.customized_filters.blank?
# Assign scores for selected reading levels
if profile.customized_filters.key? 'reading_levels'
profile.customized_filters['reading_levels'].each do |r|
reading_level = ReadingLevel.find_by_id(r)
next if reading_level.nil?
weight = reading_level.evaluation_weight.blank? ? 0 : reading_level.evaluation_weight
reading_weight_total += weight
eligible_formats = books
eligible_formats.joins(:reading_levels).where(:reading_levels => { :id => r }).uniq.each do |match|
related[match.id][:score] += weight
related[match.id][:matched_filters][:reading_levels] ||= []
related[match.id][:matched_filters][:reading_levels] << r
end
end
end
# Assign scores for selected backgrounds
if profile.customized_filters.key? 'backgrounds'
profile.customized_filters['backgrounds'].each do |b|
background_type = BackgroundType.find_by_id(b)
next if background_type.nil?
weight = background_type.evaluation_weight.blank? ? 0 : background_type.evaluation_weight
background_weight_total += weight
eligible_formats = books
eligible_formats.joins(:backgrounds).where(:backgrounds => { :background_type_id => b }).uniq.each do |match|
related[match.id][:score] += weight
related[match.id][:matched_filters][:backgrounds] ||= []
related[match.id][:matched_filters][:backgrounds] << b
end
end
end
# Assign scores for selected interests
if profile.customized_filters.key? 'interests'
profile.customized_filters['interests'].each do |i|
interest = Interest.find_by_id(i)
next if interest.nil?
weight = interest.evaluation_weight.blank? ? 0 : interest.evaluation_weight
interest_weight_total += weight
eligible_formats = books
eligible_formats.joins(:interests).where(:interests => { :id => i }).uniq.each do |match|
related[match.id][:score] += weight
related[match.id][:matched_filters][:interests] ||= []
related[match.id][:matched_filters][:interests] << i
end
end
end
# Assign scores for selected interests
if profile.customized_filters.key? 'genres'
profile.customized_filters['genres'].each do |g|
genre = Genre.find_by_id(g)
next if genre.nil?
weight = genre.evaluation_weight.blank? ? 0 : genre.evaluation_weight
genre_weight_total += weight
eligible_formats = books
eligible_formats.joins(:genres).where(:genres => { :id => g }).uniq.each do |match|
related[match.id][:score] += weight
related[match.id][:matched_filters][:genres] ||= []
related[match.id][:matched_filters][:genres] << g
end
end
end
end
# Calculate intervals
max_rs = max_relevance_score(profile, reading_weight_total, background_weight_total, interest_weight_total, genre_weight_total)
related.each do |k, _|
if max_rs == 0
related[k][:interval] = 0
related[k][:max_relevance_score] = 0
else
related[k][:interval] = 100 * related[k][:score] / max_rs
related[k][:max_relevance_score] = max_rs
end
end
# Sort
related = related.values.sort{ |x, y|
# prefer the highest interval
if x[:interval] == y[:interval]
if (x[:has_tip] == y[:has_tip])
0
else
x[:has_tip] ? -1 : 1
end
else
y[:interval] <=> x[:interval]
end
}
# Create return array
matches = []
profile_match_limit = related.size < OTHER_MATCH_COUNT ? related.size : OTHER_MATCH_COUNT
ProfileMatch.transaction do
related.slice(0, profile_match_limit).each do |item|
matches << ProfileMatch.create(item)
end
end
create_pending_profile_match(profile)
matches
else
[]
end
end
def self.evaluate_now(profile)
self.new.evaluate(profile.id)
end
def create_pending_profile_match(profile)
# get the best book profile match
best_match = ProfileMatch.best_book_match(profile)
unless best_match.nil?
PendingProfileMatch.where('profile_id = (?)', profile.id).destroy_all
pending_match_params = {
:profile_match_id => best_match.id,
:profile_id => profile.id,
:book_id => best_match.book.id,
:matchable_title => best_match.book.title,
:user_id => profile.user.id,
:user_name => profile.user.name,
:score => best_match.score,
:max_relevance_score => best_match.max_relevance_score,
:interval => best_match.interval,
:has_tip => best_match.has_tip
}
pending_match = PendingProfileMatch.new(pending_match_params)
pending_match.save
pending_match
end
end
def max_relevance_score(profile, reading_total, background_total, interest_total, genre_total)
max_score = 0
unless profile.customized_filters.blank?
max_score += reading_total unless reading_total.nil?
max_score += background_total unless background_total.nil?
max_score += interest_total unless interest_total.nil?
max_score += genre_total unless genre_total.nil?
end
max_score
end
def approved_books
@approved_books ||= Rails.cache.fetch "/microsite/#{@microsite.id}/books_approved", expires_in: 1.hour do
@microsite.books
.approved
end
end
def books_with_learning_tip
@books_with_learning_tip ||= Rails.cache.fetch "/microsite/#{@microsite.id}/books_approved_with_learning_tip", expires_in: 1.hour do
@microsite.books
.approved
.joins(:book_learning_tip)
end
end
def already_sent_books
@already_sent_books ||= sent_book_ids + pending_matches_book_ids
end
def recommendations_for_user
Recommendation.joins(:profile).where('profiles.user_id = ?', @user.id) unless @user.blank?
end
def sent_book_ids
@sent_book_ids ||= (recommendations_for_user.try(:pluck, :book_id) || [])
end
def pending_matches_for_user
PendingProfileMatch.joins(:profile).where('profiles.user_id = ?', @user.id)
end
def pending_matches_book_ids
@pending_matches_book_ids ||= pending_matches_for_user.try(:pluck, :book_id)
end
def saved_books
@saved_books ||= wish_lists_book_ids
end
def wish_lists_for_user
WishList.joins(:profile).where('profiles.user_id = ?', @user.id)
end
def wish_lists_book_ids
@wish_lists_book_ids ||= wish_lists_for_user.pluck(:itemizable_id)
end
def logged_books
@logged_books ||= @profile.logged_books.all.pluck(:book_id).compact
end
def paused_books
@paused_books ||= Microsites::PausedBooks.new(@profile.microsite).call
end
def profile_age_rounded
@profile_age_rounded ||= @profile.age_rounded
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment