Module: Recommendable::ActsAsRecommendedTo::RecommendationMethods

Defined in:
lib/recommendable/acts_as_recommended_to.rb

Instance Method Summary collapse

Instance Method Details

#common_dislikes_with(rater, options = {}) ⇒ Array

Makes a call to Redis and intersects the sets of dislikes belonging to self and rater.

Parameters:

  • rater (Object)

    the person whose set of dislikes you wish to intersect with that of self

  • options (Hash) (defaults to: {})

    the options for this intersection

Options Hash (options):

  • :class (Class, String, Symbol) — default: 'nil'

    Restrict the intersection to a single recommendable type. By default, all recomendable types are considered

  • :return_records (true, false) — default: true

    Return the actual Model instances

Returns:

  • (Array)

    An array of IDs, or strings from Redis in the form of #dislikeable_type:#id“ if options is set



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/recommendable/acts_as_recommended_to.rb', line 436

def common_dislikes_with(rater, options = {})
  defaults = { :class => nil,
               :return_records => true }
  options = defaults.merge(options)
  create_recommended_to_sets and rater.create_recommended_to_sets if options[:return_records]

  if options[:class]
    in_common = Recommendable.redis.sinter dislikes_set_for(options[:class]), rater.dislikes_set_for(options[:class])
    in_common = options[:class].to_s.classify.constantize.find in_common if options[:return_records]
  else
    in_common = Recommendable.recommendable_classes.flat_map do |klass|
      things = Recommendable.redis.sinter(dislikes_set_for(klass), rater.dislikes_set_for(klass))

      if options[:return_records]
        klass.to_s.classify.constantize.find(things)
      else
        things.map {|id| "#{klass.to_s.classify}:#{id}"}
      end
    end
  end

  destroy_recommended_to_sets and rater.destroy_recommended_to_sets if options[:return_records]
  in_common
end

#common_likes_with(rater, options = {}) ⇒ Array

Makes a call to Redis and intersects the sets of likes belonging to self and rater.

Parameters:

  • rater (Object)

    the person whose set of likes you wish to intersect with that of self

  • options (Hash) (defaults to: {})

    the options for this intersection

Options Hash (options):

  • :class (Class, String, Symbol) — default: 'nil'

    Restrict the intersection to a single recommendable type. By default, all recomendable types are considered

  • :return_records (true, false) — default: true

    Return the actual Model instances

Returns:

  • (Array)

    An array of IDs, or strings from Redis in the form of “#likeable_type:#id” if options is set



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/recommendable/acts_as_recommended_to.rb', line 403

def common_likes_with(rater, options = {})
  defaults = { :class => nil,
               :return_records => true }
  options = defaults.merge(options)
  create_recommended_to_sets and rater.create_recommended_to_sets if options[:return_records]

  if options[:class]
    in_common = Recommendable.redis.sinter likes_set_for(options[:class]), rater.likes_set_for(options[:class])
    in_common = options[:class].to_s.classify.constantize.find in_common if options[:return_records]
  else
    in_common = Recommendable.recommendable_classes.flat_map do |klass|
      things = Recommendable.redis.sinter(likes_set_for(klass), rater.likes_set_for(klass))

      if options[:return_records]
        klass.to_s.classify.constantize.find(things)
      else
        things.map {|id| "#{klass.to_s.classify}:#{id}"}
      end
    end
  end

  destroy_recommended_to_sets and rater.destroy_recommended_to_sets if options[:return_records]
  in_common
end

#completely_unrecommend(object) ⇒ Object

Used internally during liking/disliking/stashing/ignoring objects. This will prep an object to be liked, disliked, etc. by making sure that self doesn’t already have this item in their list of likes, dislikes, stashed items or ignored items.

param [Object] object the object to destroy Recommendable models for



505
506
507
508
509
510
511
# File 'lib/recommendable/acts_as_recommended_to.rb', line 505

def completely_unrecommend(object)
  unlike(object)
  undislike(object)
  unstash(object)
  unignore(object)
  unpredict(object)
end

#disagreements_with(rater, options = {}) ⇒ Array

Makes a call to Redis and intersects self’s set of likes with rater’s set of dislikes and vise versa. The idea here is that if self likes an object that rater dislikes, it is a disagreement and should count negatively towards their similarity.

Parameters:

  • rater (Object)

    the person whose sets you wish to intersect with those of self

  • options (Hash) (defaults to: {})

    the options for this intersection

Options Hash (options):

  • :class (Class, String, Symbol) — default: 'nil'

    Restrict the intersections to a single recommendable type. By default, all recomendable types are considered

  • :return_records (true, false) — default: true

    Return the actual Model instances

Returns:

  • (Array)

    An array of IDs, or strings from Redis in the form of #recommendable_type:#id“ if options is set



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/recommendable/acts_as_recommended_to.rb', line 471

def disagreements_with(rater, options = {})
  defaults = { :class => nil,
               :return_records => true }
  options = defaults.merge(options)
  create_recommended_to_sets and rater.create_recommended_to_sets if options[:return_records]
  
  if options[:class]
    disagreements =  Recommendable.redis.sinter(likes_set_for(options[:class]), rater.dislikes_set_for(options[:class]))
    disagreements += Recommendable.redis.sinter(dislikes_set_for(options[:class]), rater.likes_set_for(options[:class]))
    disagreements = options[:class].to_s.classify.constantize.find disagreements if options[:return_records]
  else
    disagreements = Recommendable.recommendable_classes.flat_map do |klass|
      things = Recommendable.redis.sinter(likes_set_for(klass), rater.dislikes_set_for(klass))
      things += Recommendable.redis.sinter(dislikes_set_for(klass), rater.likes_set_for(klass))
      
      if options[:return_records]
        klass.to_s.classify.constantize.find(things)
      else
        things.map {|id| "#{options[:class].to_s.classify}:#{id}"}
      end
    end
  end

  destroy_recommended_to_sets and rater.destroy_recommended_to_sets if options[:return_records]
  disagreements
