recommendable 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, other_ids)
6
+ def self.perform(user_id)
7
7
  user = Recommendable.user_class.find(user_id)
8
- return if other_ids.empty?
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 = "#{self.class}:#{id}:liked_by"
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 = "#{self.class}:#{id}:disliked_by"
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?(object)
45
- completely_unrecommend(object)
46
- likes.create!(:likeable_id => object.id, :likeable_type => object.class.to_s)
47
- Resque.enqueue RecommendationRefresher, self.id, object.send(:rates_by)
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?(:likeable_id => object.id, :likeable_type => object.class.to_s)
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
- Resque.enqueue RecommendationRefresher, self.id, object.send(:rates_by)
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
- likes.map {|like| like.likeable}
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
- klass.to_s.classify.constantize.find likes_for(klass).map(&:likeable_id)
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(:likeable_type => klass.to_s.classify)
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?(object)
111
- completely_unrecommend(object)
112
- dislikes.create!(:dislikeable_id => object.id, :dislikeable_type => object.class.to_s)
113
- Resque.enqueue RecommendationRefresher, self.id, object.send(:rates_by)
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
- Resque.enqueue RecommendationRefresher, self.id, object.send(:rates_by)
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
- dislikes.map {|dislike| dislike.dislikeable}
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
- klass.to_s.classify.constantize.find dislikes_for(klass).map(&:dislikeable_id)
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(:dislikeable_type => klass.to_s.classify)
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(object)
178
- unpredict(object)
179
- stashed_items.create!(:stashable_id => object.id, :stashable_type => object.class.to_s)
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?(:stashable_id => object.id, :stashable_type => object.class.to_s)
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
- stashed_items.map {|item| item.stashable}
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
- klass.to_s.classify.constantize.find stash_for(klass).map(&:stashable_id)
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?(object)
240
- completely_unrecommend(object)
241
- ignores.create!(:ignoreable_id => object.id, :ignoreable_type => object.class.to_s)
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?(:ignoreable_id => object.id, :ignoreable_type => object.class.to_s)
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
- ignores.map {|ignore| ignore.ignoreable}
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
- klass.to_s.classify.constantize.find ignores_for(klass).map(&:ignoreable_id)
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(options)
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.where("ID IN (?)", rater_ids)
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, options[:count]).map do |object|
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 recommendations_for(klass, options = {})
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 == options[:count]
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] An array of IDs, or strings from Redis in the form of "#{likeable_type}:#{id}" if options[:class] is set
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] An array of IDs, or strings from Redis in the form of #{dislikeable_type}:#{id}" if options[:class] is set
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] An array of IDs, or strings from Redis in the form of #{recommendable_type}:#{id}" if options[:class] is set
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(rater_ids = nil)
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.find(rater_ids).each do |rater|
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)