recommendable 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +20 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/LICENSE.txt +2 -0
- data/README.markdown +31 -254
- data/TODO +3 -0
- data/app/models/recommendable/dislike.rb +3 -2
- data/app/models/recommendable/ignore.rb +3 -2
- data/app/models/recommendable/like.rb +3 -2
- data/app/models/recommendable/stashed_item.rb +2 -1
- data/app/workers/recommendable/recommendation_refresher.rb +2 -3
- data/lib/generators/recommendable/templates/initializer.rb +6 -0
- data/lib/recommendable/acts_as_recommendable.rb +39 -7
- data/lib/recommendable/acts_as_recommended_to.rb +105 -82
- data/lib/recommendable/engine.rb +1 -0
- data/lib/recommendable/version.rb +1 -1
- data/recommendable.gemspec +1 -1
- data/spec/models/movie_spec.rb +69 -0
- data/spec/models/user_spec.rb +29 -1
- metadata +26 -25
@@ -1,8 +1,9 @@
|
|
1
1
|
module Recommendable
|
2
2
|
class StashedItem < ActiveRecord::Base
|
3
3
|
self.table_name = 'recommendable_stashed_items'
|
4
|
+
attr_accessible :user_id, :stashable_id, :stashable_type
|
4
5
|
|
5
|
-
belongs_to :user, :class_name => Recommendable.user_class.to_s
|
6
|
+
belongs_to :user, :class_name => Recommendable.user_class.to_s, :foreign_key => :user_id
|
6
7
|
belongs_to :stashable, :polymorphic => :true
|
7
8
|
|
8
9
|
validates :user_id, :uniqueness => { :scope => [:stashable_id, :stashable_type],
|
@@ -3,10 +3,9 @@ module Recommendable
|
|
3
3
|
include Resque::Plugins::UniqueJob
|
4
4
|
@queue = :recommendable
|
5
5
|
|
6
|
-
def self.perform(user_id
|
6
|
+
def self.perform(user_id)
|
7
7
|
user = Recommendable.user_class.find(user_id)
|
8
|
-
|
9
|
-
user.send :update_similarities, other_ids
|
8
|
+
user.send :update_similarities
|
10
9
|
user.send :update_recommendations
|
11
10
|
end
|
12
11
|
end
|
@@ -10,6 +10,12 @@ require "resque-loner"
|
|
10
10
|
# Connect to Redis via a UNIX socket instead
|
11
11
|
<% unless options.redis_socket %># <% end %>Recommendable.redis = Redis.new(:sock => "<%= options.redis_socket %>")
|
12
12
|
|
13
|
+
# Resque also needs a connection to Redis. If you are currently initializing
|
14
|
+
# Resque somewhere else, leave this commented out. Otherwise, let it use the
|
15
|
+
# same Redis connection as Recommendable. If redis is running on localhost:6379,
|
16
|
+
# You can leave this commented out.
|
17
|
+
# Resque.redis = Recommendable.redis
|
18
|
+
|
13
19
|
# Tell Redis which database to use (usually between 0 and 15). The default of 0
|
14
20
|
# is most likely okay unless you have another application using that database.
|
15
21
|
# Recommendable.redis.select "0"
|
@@ -9,17 +9,18 @@ module Recommendable
|
|
9
9
|
|
10
10
|
has_many :likes, :as => :likeable, :dependent => :destroy, :class_name => "Recommendable::Like"
|
11
11
|
has_many :dislikes, :as => :dislikeable, :dependent => :destroy, :class_name => "Recommendable::Dislike"
|
12
|
-
has_many :liked_by, :through => :likes, :source => :user
|
13
|
-
has_many :disliked_by, :through => :dislikes, :source => :user
|
14
12
|
has_many :ignores, :as => :ignoreable, :dependent => :destroy, :class_name => "Recommendable::Ignore"
|
15
13
|
has_many :stashes, :as => :stashable, :dependent => :destroy, :class_name => "Recommendable::StashedItem"
|
14
|
+
has_many :liked_by, :through => :likes, :source => :user, :foreign_key => :user_id
|
15
|
+
has_many :disliked_by, :through => :dislikes, :source => :user, :foreign_key => :user_id
|
16
16
|
|
17
17
|
include LikeableMethods
|
18
18
|
include DislikeableMethods
|
19
|
+
|
20
|
+
before_destroy :remove_from_scores
|
19
21
|
|
20
22
|
def self.acts_as_recommendable?() true end
|
21
23
|
|
22
|
-
|
23
24
|
def been_rated?
|
24
25
|
likes.count + dislikes.count > 0
|
25
26
|
end
|
@@ -30,7 +31,34 @@ module Recommendable
|
|
30
31
|
liked_by + disliked_by
|
31
32
|
end
|
32
33
|
|
34
|
+
def self.top(count=1)
|
35
|
+
ids = Recommendable.redis.zrevrange(self.score_set, 0, count - 1).map(&:to_i)
|
36
|
+
|
37
|
+
items = self.find(ids)
|
38
|
+
|
39
|
+
return items.sort do |x, y|
|
40
|
+
ids.index(x.id) <=> ids.index(y.id)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
33
44
|
private
|
45
|
+
|
46
|
+
def update_score
|
47
|
+
return 0 unless been_rated?
|
48
|
+
|
49
|
+
z = 1.96
|
50
|
+
n = likes.count + dislikes.count
|
51
|
+
|
52
|
+
phat = 1.0 * likes.count / n.to_f
|
53
|
+
score = (phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
|
54
|
+
|
55
|
+
Recommendable.redis.zadd self.class.score_set, score, self.id
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_from_scores
|
59
|
+
Recommendable.redis.zrem self.class.score_set, self.id
|
60
|
+
true
|
61
|
+
end
|
34
62
|
|
35
63
|
# Used for setup purposes. Calls convenience methods to create sets
|
36
64
|
# in redis of users that both like and dislike this object.
|
@@ -55,6 +83,10 @@ module Recommendable
|
|
55
83
|
likes.map(&:user_id) + dislikes.map(&:user_id)
|
56
84
|
end
|
57
85
|
|
86
|
+
def self.score_set
|
87
|
+
"#{self}:sorted"
|
88
|
+
end
|
89
|
+
|
58
90
|
private :likes, :dislikes, :ignores, :stashes
|
59
91
|
end
|
60
92
|
end
|
@@ -77,8 +109,8 @@ module Recommendable
|
|
77
109
|
# @private
|
78
110
|
# @return [String] the key in Redis pointing to the set
|
79
111
|
def create_liked_by_set
|
80
|
-
set = "#{
|
81
|
-
liked_by.each {|rater| Recommendable.redis.sadd set, rater.id}
|
112
|
+
set = "#{redis_key}:liked_by"
|
113
|
+
liked_by.each { |rater| Recommendable.redis.sadd set, rater.id }
|
82
114
|
return set
|
83
115
|
end
|
84
116
|
end
|
@@ -91,8 +123,8 @@ module Recommendable
|
|
91
123
|
# @private
|
92
124
|
# @return [String] the key in Redis pointing to the set
|
93
125
|
def create_disliked_by_set
|
94
|
-
set = "#{
|
95
|
-
disliked_by.each {|rater| Recommendable.redis.sadd set, rater.id}
|
126
|
+
set = "#{redis_key}:disliked_by"
|
127
|
+
disliked_by.each { |rater| Recommendable.redis.sadd set, rater.id }
|
96
128
|
return set
|
97
129
|
end
|
98
130
|
end
|
@@ -14,10 +14,10 @@ module Recommendable
|
|
14
14
|
class_eval do
|
15
15
|
Recommendable.user_class = self
|
16
16
|
|
17
|
-
has_many :likes, :class_name => "Recommendable::Like", :dependent => :destroy
|
18
|
-
has_many :dislikes, :class_name => "Recommendable::Dislike", :dependent => :destroy
|
19
|
-
has_many :ignores, :class_name => "Recommendable::Ignore", :dependent => :destroy
|
20
|
-
has_many :stashed_items, :class_name => "Recommendable::StashedItem", :dependent => :destroy
|
17
|
+
has_many :likes, :class_name => "Recommendable::Like", :dependent => :destroy, :foreign_key => :user_id
|
18
|
+
has_many :dislikes, :class_name => "Recommendable::Dislike", :dependent => :destroy, :foreign_key => :user_id
|
19
|
+
has_many :ignores, :class_name => "Recommendable::Ignore", :dependent => :destroy, :foreign_key => :user_id
|
20
|
+
has_many :stashed_items, :class_name => "Recommendable::StashedItem", :dependent => :destroy, :foreign_key => :user_id
|
21
21
|
|
22
22
|
include LikeMethods
|
23
23
|
include DislikeMethods
|
@@ -25,6 +25,35 @@ module Recommendable
|
|
25
25
|
include IgnoreMethods
|
26
26
|
include RecommendationMethods
|
27
27
|
|
28
|
+
before_destroy :remove_from_similarities
|
29
|
+
before_destroy :remove_recommendations
|
30
|
+
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
if method.to_s =~ /(liked|disliked|ignored|stashed|recommended)_(.+)/
|
33
|
+
begin
|
34
|
+
super unless $2.classify.constantize.acts_as_recommendable?
|
35
|
+
|
36
|
+
self.send "#{$1}_for", $2.classify.constantize
|
37
|
+
rescue NameError
|
38
|
+
super
|
39
|
+
end
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to?(method)
|
46
|
+
if method.to_s =~ /(liked|disliked|ignored|stashed|recommended)_(.+)/
|
47
|
+
begin
|
48
|
+
$2.classify.constantize.acts_as_recommendable?
|
49
|
+
rescue NameError
|
50
|
+
false
|
51
|
+
end
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
28
57
|
private :likes, :dislikes, :ignores, :stashed_items
|
29
58
|
end
|
30
59
|
end
|
@@ -41,10 +70,11 @@ module Recommendable
|
|
41
70
|
# @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
42
71
|
def like(object)
|
43
72
|
raise RecordNotRecommendableError unless object.recommendable?
|
44
|
-
return if likes?
|
45
|
-
completely_unrecommend
|
46
|
-
likes.create!
|
47
|
-
|
73
|
+
return if likes? object
|
74
|
+
completely_unrecommend object
|
75
|
+
likes.create! :likeable_id => object.id, :likeable_type => object.class.to_s
|
76
|
+
object.send :update_score
|
77
|
+
Resque.enqueue RecommendationRefresher, self.id
|
48
78
|
true
|
49
79
|
end
|
50
80
|
|
@@ -53,7 +83,7 @@ module Recommendable
|
|
53
83
|
# @param [Object] object the object you want to check
|
54
84
|
# @return true if self likes object, false if not
|
55
85
|
def likes?(object)
|
56
|
-
likes.exists?
|
86
|
+
likes.exists? :likeable_id => object.id, :likeable_type => object.class.to_s
|
57
87
|
end
|
58
88
|
|
59
89
|
# Destroys a Recommendable::Like currently associating self with object
|
@@ -62,7 +92,8 @@ module Recommendable
|
|
62
92
|
# @return true if object is unliked, nil if nothing happened
|
63
93
|
def unlike(object)
|
64
94
|
if likes.where(:likeable_id => object.id, :likeable_type => object.class.to_s).first.try(:destroy)
|
65
|
-
|
95
|
+
object.send :update_score
|
96
|
+
Resque.enqueue RecommendationRefresher, self.id
|
66
97
|
true
|
67
98
|
end
|
68
99
|
end
|
@@ -71,20 +102,20 @@ module Recommendable
|
|
71
102
|
|
72
103
|
# @return [Array] an array of ActiveRecord objects that self has liked
|
73
104
|
def liked
|
74
|
-
|
105
|
+
Recommendable.recommendable_classes.flat_map { |klass| liked_for klass }
|
75
106
|
end
|
76
107
|
|
108
|
+
private
|
109
|
+
|
77
110
|
# Get a list of records belonging to a passed class that self currently
|
78
111
|
# likes.
|
79
112
|
#
|
80
113
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
81
114
|
# @return [Array] an array of ActiveRecord objects that self has liked belonging to klass
|
82
115
|
def liked_for(klass)
|
83
|
-
|
116
|
+
likes.where(:likeable_type => klass).includes(:likeable).map(&:likeable)
|
84
117
|
end
|
85
118
|
|
86
|
-
private
|
87
|
-
|
88
119
|
# Get a list of Recommendable::Likes with a `#likeable_type` of the passed
|
89
120
|
# class.
|
90
121
|
#
|
@@ -92,7 +123,7 @@ module Recommendable
|
|
92
123
|
# @note You should not need to use this method. (see {#liked_for})
|
93
124
|
# @private
|
94
125
|
def likes_for(klass)
|
95
|
-
likes.where
|
126
|
+
likes.where :likeable_type => klass.to_s.classify
|
96
127
|
end
|
97
128
|
end
|
98
129
|
|
@@ -107,10 +138,11 @@ module Recommendable
|
|
107
138
|
# @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
108
139
|
def dislike(object)
|
109
140
|
raise RecordNotRecommendableError unless object.recommendable?
|
110
|
-
return if dislikes?
|
111
|
-
completely_unrecommend
|
112
|
-
dislikes.create!
|
113
|
-
|
141
|
+
return if dislikes? object
|
142
|
+
completely_unrecommend object
|
143
|
+
dislikes.create! :dislikeable_id => object.id, :dislikeable_type => object.class.to_s
|
144
|
+
object.send :update_score
|
145
|
+
Resque.enqueue RecommendationRefresher, self.id
|
114
146
|
true
|
115
147
|
end
|
116
148
|
|
@@ -128,7 +160,8 @@ module Recommendable
|
|
128
160
|
# @return true if object is removed from self's dislikes, nil if nothing happened
|
129
161
|
def undislike(object)
|
130
162
|
if dislikes.where(:dislikeable_id => object.id, :dislikeable_type => object.class.to_s).first.try(:destroy)
|
131
|
-
|
163
|
+
object.send :update_score
|
164
|
+
Resque.enqueue RecommendationRefresher, self.id
|
132
165
|
true
|
133
166
|
end
|
134
167
|
end
|
@@ -137,19 +170,19 @@ module Recommendable
|
|
137
170
|
|
138
171
|
# @return [Array] an array of ActiveRecord objects that self has disliked
|
139
172
|
def disliked
|
140
|
-
|
173
|
+
Recommendable.recommendable_classes.flat_map { |klass| disliked_for(klass) }
|
141
174
|
end
|
142
175
|
|
176
|
+
private
|
177
|
+
|
143
178
|
# Get a list of records belonging to a passed class that self currently
|
144
179
|
# dislikes.
|
145
180
|
#
|
146
181
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
147
182
|
# @return [Array] an array of ActiveRecord objects that self has disliked belonging to klass
|
148
183
|
def disliked_for(klass)
|
149
|
-
|
184
|
+
dislikes.where(:dislikeable_type => klass).includes(:dislikeable).map(&:dislikeable)
|
150
185
|
end
|
151
|
-
|
152
|
-
private
|
153
186
|
|
154
187
|
# Get a list of Recommendable::Dislikes with a `#dislikeable_type` of the
|
155
188
|
# passed class.
|
@@ -158,7 +191,7 @@ module Recommendable
|
|
158
191
|
# @note You should not need to use this method. (see {#disliked_for})
|
159
192
|
# @private
|
160
193
|
def dislikes_for(klass)
|
161
|
-
dislikes.where
|
194
|
+
dislikes.where :dislikeable_type => klass.to_s.classify
|
162
195
|
end
|
163
196
|
end
|
164
197
|
|
@@ -174,9 +207,9 @@ module Recommendable
|
|
174
207
|
def stash(object)
|
175
208
|
raise RecordNotRecommendableError unless object.recommendable?
|
176
209
|
return if rated?(object) || stashed?(object)
|
177
|
-
unignore
|
178
|
-
unpredict
|
179
|
-
stashed_items.create!
|
210
|
+
unignore object
|
211
|
+
unpredict object
|
212
|
+
stashed_items.create! :stashable_id => object.id, :stashable_type => object.class.to_s
|
180
213
|
true
|
181
214
|
end
|
182
215
|
|
@@ -185,7 +218,7 @@ module Recommendable
|
|
185
218
|
# @param [Object] object the object you want to check
|
186
219
|
# @return true if self has stashed object, false if not
|
187
220
|
def stashed?(object)
|
188
|
-
stashed_items.exists?
|
221
|
+
stashed_items.exists? :stashable_id => object.id, :stashable_type => object.class.to_s
|
189
222
|
end
|
190
223
|
|
191
224
|
# Destroys a Recommendable::StashedItem currently associating self with object
|
@@ -200,28 +233,18 @@ module Recommendable
|
|
200
233
|
#
|
201
234
|
# @return [Array] an array of ActiveRecord objects that self has stashed
|
202
235
|
def stashed
|
203
|
-
|
236
|
+
Recommendable.recommendable_classes.flat_map { |klass| stashed_for klass }
|
204
237
|
end
|
205
238
|
|
239
|
+
private
|
240
|
+
|
206
241
|
# Get a list of records belonging to a passed class that self currently
|
207
242
|
# has stashed away for later.
|
208
243
|
#
|
209
244
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
210
245
|
# @return [Array] an array of ActiveRecord objects that self has stashed belonging to klass
|
211
246
|
def stashed_for(klass)
|
212
|
-
|
213
|
-
end
|
214
|
-
|
215
|
-
private
|
216
|
-
|
217
|
-
# Get a list of Recommendable::StashedItems with a stashable_type of the
|
218
|
-
# passed class.
|
219
|
-
#
|
220
|
-
# @param [Class, String, Symbol] klass the class for which you would like to return self's stashed items. Can be the class constant, or a String/Symbol representation of the class name.
|
221
|
-
# @note You should not need to use this method. (see {#stashed_for})
|
222
|
-
# @private
|
223
|
-
def stash_for(klass)
|
224
|
-
stashed_items.where(:stashable_type => klass.to_s.classify)
|
247
|
+
stashed_items.where(:stashable_type => klass).includes(:stashable).map(&:stashable)
|
225
248
|
end
|
226
249
|
end
|
227
250
|
|
@@ -236,9 +259,9 @@ module Recommendable
|
|
236
259
|
# @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
237
260
|
def ignore(object)
|
238
261
|
raise RecordNotRecommendableError unless object.recommendable?
|
239
|
-
return if ignored?
|
240
|
-
completely_unrecommend
|
241
|
-
ignores.create!
|
262
|
+
return if ignored? object
|
263
|
+
completely_unrecommend object
|
264
|
+
ignores.create! :ignoreable_id => object.id, :ignoreable_type => object.class.to_s
|
242
265
|
true
|
243
266
|
end
|
244
267
|
|
@@ -247,7 +270,7 @@ module Recommendable
|
|
247
270
|
# @param [Object] object the object you want to check
|
248
271
|
# @return true if self has ignored object, false if not
|
249
272
|
def ignored?(object)
|
250
|
-
ignores.exists?
|
273
|
+
ignores.exists? :ignoreable_id => object.id, :ignoreable_type => object.class.to_s
|
251
274
|
end
|
252
275
|
|
253
276
|
# Destroys a Recommendable::Ignore currently associating self with object
|
@@ -262,28 +285,18 @@ module Recommendable
|
|
262
285
|
|
263
286
|
# @return [Array] an array of ActiveRecord objects that self has ignored
|
264
287
|
def ignored
|
265
|
-
|
288
|
+
Recommendable.recommendable_classes.flat_map { |klass| ignored_for klass }
|
266
289
|
end
|
267
290
|
|
291
|
+
private
|
292
|
+
|
268
293
|
# Get a list of records belonging to a passed class that self is
|
269
294
|
# currently ignoring.
|
270
295
|
#
|
271
296
|
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
272
297
|
# @return [Array] an array of ActiveRecord objects that self has ignored belonging to klass
|
273
298
|
def ignored_for(klass)
|
274
|
-
|
275
|
-
end
|
276
|
-
|
277
|
-
private
|
278
|
-
|
279
|
-
# Get a list of Recommendable::Ignores with a `#ignoreable_type` of the
|
280
|
-
# passed class.
|
281
|
-
#
|
282
|
-
# @param [Class, String, Symbol] klass the class for which you would like to return self's ignores. Can be the class constant, or a String/Symbol representation of the class name.
|
283
|
-
# @note You should not need to use this method. (see {#ignored_for})
|
284
|
-
# @private
|
285
|
-
def ignores_for(klass)
|
286
|
-
ignores.where(:ignoreable_type => klass.to_s.classify)
|
299
|
+
ignores.where(:ignoreable_type => klass).includes(:ignoreable).map(&:ignoreable)
|
287
300
|
end
|
288
301
|
end
|
289
302
|
|
@@ -312,10 +325,10 @@ module Recommendable
|
|
312
325
|
# @return [Array] An array of instances of your user class
|
313
326
|
def similar_raters(options = {})
|
314
327
|
defaults = { :count => 10 }
|
315
|
-
options = defaults.merge
|
328
|
+
options = defaults.merge options
|
316
329
|
|
317
330
|
rater_ids = Recommendable.redis.zrevrange(similarity_set, 0, options[:count] - 1).map(&:to_i)
|
318
|
-
raters = Recommendable.user_class.
|
331
|
+
raters = Recommendable.user_class.find rater_ids
|
319
332
|
|
320
333
|
# The query loses the ordering, so...
|
321
334
|
return raters.sort do |x, y|
|
@@ -331,14 +344,12 @@ module Recommendable
|
|
331
344
|
# @option options [Fixnum] :count (10) the number of recommendations to get
|
332
345
|
# @return [Array] an array of ActiveRecord objects that are recommendable
|
333
346
|
def recommendations(options = {})
|
334
|
-
defaults = { :count => 10 }
|
335
|
-
options = defaults.merge options
|
336
347
|
return [] if likes.count + dislikes.count == 0
|
337
348
|
|
338
349
|
unioned_predictions = "#{self.class}:#{id}:predictions"
|
339
|
-
Recommendable.redis.zunionstore unioned_predictions, Recommendable.recommendable_classes.map {|klass| predictions_set_for(klass)}
|
350
|
+
Recommendable.redis.zunionstore unioned_predictions, Recommendable.recommendable_classes.map { |klass| predictions_set_for(klass) }
|
340
351
|
|
341
|
-
recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0,
|
352
|
+
recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0, 10).map do |object|
|
342
353
|
klass, id = object.split(":")
|
343
354
|
klass.constantize.find(id)
|
344
355
|
end
|
@@ -346,23 +357,18 @@ module Recommendable
|
|
346
357
|
Recommendable.redis.del(unioned_predictions) and return recommendations
|
347
358
|
end
|
348
359
|
|
349
|
-
# Get a list of recommendations for self on a single recommendable type.
|
360
|
+
# Get a list of 10 recommendations for self on a single recommendable type.
|
350
361
|
# Recommendations are returned in a descending order with the first index
|
351
362
|
# being the object that self has been found most likely to enjoy.
|
352
363
|
#
|
353
364
|
# @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.
|
354
|
-
# @param [Hash] options the options for returning this list
|
355
|
-
# @option options [Fixnum] :count (10) the number of recommendations to get
|
356
365
|
# @return [Array] an array of ActiveRecord objects that are recommendable
|
357
|
-
def
|
358
|
-
defaults = { :count => 10 }
|
359
|
-
options = defaults.merge options
|
360
|
-
|
366
|
+
def recommended_for(klass)
|
361
367
|
return [] if likes_for(klass).count + dislikes_for(klass).count == 0 || Recommendable.redis.zcard(predictions_set_for(klass)) == 0
|
362
368
|
|
363
369
|
recommendations = []
|
364
370
|
i = 0
|
365
|
-
until recommendations.size ==
|
371
|
+
until recommendations.size == 10
|
366
372
|
prediction = Recommendable.redis.zrevrange(predictions_set_for(klass), i, i).first
|
367
373
|
return recommendations unless prediction # User might not have enough recommendations to return
|
368
374
|
|
@@ -399,7 +405,7 @@ module Recommendable
|
|
399
405
|
# @param [Hash] options the options for this intersection
|
400
406
|
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersection to a single recommendable type. By default, all recomendable types are considered
|
401
407
|
# @option options [true, false] :return_records (true) Return the actual Model instances
|
402
|
-
# @return [Array]
|
408
|
+
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
403
409
|
def common_likes_with(rater, options = {})
|
404
410
|
defaults = { :class => nil,
|
405
411
|
:return_records => true }
|
@@ -432,7 +438,7 @@ module Recommendable
|
|
432
438
|
# @param [Hash] options the options for this intersection
|
433
439
|
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersection to a single recommendable type. By default, all recomendable types are considered
|
434
440
|
# @option options [true, false] :return_records (true) Return the actual Model instances
|
435
|
-
# @return [Array]
|
441
|
+
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
436
442
|
def common_dislikes_with(rater, options = {})
|
437
443
|
defaults = { :class => nil,
|
438
444
|
:return_records => true }
|
@@ -467,7 +473,7 @@ module Recommendable
|
|
467
473
|
# @param [Hash] options the options for this intersection
|
468
474
|
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersections to a single recommendable type. By default, all recomendable types are considered
|
469
475
|
# @option options [true, false] :return_records (true) Return the actual Model instances
|
470
|
-
# @return [Array]
|
476
|
+
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
471
477
|
def disagreements_with(rater, options = {})
|
472
478
|
defaults = { :class => nil,
|
473
479
|
:return_records => true }
|
@@ -620,18 +626,35 @@ module Recommendable
|
|
620
626
|
# other users. This is called in the Resque job to refresh recommendations.
|
621
627
|
#
|
622
628
|
# @private
|
623
|
-
def update_similarities
|
629
|
+
def update_similarities
|
624
630
|
return unless rated_anything?
|
625
631
|
create_recommended_to_sets
|
626
|
-
rater_ids ||= Recommendable.user_class.select(:id).map!(&:id)
|
627
632
|
|
628
|
-
Recommendable.user_class.
|
629
|
-
next if self == rater
|
633
|
+
Recommendable.user_class.find_each do |rater|
|
634
|
+
next if self == rater || !rater.rated_anything?
|
630
635
|
Recommendable.redis.zadd similarity_set, similarity_with(rater), rater.id
|
631
636
|
end
|
632
637
|
|
633
638
|
destroy_recommended_to_sets
|
634
639
|
end
|
640
|
+
|
641
|
+
def remove_from_similarities
|
642
|
+
Recommendable.redis.del similarity_set
|
643
|
+
|
644
|
+
Recommendable.user_class.find_each do |user|
|
645
|
+
Recommendable.redis.zrem user.send(:similarity_set), self.id
|
646
|
+
end
|
647
|
+
|
648
|
+
true
|
649
|
+
end
|
650
|
+
|
651
|
+
def remove_recommendations
|
652
|
+
Recommendable.recommendable_classes.each do |klass|
|
653
|
+
Recommendable.redis.del predictions_set_for(klass)
|
654
|
+
end
|
655
|
+
|
656
|
+
true
|
657
|
+
end
|
635
658
|
|
636
659
|
# @private
|
637
660
|
def unpredict(object)
|