recommendable 0.2.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.markdown +21 -2
- data/Gemfile +11 -11
- data/Gemfile.lock +1 -1
- data/README.markdown +5 -0
- data/app/models/recommendable/ignore.rb +1 -0
- data/app/models/recommendable/stash.rb +1 -0
- data/lib/recommendable/acts_as_recommendable.rb +12 -5
- data/lib/recommendable/acts_as_recommended_to.rb +58 -60
- data/lib/recommendable/helpers.rb +2 -1
- data/lib/recommendable/version.rb +1 -1
- data/spec/models/movie_spec.rb +13 -0
- data/spec/models/user_spec.rb +67 -2
- metadata +2 -2
data/CHANGELOG.markdown
CHANGED
@@ -1,8 +1,27 @@
|
|
1
1
|
Changelog
|
2
2
|
=========
|
3
3
|
|
4
|
-
0.
|
5
|
-
|
4
|
+
1.0.0 (current version)
|
5
|
+
---------------------
|
6
|
+
* Dynamic finders now return ActiveRecord::Relations! This means you can chain other ActiveRecord query methods like so:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
current_user.recommended_posts.where(:category => "technology")
|
10
|
+
current_user.liked_movies.limit(10)
|
11
|
+
current_user.stashed_books.where(:author => "Cormac McCarthy")
|
12
|
+
current_user.disliked_shows.joins(:cast_members).where('cast_members.name = Kim Kardashian')
|
13
|
+
```
|
14
|
+
|
15
|
+
* You can now specify a count for `User#recommendations`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
current_user.recommendations(10)
|
19
|
+
```
|
20
|
+
|
21
|
+
* Bug fixes
|
22
|
+
|
23
|
+
0.2.0
|
24
|
+
-----
|
6
25
|
* NOTE: This release is NOT backwards compatible. Please migrate your databases:
|
7
26
|
|
8
27
|
```
|
data/Gemfile
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
source
|
1
|
+
source 'http://rubygems.org'
|
2
2
|
# Add dependencies required to use your gem here.
|
3
|
-
gem
|
4
|
-
gem
|
5
|
-
gem
|
6
|
-
gem
|
3
|
+
gem 'rails', '>= 3.1.0'
|
4
|
+
gem 'redis', '>= 2.2.0'
|
5
|
+
gem 'resque', '~> 1.19.0'
|
6
|
+
gem 'resque-loner', '~> 1.2.0'
|
7
7
|
|
8
8
|
# Add dependencies to develop your gem here.
|
9
9
|
# Include everything needed to run rake, tests, features, etc.
|
10
10
|
group :development do
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem
|
16
|
-
gem
|
11
|
+
gem 'sqlite3'
|
12
|
+
gem 'minitest'
|
13
|
+
gem 'shoulda'
|
14
|
+
gem 'miniskirt'
|
15
|
+
gem 'yard', '~> 0.6.0'
|
16
|
+
gem 'bundler', '>= 1.0.0'
|
17
17
|
end
|
data/Gemfile.lock
CHANGED
data/README.markdown
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
Recommendable is an engine for Rails 3 applications to quickly add the ability for your users to Like/Dislike items and receive recommendations for new items. It uses Redis to store your recommendations and keep them sorted by how good the recommendation is.
|
4
4
|
|
5
|
+
Requirements
|
6
|
+
------------
|
7
|
+
* Ruby 1.9.2 (1.9.x if using from git. May run on 1.8.7, I don't know)
|
8
|
+
* Rails 3.x
|
9
|
+
|
5
10
|
Installation
|
6
11
|
------------
|
7
12
|
|
@@ -24,7 +24,7 @@ module Recommendable
|
|
24
24
|
include LikeableMethods
|
25
25
|
include DislikeableMethods
|
26
26
|
|
27
|
-
before_destroy :remove_from_scores
|
27
|
+
before_destroy :remove_from_scores, :remove_from_recommendations
|
28
28
|
|
29
29
|
def self.acts_as_recommendable?() true end
|
30
30
|
|
@@ -42,10 +42,9 @@ module Recommendable
|
|
42
42
|
ids = Recommendable.redis.zrevrange(self.score_set, 0, count - 1).map(&:to_i)
|
43
43
|
|
44
44
|
items = self.find ids
|
45
|
+
return items.first if count == 1
|
45
46
|
|
46
|
-
return items.sort
|
47
|
-
ids.index(x.id) <=> ids.index(y.id)
|
48
|
-
end
|
47
|
+
return items.sort { |x, y| ids.index(x.id) <=> ids.index(y.id) }
|
49
48
|
end
|
50
49
|
|
51
50
|
private
|
@@ -66,6 +65,12 @@ module Recommendable
|
|
66
65
|
Recommendable.redis.zrem self.class.score_set, self.id
|
67
66
|
true
|
68
67
|
end
|
68
|
+
|
69
|
+
def remove_from_recommendations
|
70
|
+
Recommendable.user_class.find_each do |user|
|
71
|
+
user.send :completely_unrecommend, self
|
72
|
+
end
|
73
|
+
end
|
69
74
|
|
70
75
|
# Used for setup purposes. Calls convenience methods to create sets
|
71
76
|
# in redis of users that both like and dislike this object.
|
@@ -100,7 +105,9 @@ module Recommendable
|
|
100
105
|
|
101
106
|
def acts_as_recommendable?() false end
|
102
107
|
|
103
|
-
def sti?
|
108
|
+
def sti?
|
109
|
+
self.base_class != self && self.base_class.table_name == self.table_name
|
110
|
+
end
|
104
111
|
|
105
112
|
private
|
106
113
|
end
|
@@ -19,15 +19,14 @@ module Recommendable
|
|
19
19
|
has_many :dislikes, :class_name => "Recommendable::Dislike", :dependent => :destroy, :foreign_key => :user_id
|
20
20
|
has_many :ignores, :class_name => "Recommendable::Ignore", :dependent => :destroy, :foreign_key => :user_id
|
21
21
|
has_many :stashed_items, :class_name => "Recommendable::Stash", :dependent => :destroy, :foreign_key => :user_id
|
22
|
-
|
22
|
+
|
23
23
|
include LikeMethods
|
24
24
|
include DislikeMethods
|
25
25
|
include StashMethods
|
26
26
|
include IgnoreMethods
|
27
27
|
include RecommendationMethods
|
28
28
|
|
29
|
-
before_destroy :remove_from_similarities
|
30
|
-
before_destroy :remove_recommendations
|
29
|
+
before_destroy :remove_from_similarities, :remove_recommendations
|
31
30
|
|
32
31
|
def method_missing method, *args, &block
|
33
32
|
if method.to_s =~ /^(liked|disliked)_(.+)_in_common_with$/
|
@@ -42,7 +41,7 @@ module Recommendable
|
|
42
41
|
begin
|
43
42
|
super unless $2.classify.constantize.acts_as_recommendable?
|
44
43
|
|
45
|
-
self.send "#{$1}_for", $2.classify.constantize
|
44
|
+
self.send "#{$1}_for", $2.classify.constantize, *args
|
46
45
|
rescue NameError
|
47
46
|
super
|
48
47
|
end
|
@@ -52,8 +51,7 @@ module Recommendable
|
|
52
51
|
end
|
53
52
|
|
54
53
|
def respond_to? method, include_private = false
|
55
|
-
if method.to_s =~ /^(liked|disliked|ignored|stashed|recommended)_(.+)$/ ||
|
56
|
-
method.to_s =~ /^common_(liked|disliked)_(.+)_with$/
|
54
|
+
if method.to_s =~ /^(liked|disliked|ignored|stashed|recommended)_(.+)$/ || method.to_s =~ /^common_(liked|disliked)_(.+)_with$/
|
57
55
|
begin
|
58
56
|
$2.classify.constantize.acts_as_recommendable?
|
59
57
|
rescue NameError
|
@@ -112,7 +110,7 @@ module Recommendable
|
|
112
110
|
|
113
111
|
# @return [Array] an array of ActiveRecord objects that self has liked
|
114
112
|
def liked
|
115
|
-
Recommendable.recommendable_classes.
|
113
|
+
Recommendable.recommendable_classes.map { |klass| liked_for klass }.flatten
|
116
114
|
end
|
117
115
|
|
118
116
|
private
|
@@ -121,15 +119,15 @@ module Recommendable
|
|
121
119
|
# likes.
|
122
120
|
#
|
123
121
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
124
|
-
# @return [
|
122
|
+
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has liked
|
125
123
|
def liked_for klass
|
126
|
-
|
127
|
-
likes.joins
|
124
|
+
ids = if klass.sti?
|
125
|
+
likes.joins(manual_join(klass, 'like')).map(&:likeable_id)
|
128
126
|
else
|
129
|
-
likes.where(:likeable_type => klass.to_s).
|
127
|
+
likes.where(:likeable_type => klass.to_s).map(&:likeable_id)
|
130
128
|
end
|
131
129
|
|
132
|
-
|
130
|
+
klass.where('ID IN (?)', ids)
|
133
131
|
end
|
134
132
|
|
135
133
|
# Get a list of Recommendable::Likes with a `#likeable_type` of the passed
|
@@ -190,7 +188,7 @@ module Recommendable
|
|
190
188
|
|
191
189
|
# @return [Array] an array of ActiveRecord objects that self has disliked
|
192
190
|
def disliked
|
193
|
-
Recommendable.recommendable_classes.
|
191
|
+
Recommendable.recommendable_classes.map { |klass| disliked_for klass }.flatten
|
194
192
|
end
|
195
193
|
|
196
194
|
private
|
@@ -199,15 +197,15 @@ module Recommendable
|
|
199
197
|
# dislikes.
|
200
198
|
#
|
201
199
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
202
|
-
# @return [
|
200
|
+
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has disliked
|
203
201
|
def disliked_for klass
|
204
|
-
|
205
|
-
dislikes.joins
|
202
|
+
ids = if klass.sti?
|
203
|
+
dislikes.joins(manual_join(klass, 'dislike')).map(&:dislikeable_id)
|
206
204
|
else
|
207
|
-
dislikes.where(:dislikeable_type => klass.to_s).
|
205
|
+
dislikes.where(:dislikeable_type => klass.to_s).map(&:dislikeable_id)
|
208
206
|
end
|
209
207
|
|
210
|
-
|
208
|
+
klass.where('ID IN (?)', ids)
|
211
209
|
end
|
212
210
|
|
213
211
|
# Get a list of Recommendable::Dislikes with a `#dislikeable_type` of the
|
@@ -263,7 +261,7 @@ module Recommendable
|
|
263
261
|
#
|
264
262
|
# @return [Array] an array of ActiveRecord objects that self has stashed
|
265
263
|
def stashed
|
266
|
-
Recommendable.recommendable_classes.
|
264
|
+
Recommendable.recommendable_classes.map { |klass| stashed_for klass }.flatten
|
267
265
|
end
|
268
266
|
|
269
267
|
private
|
@@ -272,15 +270,15 @@ module Recommendable
|
|
272
270
|
# has stashed away for later.
|
273
271
|
#
|
274
272
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
275
|
-
# @return [
|
273
|
+
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has stashed
|
276
274
|
def stashed_for klass
|
277
|
-
|
278
|
-
stashed_items.joins
|
275
|
+
ids = if klass.sti?
|
276
|
+
stashed_items.joins(manual_join(klass, 'stash')).map(&:stashable_id)
|
279
277
|
else
|
280
|
-
stashed_items.where(:stashable_type => klass.to_s).
|
278
|
+
stashed_items.where(:stashable_type => klass.to_s).map(&:stashable_id)
|
281
279
|
end
|
282
280
|
|
283
|
-
|
281
|
+
klass.where('ID IN (?)', ids)
|
284
282
|
end
|
285
283
|
end
|
286
284
|
|
@@ -321,7 +319,7 @@ module Recommendable
|
|
321
319
|
|
322
320
|
# @return [Array] an array of ActiveRecord objects that self has ignored
|
323
321
|
def ignored
|
324
|
-
Recommendable.recommendable_classes.
|
322
|
+
Recommendable.recommendable_classes.map { |klass| ignored_for klass }.flatten
|
325
323
|
end
|
326
324
|
|
327
325
|
private
|
@@ -330,15 +328,15 @@ module Recommendable
|
|
330
328
|
# currently ignoring.
|
331
329
|
#
|
332
330
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
333
|
-
# @return [
|
331
|
+
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has ignored
|
334
332
|
def ignored_for klass
|
335
|
-
|
336
|
-
ignores.joins
|
333
|
+
ids = if klass.sti?
|
334
|
+
ignores.joins(manual_join(klass, 'ignore')).map(&:ignoreable_id)
|
337
335
|
else
|
338
|
-
ignores.where(:
|
336
|
+
ignores.where(:ignoreable_type => klass.to_s).map(&:ignoreable_id)
|
339
337
|
end
|
340
338
|
|
341
|
-
|
339
|
+
klass.where('ID IN (?)', ids)
|
342
340
|
end
|
343
341
|
end
|
344
342
|
|
@@ -406,45 +404,42 @@ module Recommendable
|
|
406
404
|
# Recommendations are returned in a descending order with the first index
|
407
405
|
# being the object that self has been found most likely to enjoy.
|
408
406
|
#
|
409
|
-
# @param [
|
410
|
-
# @option options [Fixnum] :count (10) the number of recommendations to get
|
407
|
+
# @param [Fixnum] count the number of recmomendations to return
|
411
408
|
# @return [Array] an array of ActiveRecord objects that are recommendable
|
412
|
-
def recommendations
|
409
|
+
def recommendations count = 10
|
413
410
|
return [] if likes.count + dislikes.count == 0
|
414
411
|
|
415
412
|
unioned_predictions = "#{self.class}:#{id}:predictions"
|
416
413
|
Recommendable.redis.zunionstore unioned_predictions, Recommendable.recommendable_classes.map { |klass| predictions_set_for klass }
|
417
414
|
|
418
|
-
recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0,
|
415
|
+
recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0, count - 1).map do |object|
|
419
416
|
klass, id = object.split(":")
|
420
417
|
klass.constantize.find(id)
|
421
418
|
end
|
422
419
|
|
420
|
+
recommendations = recommendations.first if count == 1
|
421
|
+
|
423
422
|
Recommendable.redis.del(unioned_predictions) and return recommendations
|
424
423
|
end
|
425
424
|
|
426
|
-
# Get a list of
|
425
|
+
# Get a list of recommendations for self on a single recommendable type.
|
427
426
|
# Recommendations are returned in a descending order with the first index
|
428
427
|
# being the object that self has been found most likely to enjoy.
|
429
428
|
#
|
430
429
|
# @param [Class, String, Symbol] klass the class to receive recommendations for. Can be the class constant, or a String/Symbol representation of the class name.
|
431
|
-
# @return [
|
432
|
-
def recommended_for klass
|
433
|
-
return [] if likes_for(klass.base_class).count + dislikes_for(klass.base_class).count == 0 ||
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
i = 0
|
438
|
-
until recommendations.size == 10
|
430
|
+
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of recommendations
|
431
|
+
def recommended_for klass, count = 10
|
432
|
+
return [] if likes_for(klass.base_class).count + dislikes_for(klass.base_class).count == 0 || Recommendable.redis.zcard(predictions_set_for(klass)) == 0
|
433
|
+
ids = []
|
434
|
+
|
435
|
+
(0...count).each do |i|
|
439
436
|
prediction = Recommendable.redis.zrevrange(predictions_set_for(klass), i, i).first
|
440
|
-
|
437
|
+
break unless prediction
|
441
438
|
|
442
|
-
|
443
|
-
recommendations << object
|
444
|
-
i += 1
|
439
|
+
ids << prediction.split(":").last
|
445
440
|
end
|
446
441
|
|
447
|
-
return
|
442
|
+
return klass.to_s.classify.constantize.where('ID IN (?)', ids)
|
448
443
|
end
|
449
444
|
|
450
445
|
# Return the value calculated by {#predict} on self for a passed object.
|
@@ -481,9 +476,9 @@ module Recommendable
|
|
481
476
|
|
482
477
|
if options[:class]
|
483
478
|
in_common = Recommendable.redis.sinter likes_set_for(options[:class]), rater.likes_set_for(options[:class])
|
484
|
-
in_common = options[:class].to_s.classify.constantize.
|
479
|
+
in_common = options[:class].to_s.classify.constantize.where('ID IN (?)', in_common) if options[:return_records]
|
485
480
|
else
|
486
|
-
in_common = Recommendable.recommendable_classes.
|
481
|
+
in_common = Recommendable.recommendable_classes.map do |klass|
|
487
482
|
things = Recommendable.redis.sinter(likes_set_for(klass), rater.likes_set_for(klass))
|
488
483
|
|
489
484
|
if options[:return_records]
|
@@ -492,9 +487,11 @@ module Recommendable
|
|
492
487
|
things.map { |id| "#{klass.to_s.classify}:#{id}" }
|
493
488
|
end
|
494
489
|
end
|
490
|
+
|
491
|
+
in_common.flatten!
|
495
492
|
end
|
496
493
|
|
497
|
-
in_common
|
494
|
+
return in_common
|
498
495
|
end
|
499
496
|
|
500
497
|
# Makes a call to Redis and intersects the sets of dislikes belonging to
|
@@ -511,9 +508,9 @@ module Recommendable
|
|
511
508
|
|
512
509
|
if options[:class]
|
513
510
|
in_common = Recommendable.redis.sinter dislikes_set_for(options[:class]), rater.dislikes_set_for(options[:class])
|
514
|
-
in_common = options[:class].to_s.classify.constantize.
|
511
|
+
in_common = options[:class].to_s.classify.constantize.where('ID IN (?)', in_common) if options[:return_records]
|
515
512
|
else
|
516
|
-
in_common = Recommendable.recommendable_classes.
|
513
|
+
in_common = Recommendable.recommendable_classes.map do |klass|
|
517
514
|
things = Recommendable.redis.sinter(dislikes_set_for(klass), rater.dislikes_set_for(klass))
|
518
515
|
|
519
516
|
if options[:return_records]
|
@@ -522,6 +519,8 @@ module Recommendable
|
|
522
519
|
things.map { |id| "#{klass.to_s.classify}:#{id}" }
|
523
520
|
end
|
524
521
|
end
|
522
|
+
|
523
|
+
in_common.flatten!
|
525
524
|
end
|
526
525
|
|
527
526
|
in_common
|
@@ -544,9 +543,9 @@ module Recommendable
|
|
544
543
|
if options[:class]
|
545
544
|
disagreements = Recommendable.redis.sinter(likes_set_for(options[:class]), rater.dislikes_set_for(options[:class]))
|
546
545
|
disagreements += Recommendable.redis.sinter(dislikes_set_for(options[:class]), rater.likes_set_for(options[:class]))
|
547
|
-
disagreements = options[:class].to_s.classify.constantize.
|
546
|
+
disagreements = options[:class].to_s.classify.constantize.where('ID IN (?)', disagreements) if options[:return_records]
|
548
547
|
else
|
549
|
-
disagreements = Recommendable.recommendable_classes.
|
548
|
+
disagreements = Recommendable.recommendable_classes.map do |klass|
|
550
549
|
things = Recommendable.redis.sinter(likes_set_for(klass), rater.dislikes_set_for(klass))
|
551
550
|
things += Recommendable.redis.sinter(dislikes_set_for(klass), rater.likes_set_for(klass))
|
552
551
|
|
@@ -556,6 +555,8 @@ module Recommendable
|
|
556
555
|
things.map { |id| "#{options[:class].to_s.classify}:#{id}" }
|
557
556
|
end
|
558
557
|
end
|
558
|
+
|
559
|
+
disagreements.flatten!
|
559
560
|
end
|
560
561
|
|
561
562
|
disagreements
|
@@ -569,11 +570,8 @@ module Recommendable
|
|
569
570
|
# param [Object] object the object to destroy Recommendable models for
|
570
571
|
# @private
|
571
572
|
def completely_unrecommend object
|
572
|
-
unlike object
|
573
|
-
|
574
|
-
unstash object
|
575
|
-
unignore object
|
576
|
-
unpredict object
|
573
|
+
unlike(object) || undislike(object) || unstash(object) || unignore(object)
|
574
|
+
unpredict(object)
|
577
575
|
end
|
578
576
|
|
579
577
|
# @private
|
@@ -2,7 +2,8 @@ module Recommendable
|
|
2
2
|
module Helpers
|
3
3
|
def manual_join(klass, action)
|
4
4
|
table = klass.base_class.table_name
|
5
|
-
|
5
|
+
inheritance_column = klass.base_class.inheritance_column
|
6
|
+
"JOIN #{table} ON recommendable_#{action.pluralize}.#{action}able_id = #{table}.id AND #{table}.#{inheritance_column} = '#{klass}'"
|
6
7
|
end
|
7
8
|
end
|
8
9
|
end
|
data/spec/models/movie_spec.rb
CHANGED
@@ -20,6 +20,19 @@ class MovieSpec < MiniTest::Spec
|
|
20
20
|
|
21
21
|
Movie.top(2).wont_include @movie2
|
22
22
|
end
|
23
|
+
|
24
|
+
it "should be removed from recommendations" do
|
25
|
+
@user2 = Factory(:user)
|
26
|
+
@user1.like @movie1
|
27
|
+
@user2.like @movie1
|
28
|
+
@user2.like @movie2
|
29
|
+
|
30
|
+
@user1.send :update_recommendations
|
31
|
+
@movie2.destroy
|
32
|
+
|
33
|
+
@user2.liked.size.must_equal 1
|
34
|
+
@user1.recommendations.size.must_equal 0
|
35
|
+
end
|
23
36
|
end
|
24
37
|
|
25
38
|
describe ".top" do
|
data/spec/models/user_spec.rb
CHANGED
@@ -34,7 +34,7 @@ class UserSpec < MiniTest::Spec
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
describe "that does not
|
37
|
+
describe "that does not act_as_recommended_to" do
|
38
38
|
before :each do
|
39
39
|
@user = Factory(:bully)
|
40
40
|
@movie = Factory(:movie)
|
@@ -186,7 +186,7 @@ class UserSpec < MiniTest::Spec
|
|
186
186
|
@user.stashed?(@movie).must_equal true
|
187
187
|
end
|
188
188
|
|
189
|
-
it "should not be able to rate or ignore an item that is not recommendable.
|
189
|
+
it "should not be able to rate or ignore an item that is not recommendable." do
|
190
190
|
@cakephp = Factory(:php_framework)
|
191
191
|
|
192
192
|
proc { @user.like(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
|
@@ -235,6 +235,27 @@ class UserSpec < MiniTest::Spec
|
|
235
235
|
@user.unstash(@movie).must_equal true
|
236
236
|
end
|
237
237
|
end
|
238
|
+
|
239
|
+
describe "while using finders" do
|
240
|
+
it "should return ActiveRecord::Relations" do
|
241
|
+
@user = Factory(:user)
|
242
|
+
@movie1 = Factory(:movie)
|
243
|
+
@movie2 = Factory(:movie)
|
244
|
+
@movie3 = Factory(:movie)
|
245
|
+
@movie4 = Factory(:movie)
|
246
|
+
@movie5 = Factory(:movie)
|
247
|
+
|
248
|
+
@user.like @movie1
|
249
|
+
@user.like @movie2
|
250
|
+
@user.dislike @movie3
|
251
|
+
|
252
|
+
@user.liked_movies.must_be_instance_of ActiveRecord::Relation
|
253
|
+
@user.liked_movies.where(:title => @movie1.title).must_include @movie1
|
254
|
+
@user.liked_movies.where(:title => @movie2.title).wont_include @movie1
|
255
|
+
@user.liked_movies.limit(1).size.must_equal 1
|
256
|
+
@user.disliked_movies.where(:title => @movie5.title).must_be_empty
|
257
|
+
end
|
258
|
+
end
|
238
259
|
|
239
260
|
describe "while getting recommendations" do
|
240
261
|
before :each do
|
@@ -255,6 +276,37 @@ class UserSpec < MiniTest::Spec
|
|
255
276
|
Recommendable.redis.del "User:#{@frank.id}:predictions:Movie"
|
256
277
|
end
|
257
278
|
|
279
|
+
it "should respect passed counts" do
|
280
|
+
@dave.like @movie1
|
281
|
+
@frank.like @movie1
|
282
|
+
@frank.like @movie2
|
283
|
+
@frank.like @movie3
|
284
|
+
@frank.like @movie4
|
285
|
+
@frank.like @movie5
|
286
|
+
|
287
|
+
@dave.send :update_similarities and @dave.send :update_recommendations
|
288
|
+
|
289
|
+
@dave.recommendations.size.must_equal 4
|
290
|
+
@dave.recommendations(2).size.must_equal 2
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should return an ActiveRecord::Relation when using the dynamic finder" do
|
294
|
+
@dave.like @movie1
|
295
|
+
@frank.like @movie1
|
296
|
+
@frank.like @movie2
|
297
|
+
@frank.like @movie3
|
298
|
+
@frank.like @movie4
|
299
|
+
@frank.like @movie5
|
300
|
+
|
301
|
+
@dave.send :update_similarities and @dave.send :update_recommendations
|
302
|
+
|
303
|
+
@dave.recommended_movies.limit(2).size.must_equal 2
|
304
|
+
@dave.recommended_movies.where(:title => @movie3.title).must_include @movie3
|
305
|
+
@dave.recommended_movies.where(:title => @movie3.title).wont_include @movie2
|
306
|
+
@dave.recommended_movies.where(:title => @movie3.title).wont_include @movie4
|
307
|
+
@dave.recommended_movies.where(:title => @movie3.title).wont_include @movie5
|
308
|
+
end
|
309
|
+
|
258
310
|
it "should have common likes with a friend" do
|
259
311
|
@dave.like @movie1
|
260
312
|
@dave.like @movie2
|
@@ -295,6 +347,19 @@ class UserSpec < MiniTest::Spec
|
|
295
347
|
@dave.similar_raters.must_include @frank
|
296
348
|
@dave.recommended_movies.must_include @movie2
|
297
349
|
end
|
350
|
+
|
351
|
+
it "should return only the number of specified recommendations" do
|
352
|
+
@dave.like(@movie1)
|
353
|
+
@frank.like(@movie1)
|
354
|
+
@frank.like(@movie2)
|
355
|
+
@frank.like(@movie3)
|
356
|
+
@frank.like(@movie4)
|
357
|
+
@dave.send :update_similarities
|
358
|
+
@dave.send :update_recommendations
|
359
|
+
|
360
|
+
@dave.recommendations(2).size.must_equal 2
|
361
|
+
@dave.recommended_movies(1).size.must_equal 1
|
362
|
+
end
|
298
363
|
|
299
364
|
it "should order similar users by similarity" do
|
300
365
|
@dave.like(@movie1)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recommendable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sqlite3
|