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.
@@ -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)