recommendable 1.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/recommendable.rb +38 -26
- data/lib/recommendable/configuration.rb +47 -0
- data/lib/recommendable/helpers.rb +3 -9
- data/lib/recommendable/helpers/calculations.rb +150 -0
- data/lib/recommendable/helpers/queriers.rb +23 -0
- data/lib/recommendable/helpers/redis_key_mapper.rb +29 -0
- data/lib/recommendable/orm/active_record.rb +6 -0
- data/lib/recommendable/orm/data_mapper.rb +7 -0
- data/lib/recommendable/orm/mongo_mapper.rb +8 -0
- data/lib/recommendable/orm/mongoid.rb +7 -0
- data/lib/recommendable/ratable.rb +83 -0
- data/lib/recommendable/ratable/dislikable.rb +26 -0
- data/lib/recommendable/ratable/likable.rb +26 -0
- data/lib/recommendable/rater.rb +109 -0
- data/lib/recommendable/rater/bookmarker.rb +120 -0
- data/lib/recommendable/rater/disliker.rb +122 -0
- data/lib/recommendable/rater/hider.rb +120 -0
- data/lib/recommendable/rater/liker.rb +122 -0
- data/lib/recommendable/rater/recommender.rb +68 -0
- data/lib/recommendable/version.rb +5 -4
- data/lib/recommendable/workers/delayed_job.rb +16 -0
- data/lib/recommendable/workers/rails.rb +16 -0
- data/lib/recommendable/workers/resque.rb +13 -0
- data/lib/recommendable/workers/sidekiq.rb +13 -0
- metadata +62 -131
- data/.gitignore +0 -57
- data/.travis.yml +0 -3
- data/CHANGELOG.markdown +0 -159
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -112
- data/LICENSE.txt +0 -22
- data/README.markdown +0 -135
- data/Rakefile +0 -26
- data/TODO +0 -7
- data/app/models/recommendable/dislike.rb +0 -19
- data/app/models/recommendable/ignore.rb +0 -19
- data/app/models/recommendable/like.rb +0 -19
- data/app/models/recommendable/stash.rb +0 -19
- data/app/workers/recommendable/delayed_job_worker.rb +0 -17
- data/app/workers/recommendable/rails_worker.rb +0 -17
- data/app/workers/recommendable/resque_worker.rb +0 -14
- data/app/workers/recommendable/sidekiq_worker.rb +0 -14
- data/config/routes.rb +0 -3
- data/db/migrate/20120124193723_create_likes.rb +0 -17
- data/db/migrate/20120124193728_create_dislikes.rb +0 -17
- data/db/migrate/20120127092558_create_ignores.rb +0 -17
- data/db/migrate/20120131173909_create_stashes.rb +0 -17
- data/lib/generators/recommendable/USAGE +0 -8
- data/lib/generators/recommendable/install_generator.rb +0 -40
- data/lib/generators/recommendable/templates/initializer.rb +0 -28
- data/lib/recommendable/acts_as_recommendable.rb +0 -176
- data/lib/recommendable/acts_as_recommended_to.rb +0 -774
- data/lib/recommendable/engine.rb +0 -14
- data/lib/recommendable/exceptions.rb +0 -4
- data/lib/recommendable/railtie.rb +0 -6
- data/lib/sidekiq/middleware/client/unique_jobs.rb +0 -37
- data/lib/sidekiq/middleware/server/unique_jobs.rb +0 -17
- data/lib/tasks/recommendable_tasks.rake +0 -1
- data/recommendable.gemspec +0 -30
- data/script/rails +0 -8
- data/spec/configuration_spec.rb +0 -9
- data/spec/dummy/README.rdoc +0 -261
- data/spec/dummy/Rakefile +0 -7
- data/spec/dummy/app/assets/javascripts/application.js +0 -15
- data/spec/dummy/app/assets/stylesheets/application.css +0 -13
- data/spec/dummy/app/controllers/application_controller.rb +0 -3
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/bully.rb +0 -2
- data/spec/dummy/app/models/movie.rb +0 -2
- data/spec/dummy/app/models/php_framework.rb +0 -2
- data/spec/dummy/app/models/user.rb +0 -3
- data/spec/dummy/app/views/layouts/application.html.erb +0 -14
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -56
- data/spec/dummy/config/boot.rb +0 -10
- data/spec/dummy/config/database.yml +0 -25
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -37
- data/spec/dummy/config/environments/production.rb +0 -67
- data/spec/dummy/config/environments/test.rb +0 -37
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/inflections.rb +0 -15
- data/spec/dummy/config/initializers/mime_types.rb +0 -5
- data/spec/dummy/config/initializers/recommendable.rb +0 -14
- data/spec/dummy/config/initializers/secret_token.rb +0 -7
- data/spec/dummy/config/initializers/session_store.rb +0 -8
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/en.yml +0 -5
- data/spec/dummy/config/routes.rb +0 -4
- data/spec/dummy/db/migrate/20120128005553_create_likes.recommendable.rb +0 -18
- data/spec/dummy/db/migrate/20120128005554_create_dislikes.recommendable.rb +0 -18
- data/spec/dummy/db/migrate/20120128005555_create_ignores.recommendable.rb +0 -18
- data/spec/dummy/db/migrate/20120128020228_create_users.rb +0 -9
- data/spec/dummy/db/migrate/20120128020413_create_movies.rb +0 -10
- data/spec/dummy/db/migrate/20120128024632_create_php_frameworks.rb +0 -9
- data/spec/dummy/db/migrate/20120128024804_create_bullies.rb +0 -9
- data/spec/dummy/db/migrate/20120131195416_create_stashes.recommendable.rb +0 -19
- data/spec/dummy/db/schema.rb +0 -89
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +0 -26
- data/spec/dummy/public/422.html +0 -26
- data/spec/dummy/public/500.html +0 -25
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/recommendable_dummy_development +0 -0
- data/spec/dummy/recommendable_dummy_test +0 -0
- data/spec/dummy/script/rails +0 -6
- data/spec/factories.rb +0 -16
- data/spec/models/dislike_spec.rb +0 -41
- data/spec/models/ignore_spec.rb +0 -27
- data/spec/models/like_spec.rb +0 -42
- data/spec/models/movie_spec.rb +0 -82
- data/spec/models/stash_spec.rb +0 -27
- data/spec/models/user_benchmark_spec.rb +0 -49
- data/spec/models/user_spec.rb +0 -443
- data/spec/spec_helper.rb +0 -28
|
@@ -1,774 +0,0 @@
|
|
|
1
|
-
require 'active_support/concern'
|
|
2
|
-
|
|
3
|
-
module Recommendable
|
|
4
|
-
module ActsAsRecommendedTo
|
|
5
|
-
include Recommendable::Helpers
|
|
6
|
-
extend ActiveSupport::Concern
|
|
7
|
-
|
|
8
|
-
module ClassMethods
|
|
9
|
-
def recommends *things
|
|
10
|
-
acts_as_recommended_to
|
|
11
|
-
things.each { |thing| thing.to_s.classify.constantize.acts_as_recommendable }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def acts_as_recommended_to
|
|
15
|
-
class_eval do
|
|
16
|
-
Recommendable.user_class = self
|
|
17
|
-
|
|
18
|
-
has_many :likes, :class_name => "Recommendable::Like", :dependent => :destroy, :foreign_key => :user_id
|
|
19
|
-
has_many :dislikes, :class_name => "Recommendable::Dislike", :dependent => :destroy, :foreign_key => :user_id
|
|
20
|
-
has_many :ignores, :class_name => "Recommendable::Ignore", :dependent => :destroy, :foreign_key => :user_id
|
|
21
|
-
has_many :stashed_items, :class_name => "Recommendable::Stash", :dependent => :destroy, :foreign_key => :user_id
|
|
22
|
-
|
|
23
|
-
include LikeMethods
|
|
24
|
-
include DislikeMethods
|
|
25
|
-
include StashMethods
|
|
26
|
-
include IgnoreMethods
|
|
27
|
-
include RecommendationMethods
|
|
28
|
-
include Hooks
|
|
29
|
-
|
|
30
|
-
before_destroy :remove_from_similarities, :remove_recommendations
|
|
31
|
-
|
|
32
|
-
define_hooks :before_like, :after_like, :before_unlike, :after_unlike,
|
|
33
|
-
:before_dislike, :after_dislike, :before_undislike, :after_undislike,
|
|
34
|
-
:before_stash, :after_stash, :before_unstash, :after_unstash,
|
|
35
|
-
:before_ignore, :after_ignore, :before_unignore, :after_unignore
|
|
36
|
-
|
|
37
|
-
%w(like dislike ignore).each do |action|
|
|
38
|
-
send "before_#{action}", lambda { |obj| completely_unrecommend obj }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
%w(like unlike dislike undislike).each do |action|
|
|
42
|
-
send "after_#{action}", lambda { |obj|
|
|
43
|
-
obj.send(:update_score)
|
|
44
|
-
obj.send "update_#{action.gsub('un', '')}_count"
|
|
45
|
-
Recommendable.enqueue(self.id)
|
|
46
|
-
}
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
before_stash { |obj| unignore(obj) and unpredict(obj) }
|
|
50
|
-
|
|
51
|
-
def method_missing method, *args, &block
|
|
52
|
-
if method.to_s =~ /^(liked|disliked)_(.+)_in_common_with$/
|
|
53
|
-
begin
|
|
54
|
-
super unless $2.classify.constantize.acts_as_recommendable?
|
|
55
|
-
|
|
56
|
-
self.send "#{$1}_in_common_with", *args, { :class => $2.classify.constantize }
|
|
57
|
-
rescue NameError
|
|
58
|
-
super
|
|
59
|
-
end
|
|
60
|
-
elsif method.to_s =~ /^(liked|disliked|ignored|stashed|recommended)_(.+)$/
|
|
61
|
-
begin
|
|
62
|
-
super unless $2.classify.constantize.acts_as_recommendable?
|
|
63
|
-
|
|
64
|
-
self.send "#{$1}_for", $2.classify.constantize, *args
|
|
65
|
-
rescue NameError
|
|
66
|
-
super
|
|
67
|
-
end
|
|
68
|
-
else
|
|
69
|
-
super
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def respond_to? method, include_private = false
|
|
74
|
-
if method.to_s =~ /^(liked|disliked|ignored|stashed|recommended)_(.+)$/ || method.to_s =~ /^common_(liked|disliked)_(.+)_with$/
|
|
75
|
-
begin
|
|
76
|
-
$2.classify.constantize.acts_as_recommendable?
|
|
77
|
-
rescue NameError
|
|
78
|
-
false
|
|
79
|
-
end
|
|
80
|
-
else
|
|
81
|
-
super
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
private :likes, :dislikes, :ignores, :stashed_items
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
module LikeMethods
|
|
91
|
-
# Creates a Recommendable::Like to associate self to a passed object. If
|
|
92
|
-
# self is currently found to have disliked object, the corresponding
|
|
93
|
-
# Recommendable::Dislike will be destroyed. It will also be removed from
|
|
94
|
-
# the user's stash or ignores.
|
|
95
|
-
#
|
|
96
|
-
# @param [Object] object the object you want self to like.
|
|
97
|
-
# @return true if object has been liked
|
|
98
|
-
# @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
|
99
|
-
def like object
|
|
100
|
-
raise UnrecommendableError unless object.recommendable?
|
|
101
|
-
return if likes? object
|
|
102
|
-
|
|
103
|
-
run_hook :before_like, object
|
|
104
|
-
likes.create! :likeable_id => object.id, :likeable_type => object.class
|
|
105
|
-
run_hook :after_like, object
|
|
106
|
-
|
|
107
|
-
true
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Checks to see if self has already liked a passed object.
|
|
111
|
-
#
|
|
112
|
-
# @param [Object] object the object you want to check
|
|
113
|
-
# @return true if self likes object, false if not
|
|
114
|
-
def likes? object
|
|
115
|
-
likes.exists? :likeable_id => object.id, :likeable_type => object.class.base_class.to_s
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Destroys a Recommendable::Like currently associating self with object
|
|
119
|
-
#
|
|
120
|
-
# @param [Object] object the object you want to remove from self's likes
|
|
121
|
-
# @return true if object is unliked, nil if nothing happened
|
|
122
|
-
def unlike object
|
|
123
|
-
like = likes.where(:likeable_id => object.id, :likeable_type => object.class.base_class.to_s)
|
|
124
|
-
if like.exists?
|
|
125
|
-
run_hook :before_unlike, object
|
|
126
|
-
like.first.destroy
|
|
127
|
-
run_hook :after_unlike, object
|
|
128
|
-
true
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Get a list of records that self currently likes
|
|
133
|
-
|
|
134
|
-
# @return [Array] an array of ActiveRecord objects that self has liked
|
|
135
|
-
def liked
|
|
136
|
-
Recommendable.recommendable_classes.map { |klass| liked_for klass }.flatten
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
# Get a list of records belonging to a passed class that self currently
|
|
142
|
-
# likes.
|
|
143
|
-
#
|
|
144
|
-
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
|
145
|
-
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has liked
|
|
146
|
-
def liked_for klass
|
|
147
|
-
ids = if klass.sti?
|
|
148
|
-
likes.joins(manual_join(klass, 'like')).map(&:likeable_id)
|
|
149
|
-
else
|
|
150
|
-
likes.where(:likeable_type => klass.to_s).map(&:likeable_id)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
klass.where('ID IN (?)', ids)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Get a list of Recommendable::Likes with a `#likeable_type` of the passed
|
|
157
|
-
# class.
|
|
158
|
-
#
|
|
159
|
-
# @param [Class, String, Symbol] klass the class for which you would like to return self's likes. Can be the class constant, or a String/Symbol representation of the class name.
|
|
160
|
-
# @note You should not need to use this method. (see {#liked_for})
|
|
161
|
-
# @private
|
|
162
|
-
def likes_for klass
|
|
163
|
-
if klass.sti?
|
|
164
|
-
likes.joins manual_join(klass, 'like')
|
|
165
|
-
else
|
|
166
|
-
likes.where(:likeable_type => klass.to_s)
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
module DislikeMethods
|
|
172
|
-
# Creates a Recommendable::Dislike to associate self to a passed object. If
|
|
173
|
-
# self is currently found to have liked object, the corresponding
|
|
174
|
-
# Recommendable::Like will be destroyed. It will also be removed from the
|
|
175
|
-
# user's stash or list of ignores.
|
|
176
|
-
#
|
|
177
|
-
# @param [Object] object the object you want self to dislike.
|
|
178
|
-
# @return true if object has been disliked
|
|
179
|
-
# @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
|
180
|
-
def dislike object
|
|
181
|
-
raise UnrecommendableError unless object.recommendable?
|
|
182
|
-
return if dislikes? object
|
|
183
|
-
|
|
184
|
-
run_hook :before_dislike, object
|
|
185
|
-
dislikes.create! :dislikeable_id => object.id, :dislikeable_type => object.class
|
|
186
|
-
run_hook :after_dislike, object
|
|
187
|
-
|
|
188
|
-
true
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Checks to see if self has already disliked a passed object.
|
|
192
|
-
#
|
|
193
|
-
# @param [Object] object the object you want to check
|
|
194
|
-
# @return true if self dislikes object, false if not
|
|
195
|
-
def dislikes? object
|
|
196
|
-
dislikes.exists? :dislikeable_id => object.id, :dislikeable_type => object.class.base_class.to_s
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Destroys a Recommendable::Dislike currently associating self with object
|
|
200
|
-
#
|
|
201
|
-
# @param [Object] object the object you want to remove from self's dislikes
|
|
202
|
-
# @return true if object is removed from self's dislikes, nil if nothing happened
|
|
203
|
-
def undislike object
|
|
204
|
-
dislike = dislikes.where(:dislikeable_id => object.id, :dislikeable_type => object.class.base_class.to_s)
|
|
205
|
-
if dislike.exists?
|
|
206
|
-
run_hook :before_undislike, object
|
|
207
|
-
dislike.first.destroy
|
|
208
|
-
run_hook :after_undislike, object
|
|
209
|
-
true
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# Get a list of records that self currently dislikes
|
|
214
|
-
|
|
215
|
-
# @return [Array] an array of ActiveRecord objects that self has disliked
|
|
216
|
-
def disliked
|
|
217
|
-
Recommendable.recommendable_classes.map { |klass| disliked_for klass }.flatten
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
private
|
|
221
|
-
|
|
222
|
-
# Get a list of records belonging to a passed class that self currently
|
|
223
|
-
# dislikes.
|
|
224
|
-
#
|
|
225
|
-
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
|
226
|
-
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has disliked
|
|
227
|
-
def disliked_for klass
|
|
228
|
-
ids = if klass.sti?
|
|
229
|
-
dislikes.joins(manual_join(klass, 'dislike')).map(&:dislikeable_id)
|
|
230
|
-
else
|
|
231
|
-
dislikes.where(:dislikeable_type => klass.to_s).map(&:dislikeable_id)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
klass.where('ID IN (?)', ids)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Get a list of Recommendable::Dislikes with a `#dislikeable_type` of the
|
|
238
|
-
# passed class.
|
|
239
|
-
#
|
|
240
|
-
# @param [Class, String, Symbol] klass the class for which you would like to return self's dislikes. Can be the class constant, or a String/Symbol representation of the class name.
|
|
241
|
-
# @note You should not need to use this method. (see {#disliked_for})
|
|
242
|
-
# @private
|
|
243
|
-
def dislikes_for klass
|
|
244
|
-
if klass.sti?
|
|
245
|
-
dislikes.joins manual_join(klass, 'dislike')
|
|
246
|
-
else
|
|
247
|
-
dislikes.where(:dislikeable_type => klass.to_s)
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
module StashMethods
|
|
253
|
-
# Creates a Recommendable::Stash to associate self to a passed object.
|
|
254
|
-
# This will remove the item from this user's recommendations.
|
|
255
|
-
# If self is currently found to have liked or disliked the object, nothing
|
|
256
|
-
# will happen. It will, however, be unignored.
|
|
257
|
-
#
|
|
258
|
-
# @param [Object] object the object you want self to stash.
|
|
259
|
-
# @return true if object has been stashed
|
|
260
|
-
# @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
|
261
|
-
def stash object
|
|
262
|
-
raise UnrecommendableError unless object.recommendable?
|
|
263
|
-
return if rated?(object) || stashed?(object)
|
|
264
|
-
|
|
265
|
-
run_hook :before_stash, object
|
|
266
|
-
stashed_items.create! :stashable_id => object.id, :stashable_type => object.class
|
|
267
|
-
run_hook :after_stash, object
|
|
268
|
-
|
|
269
|
-
true
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Checks to see if self has already stashed a passed object for later.
|
|
273
|
-
#
|
|
274
|
-
# @param [Object] object the object you want to check
|
|
275
|
-
# @return true if self has stashed object, false if not
|
|
276
|
-
def stashed? object
|
|
277
|
-
stashed_items.exists? :stashable_id => object.id, :stashable_type => object.class.base_class.to_s
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Destroys a Recommendable::Stash currently associating self with object
|
|
281
|
-
#
|
|
282
|
-
# @param [Object] object the object you want to remove from self's stash
|
|
283
|
-
# @return true if object is stashed, nil if nothing happened
|
|
284
|
-
def unstash object
|
|
285
|
-
stash = stashed_items.where(:stashable_id => object.id, :stashable_type => object.class.base_class.to_s)
|
|
286
|
-
if stash.exists?
|
|
287
|
-
run_hook :before_unstash, object
|
|
288
|
-
stash.first.destroy
|
|
289
|
-
run_hook :after_unstash, object
|
|
290
|
-
true
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
# Get a list of records that self has currently stashed for later
|
|
295
|
-
#
|
|
296
|
-
# @return [Array] an array of ActiveRecord objects that self has stashed
|
|
297
|
-
def stashed
|
|
298
|
-
Recommendable.recommendable_classes.map { |klass| stashed_for klass }.flatten
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
private
|
|
302
|
-
|
|
303
|
-
# Get a list of records belonging to a passed class that self currently
|
|
304
|
-
# has stashed away for later.
|
|
305
|
-
#
|
|
306
|
-
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
|
307
|
-
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has stashed
|
|
308
|
-
def stashed_for klass
|
|
309
|
-
ids = if klass.sti?
|
|
310
|
-
stashed_items.joins(manual_join(klass, 'stash')).map(&:stashable_id)
|
|
311
|
-
else
|
|
312
|
-
stashed_items.where(:stashable_type => klass.to_s).map(&:stashable_id)
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
klass.where('ID IN (?)', ids)
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
module IgnoreMethods
|
|
320
|
-
# Creates a Recommendable::Ignore to associate self to a passed object. If
|
|
321
|
-
# self is currently found to have liked or dislikedobject, the
|
|
322
|
-
# corresponding Recommendable::Like or Recommendable::Dislike will be
|
|
323
|
-
# destroyed.
|
|
324
|
-
#
|
|
325
|
-
# @param [Object] object the object you want self to ignore.
|
|
326
|
-
# @return true if object has been ignored
|
|
327
|
-
# @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
|
|
328
|
-
def ignore object
|
|
329
|
-
raise UnrecommendableError unless object.recommendable?
|
|
330
|
-
return if ignored? object
|
|
331
|
-
|
|
332
|
-
run_hook :before_ignore, object
|
|
333
|
-
ignores.create! :ignorable_id => object.id, :ignorable_type => object.class
|
|
334
|
-
run_hook :after_ignore, object
|
|
335
|
-
|
|
336
|
-
true
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
# Checks to see if self has already ignored a passed object.
|
|
340
|
-
#
|
|
341
|
-
# @param [Object] object the object you want to check
|
|
342
|
-
# @return true if self has ignored object, false if not
|
|
343
|
-
def ignored? object
|
|
344
|
-
ignores.exists? :ignorable_id => object.id, :ignorable_type => object.class.base_class.to_s
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
# Destroys a Recommendable::Ignore currently associating self with object
|
|
348
|
-
#
|
|
349
|
-
# @param [Object] object the object you want to remove from self's ignores
|
|
350
|
-
# @return true if object is removed from self's ignores, nil if nothing happened
|
|
351
|
-
def unignore object
|
|
352
|
-
ignore = ignores.where(:ignorable_id => object.id, :ignorable_type => object.class.base_class.to_s)
|
|
353
|
-
if ignore.exists?
|
|
354
|
-
run_hook :before_unignore, object
|
|
355
|
-
ignore.first.destroy
|
|
356
|
-
run_hook :after_unignore, object
|
|
357
|
-
true
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
# Get a list of records that self is currently ignoring
|
|
362
|
-
|
|
363
|
-
# @return [Array] an array of ActiveRecord objects that self has ignored
|
|
364
|
-
def ignored
|
|
365
|
-
Recommendable.recommendable_classes.map { |klass| ignored_for klass }.flatten
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
private
|
|
369
|
-
|
|
370
|
-
# Get a list of records belonging to a passed class that self is
|
|
371
|
-
# currently ignoring.
|
|
372
|
-
#
|
|
373
|
-
# @param [Class, String, Symbol] klass the class of records. Can be the class constant, or a String/Symbol representation of the class name.
|
|
374
|
-
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of records that self has ignored
|
|
375
|
-
def ignored_for klass
|
|
376
|
-
ids = if klass.sti?
|
|
377
|
-
ignores.joins(manual_join(klass, 'ignore')).map(&:ignorable_id)
|
|
378
|
-
else
|
|
379
|
-
ignores.where(:ignorable_type => klass.to_s).map(&:ignorable_id)
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
klass.where('ID IN (?)', ids)
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
module RecommendationMethods
|
|
387
|
-
# Checks to see if self has already liked or disliked a passed object.
|
|
388
|
-
#
|
|
389
|
-
# @param [Object] object the object you want to check
|
|
390
|
-
# @return true if self has liked or disliked object, false if not
|
|
391
|
-
def rated? object
|
|
392
|
-
likes?(object) || dislikes?(object)
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# Checks to see if self has liked or disliked any objects yet.
|
|
396
|
-
#
|
|
397
|
-
# @return true if self has liked or disliked anything, false if not
|
|
398
|
-
def rated_anything?
|
|
399
|
-
likes.count > 0 || dislikes.count > 0
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Get a list of raters that have been found to be the most similar to
|
|
403
|
-
# self. They are sorted in a descending fashion with the most similar
|
|
404
|
-
# rater in the first index.
|
|
405
|
-
#
|
|
406
|
-
# @param [Hash] options the options for this query
|
|
407
|
-
# @option options [Fixnum] :count (10) The number of raters to return
|
|
408
|
-
# @return [Array] An array of instances of your user class
|
|
409
|
-
def similar_raters options = {}
|
|
410
|
-
defaults = { :count => 10 }
|
|
411
|
-
options = defaults.merge options
|
|
412
|
-
|
|
413
|
-
rater_ids = Recommendable.redis.zrevrange(similarity_set, 0, options[:count] - 1).map(&:to_i)
|
|
414
|
-
raters = Recommendable.user_class.find rater_ids
|
|
415
|
-
|
|
416
|
-
# The query loses the ordering, so...
|
|
417
|
-
return raters.sort_by { |rater| rater_ids.index(rater.id) }
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
def liked_in_common_with rater, options = {}
|
|
421
|
-
options.merge!({ :return_records => true })
|
|
422
|
-
create_recommended_to_sets and rater.create_recommended_to_sets
|
|
423
|
-
liked = common_likes_with rater, options
|
|
424
|
-
destroy_recommended_to_sets and rater.destroy_recommended_to_sets
|
|
425
|
-
return liked
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
def disliked_in_common_with rater, options = {}
|
|
429
|
-
options.merge!({ :return_records => true })
|
|
430
|
-
create_recommended_to_sets and rater.create_recommended_to_sets
|
|
431
|
-
disliked = common_dislikes_with rater, options
|
|
432
|
-
destroy_recommended_to_sets and rater.destroy_recommended_to_sets
|
|
433
|
-
return disliked
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
def disagreed_on_with rater, options = {}
|
|
437
|
-
options.merge!({ :return_records => true })
|
|
438
|
-
create_recommended_to_sets and rater.create_recommended_to_sets
|
|
439
|
-
disagreements = disagreements_with rater, options
|
|
440
|
-
destroy_recommended_to_sets and rater.destroy_recommended_to_sets
|
|
441
|
-
return disagreements
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
# Get a list of recommendations for self. The whole point of this gem!
|
|
445
|
-
# Recommendations are returned in a descending order with the first index
|
|
446
|
-
# being the object that self has been found most likely to enjoy.
|
|
447
|
-
#
|
|
448
|
-
# @param [Fixnum] count the number of recmomendations to return
|
|
449
|
-
# @return [Array] an array of ActiveRecord objects that are recommendable
|
|
450
|
-
def recommendations count = 10
|
|
451
|
-
return [] if likes.count + dislikes.count == 0
|
|
452
|
-
|
|
453
|
-
unioned_predictions = "#{self.class}:#{id}:predictions"
|
|
454
|
-
Recommendable.redis.zunionstore unioned_predictions, Recommendable.recommendable_classes.map { |klass| predictions_set_for klass }
|
|
455
|
-
|
|
456
|
-
recommendations = Recommendable.redis.zrevrange(unioned_predictions, 0, count - 1).map do |object|
|
|
457
|
-
klass, id = object.split(":")
|
|
458
|
-
klass.constantize.find(id)
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
recommendations = recommendations.first if count == 1
|
|
462
|
-
|
|
463
|
-
Recommendable.redis.del(unioned_predictions) and return recommendations
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
# Get a list of recommendations for self on a single recommendable type.
|
|
467
|
-
# Recommendations are returned in a descending order with the first index
|
|
468
|
-
# being the object that self has been found most likely to enjoy.
|
|
469
|
-
#
|
|
470
|
-
# @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.
|
|
471
|
-
# @return [ActiveRecord::Relation] an ActiveRecord::Relation of recommendations
|
|
472
|
-
def recommended_for klass, count = 10
|
|
473
|
-
return [] if likes_for(klass.base_class).count + dislikes_for(klass.base_class).count == 0 || Recommendable.redis.zcard(predictions_set_for(klass)) == 0
|
|
474
|
-
ids = []
|
|
475
|
-
|
|
476
|
-
(0...count).each do |i|
|
|
477
|
-
prediction = Recommendable.redis.zrevrange(predictions_set_for(klass), i, i).first
|
|
478
|
-
break unless prediction
|
|
479
|
-
|
|
480
|
-
ids << prediction.split(":").last
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
return klass.to_s.classify.constantize.where('ID IN (?)', ids)
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
# Return the value calculated by {#predict} on self for a passed object.
|
|
487
|
-
#
|
|
488
|
-
# @param [Object] object the object to fetch the probability for
|
|
489
|
-
# @return [Float] the likelihood of self liking the passed object
|
|
490
|
-
def probability_of_liking object
|
|
491
|
-
Recommendable.redis.zscore predictions_set_for(object.class), object.redis_key
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Return the negation of the value calculated by {#predict} on self
|
|
495
|
-
# for a passed object.
|
|
496
|
-
#
|
|
497
|
-
# @param [Object] object the object to fetch the probability for
|
|
498
|
-
# @return [Float] the likelihood of self disliking the passed object
|
|
499
|
-
# @see {#probability_of_liking}
|
|
500
|
-
def probability_of_disliking object
|
|
501
|
-
-probability_of_liking(object)
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
protected
|
|
505
|
-
|
|
506
|
-
# Makes a call to Redis and intersects the sets of likes belonging to self
|
|
507
|
-
# and rater.
|
|
508
|
-
#
|
|
509
|
-
# @param [Object] rater the person whose set of likes you wish to intersect with that of self
|
|
510
|
-
# @param [Hash] options the options for this intersection
|
|
511
|
-
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersection to a single recommendable type. By default, all recomendable types are considered
|
|
512
|
-
# @option options [true, false] :return_records (true) Return the actual Model instances
|
|
513
|
-
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
|
514
|
-
def common_likes_with rater, options = {}
|
|
515
|
-
defaults = { :class => nil, :return_records => false }
|
|
516
|
-
options = defaults.merge options
|
|
517
|
-
|
|
518
|
-
if options[:class]
|
|
519
|
-
in_common = Recommendable.redis.sinter likes_set_for(options[:class]), rater.likes_set_for(options[:class])
|
|
520
|
-
in_common = options[:class].to_s.classify.constantize.where('ID IN (?)', in_common) if options[:return_records]
|
|
521
|
-
else
|
|
522
|
-
in_common = Recommendable.recommendable_classes.map do |klass|
|
|
523
|
-
things = Recommendable.redis.sinter(likes_set_for(klass), rater.likes_set_for(klass))
|
|
524
|
-
|
|
525
|
-
if options[:return_records]
|
|
526
|
-
klass.to_s.classify.constantize.find things
|
|
527
|
-
else
|
|
528
|
-
things.map { |id| "#{klass.to_s.classify}:#{id}" }
|
|
529
|
-
end
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
in_common.flatten!
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
return in_common
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
# Makes a call to Redis and intersects the sets of dislikes belonging to
|
|
539
|
-
# self and rater.
|
|
540
|
-
#
|
|
541
|
-
# @param [Object] rater the person whose set of dislikes you wish to intersect with that of self
|
|
542
|
-
# @param [Hash] options the options for this intersection
|
|
543
|
-
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersection to a single recommendable type. By default, all recomendable types are considered
|
|
544
|
-
# @option options [true, false] :return_records (true) Return the actual Model instances
|
|
545
|
-
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
|
546
|
-
def common_dislikes_with rater, options = {}
|
|
547
|
-
defaults = { :class => nil, :return_records => false }
|
|
548
|
-
options = defaults.merge options
|
|
549
|
-
|
|
550
|
-
if options[:class]
|
|
551
|
-
in_common = Recommendable.redis.sinter dislikes_set_for(options[:class]), rater.dislikes_set_for(options[:class])
|
|
552
|
-
in_common = options[:class].to_s.classify.constantize.where('ID IN (?)', in_common) if options[:return_records]
|
|
553
|
-
else
|
|
554
|
-
in_common = Recommendable.recommendable_classes.map do |klass|
|
|
555
|
-
things = Recommendable.redis.sinter(dislikes_set_for(klass), rater.dislikes_set_for(klass))
|
|
556
|
-
|
|
557
|
-
if options[:return_records]
|
|
558
|
-
klass.to_s.classify.constantize.find(things)
|
|
559
|
-
else
|
|
560
|
-
things.map { |id| "#{klass.to_s.classify}:#{id}" }
|
|
561
|
-
end
|
|
562
|
-
end
|
|
563
|
-
|
|
564
|
-
in_common.flatten!
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
in_common
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
# Makes a call to Redis and intersects self's set of likes with rater's
|
|
571
|
-
# set of dislikes and vise versa. The idea here is that if self likes
|
|
572
|
-
# an object that rater dislikes, it is a disagreement and should count
|
|
573
|
-
# negatively towards their similarity.
|
|
574
|
-
#
|
|
575
|
-
# @param [Object] rater the person whose sets you wish to intersect with those of self
|
|
576
|
-
# @param [Hash] options the options for this intersection
|
|
577
|
-
# @option options [Class, String, Symbol] :class ('nil') Restrict the intersections to a single recommendable type. By default, all recomendable types are considered
|
|
578
|
-
# @option options [true, false] :return_records (true) Return the actual Model instances
|
|
579
|
-
# @return [Array] Typically, an array of ActiveRecord objects (unless :return_records is false)
|
|
580
|
-
def disagreements_with rater, options = {}
|
|
581
|
-
defaults = { :class => nil, :return_records => false }
|
|
582
|
-
options = defaults.merge options
|
|
583
|
-
|
|
584
|
-
if options[:class]
|
|
585
|
-
disagreements = Recommendable.redis.sinter(likes_set_for(options[:class]), rater.dislikes_set_for(options[:class]))
|
|
586
|
-
disagreements += Recommendable.redis.sinter(dislikes_set_for(options[:class]), rater.likes_set_for(options[:class]))
|
|
587
|
-
disagreements = options[:class].to_s.classify.constantize.where('ID IN (?)', disagreements) if options[:return_records]
|
|
588
|
-
else
|
|
589
|
-
disagreements = Recommendable.recommendable_classes.map do |klass|
|
|
590
|
-
things = Recommendable.redis.sinter(likes_set_for(klass), rater.dislikes_set_for(klass))
|
|
591
|
-
things += Recommendable.redis.sinter(dislikes_set_for(klass), rater.likes_set_for(klass))
|
|
592
|
-
|
|
593
|
-
if options[:return_records]
|
|
594
|
-
klass.to_s.classify.constantize.find(things)
|
|
595
|
-
else
|
|
596
|
-
things.map { |id| "#{options[:class].to_s.classify}:#{id}" }
|
|
597
|
-
end
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
disagreements.flatten!
|
|
601
|
-
end
|
|
602
|
-
|
|
603
|
-
disagreements
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
# Used internally during liking/disliking/stashing/ignoring objects. This
|
|
607
|
-
# will prep an object to be liked, disliked, etc. by making sure that self
|
|
608
|
-
# doesn't already have this item in their list of likes, dislikes, stashed
|
|
609
|
-
# items or ignored items.
|
|
610
|
-
#
|
|
611
|
-
# param [Object] object the object to destroy Recommendable models for
|
|
612
|
-
# @private
|
|
613
|
-
def completely_unrecommend object
|
|
614
|
-
unlike(object) || undislike(object) || unstash(object) || unignore(object)
|
|
615
|
-
unpredict(object)
|
|
616
|
-
end
|
|
617
|
-
|
|
618
|
-
# @private
|
|
619
|
-
def likes_set_for klass
|
|
620
|
-
"#{self.class}:#{id}:likes:#{klass}"
|
|
621
|
-
end
|
|
622
|
-
|
|
623
|
-
# @private
|
|
624
|
-
def dislikes_set_for klass
|
|
625
|
-
"#{self.class}:#{id}:dislikes:#{klass}"
|
|
626
|
-
end
|
|
627
|
-
|
|
628
|
-
# Used for setup purposes. Creates and populates sets in redis containing
|
|
629
|
-
# self's likes and dislikes.
|
|
630
|
-
# @private
|
|
631
|
-
def create_recommended_to_sets
|
|
632
|
-
Recommendable.recommendable_classes.each do |klass|
|
|
633
|
-
likes_for(klass).each { |like| Recommendable.redis.sadd likes_set_for(klass), like.likeable_id }
|
|
634
|
-
dislikes_for(klass).each { |dislike| Recommendable.redis.sadd dislikes_set_for(klass), dislike.dislikeable_id }
|
|
635
|
-
end
|
|
636
|
-
end
|
|
637
|
-
|
|
638
|
-
# Used for teardown purposes. Destroys the redis sets containing self's
|
|
639
|
-
# likes and dislikes, as they are only used during the process of
|
|
640
|
-
# updating recommendations and similarity values.
|
|
641
|
-
# @private
|
|
642
|
-
def destroy_recommended_to_sets
|
|
643
|
-
Recommendable.recommendable_classes.each do |klass|
|
|
644
|
-
Recommendable.redis.del likes_set_for(klass)
|
|
645
|
-
Recommendable.redis.del dislikes_set_for(klass)
|
|
646
|
-
end
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
private
|
|
650
|
-
|
|
651
|
-
# Checks how similar a passed rater is with self. This method calculates
|
|
652
|
-
# a numeric similarity value that can fall between -1.0 and 1.0. A value of
|
|
653
|
-
# 1.0 indicates that rater has the exact same likes and dislikes as self
|
|
654
|
-
# while a value of -1.0 indicates that rater dislikes every object that self
|
|
655
|
-
# likes and likes every object that self dislikes. A value of 0.0 would
|
|
656
|
-
# indicate that the two users share no likes or dislikes.
|
|
657
|
-
#
|
|
658
|
-
# @param [Object] rater an ActiveRecord object declared to `act_as_recommendable_to`
|
|
659
|
-
# @return [Float] the numeric similarity between self and rater
|
|
660
|
-
# @note The returned value relies on which user the method is called on. current_user.similarity_with(rater) will not equal rater.similarity_with(current_user) unless their sets of likes and dislikes are identical. current_user.similarity_with(rater) will return 1.0 even if rater has several likes/dislikes that `current_user` does not.
|
|
661
|
-
# @private
|
|
662
|
-
def similarity_with(rater)
|
|
663
|
-
rater.create_recommended_to_sets
|
|
664
|
-
agreements = common_likes_with(rater, :return_records => false).size
|
|
665
|
-
agreements += common_dislikes_with(rater, :return_records => false).size
|
|
666
|
-
disagreements = disagreements_with(rater, :return_records => false).size
|
|
667
|
-
|
|
668
|
-
similarity = (agreements - disagreements).to_f / (likes.count + dislikes.count)
|
|
669
|
-
rater.destroy_recommended_to_sets
|
|
670
|
-
|
|
671
|
-
return similarity
|
|
672
|
-
end
|
|
673
|
-
|
|
674
|
-
# Used internally to update self's prediction values across all
|
|
675
|
-
# recommendable types. This is called in the Resque job to refresh
|
|
676
|
-
# recommendations.
|
|
677
|
-
#
|
|
678
|
-
# @private
|
|
679
|
-
def update_recommendations
|
|
680
|
-
Recommendable.recommendable_classes.each { |klass| update_recommendations_for klass }
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
# Used internally to update self's prediction values across a single
|
|
684
|
-
# recommendable type. Convenience method for {#update_recommendations}
|
|
685
|
-
#
|
|
686
|
-
# @param [Class] klass the recommendable type to update predictions for
|
|
687
|
-
# @private
|
|
688
|
-
def update_recommendations_for klass
|
|
689
|
-
klass.find_each do |object|
|
|
690
|
-
next if rated?(object) || !object.been_rated? || ignored?(object) || stashed?(object)
|
|
691
|
-
prediction = predict object
|
|
692
|
-
Recommendable.redis.zadd(predictions_set_for(object.class), prediction, object.redis_key) if prediction
|
|
693
|
-
end
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
# Predict how likely it is that self will like a passed in object. This
|
|
697
|
-
# probability is not based on percentage. 0.0 indicates that self will
|
|
698
|
-
# neither like nor dislike the passed object. Values that approach Infinity
|
|
699
|
-
# indicate a rising probability of liking the passed object while values
|
|
700
|
-
# approaching -Infinity indicate a rising probability of disliking the
|
|
701
|
-
# passed object.
|
|
702
|
-
#
|
|
703
|
-
# @param [Object] object the object to check the likeliness of liking
|
|
704
|
-
# @return [Float] the probability that self will like object
|
|
705
|
-
# @private
|
|
706
|
-
def predict object
|
|
707
|
-
liked_by, disliked_by = object.send :create_recommendable_sets
|
|
708
|
-
rated_by = Recommendable.redis.scard(liked_by) + Recommendable.redis.scard(disliked_by)
|
|
709
|
-
similarity_sum = 0.0
|
|
710
|
-
prediction = 0.0
|
|
711
|
-
|
|
712
|
-
Recommendable.redis.smembers(liked_by).inject(similarity_sum) { |sum, r| sum += Recommendable.redis.zscore(similarity_set, r).to_f }
|
|
713
|
-
Recommendable.redis.smembers(disliked_by).inject(similarity_sum) { |sum, r| sum -= Recommendable.redis.zscore(similarity_set, r).to_f }
|
|
714
|
-
|
|
715
|
-
prediction = similarity_sum / rated_by.to_f
|
|
716
|
-
|
|
717
|
-
object.send :destroy_recommendable_sets
|
|
718
|
-
|
|
719
|
-
return prediction
|
|
720
|
-
end
|
|
721
|
-
|
|
722
|
-
# Used internally to update the similarity values between self and all
|
|
723
|
-
# other users. This is called in the Resque job to refresh recommendations.
|
|
724
|
-
#
|
|
725
|
-
# @private
|
|
726
|
-
def update_similarities
|
|
727
|
-
return unless rated_anything?
|
|
728
|
-
create_recommended_to_sets
|
|
729
|
-
|
|
730
|
-
Recommendable.user_class.find_each do |rater|
|
|
731
|
-
next if self == rater || !rater.rated_anything?
|
|
732
|
-
Recommendable.redis.zadd similarity_set, similarity_with(rater), rater.id
|
|
733
|
-
end
|
|
734
|
-
|
|
735
|
-
destroy_recommended_to_sets
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
# @private
|
|
739
|
-
def remove_from_similarities
|
|
740
|
-
Recommendable.redis.del similarity_set
|
|
741
|
-
|
|
742
|
-
Recommendable.user_class.find_each do |user|
|
|
743
|
-
Recommendable.redis.zrem user.send(:similarity_set), self.id
|
|
744
|
-
end
|
|
745
|
-
|
|
746
|
-
true
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
# @private
|
|
750
|
-
def remove_recommendations
|
|
751
|
-
Recommendable.recommendable_classes.each do |klass|
|
|
752
|
-
Recommendable.redis.del predictions_set_for(klass)
|
|
753
|
-
end
|
|
754
|
-
|
|
755
|
-
true
|
|
756
|
-
end
|
|
757
|
-
|
|
758
|
-
# @private
|
|
759
|
-
def unpredict object
|
|
760
|
-
Recommendable.redis.zrem predictions_set_for(object.class), object.redis_key
|
|
761
|
-
end
|
|
762
|
-
|
|
763
|
-
# @private
|
|
764
|
-
def similarity_set
|
|
765
|
-
"#{self.class}:#{id}:similarities"
|
|
766
|
-
end
|
|
767
|
-
|
|
768
|
-
# @private
|
|
769
|
-
def predictions_set_for klass
|
|
770
|
-
"#{self.class}:#{id}:predictions:#{klass}"
|
|
771
|
-
end
|
|
772
|
-
end
|
|
773
|
-
end
|
|
774
|
-
end
|