end

#probability_of_disliking(object) ⇒ Float

Return the negation of the value calculated by #predict on self for a passed object.

Parameters:

  • object (Object)

    the object to fetch the probability for

Returns:

  • (Float)

    the likelihood of self disliking the passed object

See Also:

  • Recommendable::ActsAsRecommendedTo::RecommendationMethods.{{#probability_of_liking}


391
392
393
# File 'lib/recommendable/acts_as_recommended_to.rb', line 391

def probability_of_disliking(object)
  -probability_of_liking(object)
end

#probability_of_liking(object) ⇒ Float

Return the value calculated by #predict on self for a passed object.

Parameters:

  • object (Object)

    the object to fetch the probability for

Returns:

  • (Float)

    the likelihood of self liking the passed object



381
382
383
# File 'lib/recommendable/acts_as_recommended_to.rb', line 381

def probability_of_liking(object)
  Recommendable.redis.zscore predictions_set_for(object.class), object.redis_key
end

#rated?(object) ⇒ Boolean

Checks to see if self has already liked or disliked a passed object.

Parameters:

  • object (Object)

    the object you want to check

Returns:

  • (Boolean)

    true if self has liked or disliked object, false if not



295
296
297
# File 'lib/recommendable/acts_as_recommended_to.rb', line 295

def rated?(object)
  likes?(object) || dislikes?(object)
end

#rated_anything?Boolean

Checks to see if self has liked or disliked any objects yet.

Returns:

  • (Boolean)

    true if self has liked or disliked anything, false if not



302
303
304
# File 'lib/recommendable/acts_as_recommended_to.rb', line 302

def rated_anything?
  likes.count > 0 || dislikes.count > 0
end

#recommendations(options = {}) ⇒ Array

Get a list of recommendations for self. The whole point of this gem! Recommendations are returned in a descending order with the first index being the object that self has been found most likely to enjoy.

Parameters:

  • options (Hash) (defaults to: {})

    the options for returning this list

Options Hash (options):

  • :count (Fixnum) — default: 10

    the number of recommendations to get

Returns:

  • (Array)

    an array of ActiveRecord objects that are recommendable



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/recommendable/acts_as_recommended_to.rb', line 333

def recommendations(options = {})
  defaults = { :count => 10 }
  options = defaults.merge options
  return [] if likes.count + dislikes.count == 0

  unioned_predictions = "#{self.class}:#{id}:predictions"
  Recommendable.redis.zunionstore unioned_predictions, Recommendable.recommendable_classes.map {|klass| predictions_set_for(klass)}
  
  recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0, options[:count]).map do |object|
    klass, id = object.split(":")
    klass.constantize.find(id)
  end
  
  Recommendable.redis.del(unioned_predictions) and return recommendations
end

#recommendations_for(klass, options = {}) ⇒ Array

Get a list of recommendations for self on a single recommendable type. Recommendations are returned in a descending order with the first index being the object that self has been found most likely to enjoy.

Parameters:

  • klass (Class, String, Symbol)

    the class to receive recommendations for. Can be the class constant, or a String/Symbol representation of the class name.

  • options (Hash) (defaults to: {})

    the options for returning this list

Options Hash (options):

  • :count (Fixnum) — default: 10

    the number of recommendations to get

Returns:

  • (Array)

    an array of ActiveRecord objects that are recommendable



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/recommendable/acts_as_recommended_to.rb', line 357

def recommendations_for(klass, options = {})
  defaults = { :count => 10 }
  options = defaults.merge options
  
  return [] if likes_for(klass).count + dislikes_for(klass).count == 0 || Recommendable.redis.zcard(predictions_set_for(klass)) == 0

  recommendations = []
  i = 0
  until recommendations.size == options[:count]
    prediction = Recommendable.redis.zrevrange(predictions_set_for(klass), i, i).first
    return recommendations unless prediction # User might not have enough recommendations to return
    
    object = klass.to_s.classify.constantize.find(prediction.split(":")[1])
    recommendations << object
    i += 1
  end

  return recommendations
end

#similar_raters(options = {}) ⇒ Array

Get a list of raters that have been found to be the most similar to self. They are sorted in a descending fashion with the most similar rater in the first index.

Parameters:

  • options (Hash) (defaults to: {})

    the options for this query

Options Hash (options):

  • :count (Fixnum) — default: 10

    The number of raters to return

Returns:

  • (Array)

    An array of instances of your user class



313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/recommendable/acts_as_recommended_to.rb', line 313

def similar_raters(options = {})
  defaults = { :count => 10 }
  options = defaults.merge(options)
  
  rater_ids = Recommendable.redis.zrevrange(similarity_set, 0, options[:count] - 1).map(&:to_i)
  raters = Recommendable.user_class.where("ID IN (?)", rater_ids)
  
  # The query loses the ordering, so...
  return raters.sort do |x, y|
    rater_ids.index(x.id) <=> rater_ids.index(y.id)
  end
end