recommendable 0.1.5 → 0.1.6
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 +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)
|