recommendable 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.markdown CHANGED
@@ -1,8 +1,14 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- 1.0.0 (current version)
5
- ---------------------
4
+ 1.1.0 (current version)
5
+ -----------------------
6
+ * Support for Sidekiq, Resque, DelayedJob and Rails::Queueing (issue #28)
7
+ * You must manually bundle Sidekiq, Resque, or DelayedJob. Rails::Queueing is available as a fallback for Rails 4.x
8
+ * Use [apotonick/hooks](https://github.com/apotonick/hooks) to implement callbacks (issue #25). See the [detailed README](http://davidcelis.com/recommendable) for more info on usage.
9
+
10
+ 1.0.0
11
+ -----
6
12
  * Dynamic finders now return ActiveRecord::Relations! This means you can chain other ActiveRecord query methods like so:
7
13
 
8
14
  ```ruby
data/Gemfile CHANGED
@@ -1,17 +1,3 @@
1
1
  source 'http://rubygems.org'
2
2
  # Add dependencies required to use your gem here.
3
- gem 'rails', '>= 3.1.0'
4
- gem 'redis', '>= 2.2.0'
5
- gem 'resque', '~> 1.19.0'
6
- gem 'resque-loner', '~> 1.2.0'
7
-
8
- # Add dependencies to develop your gem here.
9
- # Include everything needed to run rake, tests, features, etc.
10
- group :development do
11
- gem 'sqlite3'
12
- gem 'minitest'
13
- gem 'shoulda'
14
- gem 'miniskirt'
15
- gem 'yard', '~> 0.6.0'
16
- gem 'bundler', '>= 1.0.0'
17
- end
3
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,118 +1,111 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ recommendable (1.1.1)
5
+ hooks
6
+ rails (>= 3.0.0)
7
+ redis (~> 2.2.0)
8
+
1
9
  GEM
2
10
  remote: http://rubygems.org/
3
11
  specs:
4
- actionmailer (3.2.0)
5
- actionpack (= 3.2.0)
6
- mail (~> 2.4.0)
7
- actionpack (3.2.0)
8
- activemodel (= 3.2.0)
9
- activesupport (= 3.2.0)
12
+ actionmailer (3.2.6)
13
+ actionpack (= 3.2.6)
14
+ mail (~> 2.4.4)
15
+ actionpack (3.2.6)
16
+ activemodel (= 3.2.6)
17
+ activesupport (= 3.2.6)
10
18
  builder (~> 3.0.0)
11
19
  erubis (~> 2.7.0)
12
- journey (~> 1.0.0)
20
+ journey (~> 1.0.1)
13
21
  rack (~> 1.4.0)
14
- rack-cache (~> 1.1)
22
+ rack-cache (~> 1.2)
15
23
  rack-test (~> 0.6.1)
16
- sprockets (~> 2.1.2)
17
- activemodel (3.2.0)
18
- activesupport (= 3.2.0)
24
+ sprockets (~> 2.1.3)
25
+ activemodel (3.2.6)
26
+ activesupport (= 3.2.6)
19
27
  builder (~> 3.0.0)
20
- activerecord (3.2.0)
21
- activemodel (= 3.2.0)
22
- activesupport (= 3.2.0)
23
- arel (~> 3.0.0)
28
+ activerecord (3.2.6)
29
+ activemodel (= 3.2.6)
30
+ activesupport (= 3.2.6)
31
+ arel (~> 3.0.2)
24
32
  tzinfo (~> 0.3.29)
25
- activeresource (3.2.0)
26
- activemodel (= 3.2.0)
27
- activesupport (= 3.2.0)
28
- activesupport (3.2.0)
33
+ activeresource (3.2.6)
34
+ activemodel (= 3.2.6)
35
+ activesupport (= 3.2.6)
36
+ activesupport (3.2.6)
29
37
  i18n (~> 0.6)
30
38
  multi_json (~> 1.0)
31
- arel (3.0.0)
39
+ arel (3.0.2)
32
40
  builder (3.0.0)
33
41
  erubis (2.7.0)
34
42
  hike (1.2.1)
43
+ hooks (0.2.0)
35
44
  i18n (0.6.0)
36
- journey (1.0.0)
37
- json (1.6.5)
38
- mail (2.4.1)
45
+ journey (1.0.4)
46
+ json (1.7.3)
47
+ mail (2.4.4)
39
48
  i18n (>= 0.4.0)
40
49
  mime-types (~> 1.16)
41
50
  treetop (~> 1.4.8)
42
- mime-types (1.17.2)
51
+ mime-types (1.19)
43
52
  miniskirt (1.1.1)
44
53
  activesupport (>= 2.2)
45
- minitest (2.10.1)
46
- multi_json (1.0.4)
54
+ minitest (3.2.0)
55
+ multi_json (1.3.6)
47
56
  polyglot (0.3.3)
48
- rack (1.4.0)
49
- rack-cache (1.1)
57
+ rack (1.4.1)
58
+ rack-cache (1.2)
50
59
  rack (>= 0.4)
51
- rack-protection (1.2.0)
52
- rack
53
60
  rack-ssl (1.3.2)
54
61
  rack
55
62
  rack-test (0.6.1)
56
63
  rack (>= 1.0)
57
- rails (3.2.0)
58
- actionmailer (= 3.2.0)
59
- actionpack (= 3.2.0)
60
- activerecord (= 3.2.0)
61
- activeresource (= 3.2.0)
62
- activesupport (= 3.2.0)
64
+ rails (3.2.6)
65
+ actionmailer (= 3.2.6)
66
+ actionpack (= 3.2.6)
67
+ activerecord (= 3.2.6)
68
+ activeresource (= 3.2.6)
69
+ activesupport (= 3.2.6)
63
70
  bundler (~> 1.0)
64
- railties (= 3.2.0)
65
- railties (3.2.0)
66
- actionpack (= 3.2.0)
67
- activesupport (= 3.2.0)
71
+ railties (= 3.2.6)
72
+ railties (3.2.6)
73
+ actionpack (= 3.2.6)
74
+ activesupport (= 3.2.6)
68
75
  rack-ssl (~> 1.3.2)
69
76
  rake (>= 0.8.7)
70
77
  rdoc (~> 3.4)
71
- thor (~> 0.14.6)
78
+ thor (>= 0.14.6, < 2.0)
72
79
  rake (0.9.2.2)
73
80
  rdoc (3.12)
74
81
  json (~> 1.4)
75
82
  redis (2.2.2)
76
- redis-namespace (1.0.3)
77
- redis (< 3.0.0)
78
- resque (1.19.0)
79
- multi_json (~> 1.0)
80
- redis-namespace (~> 1.0.2)
81
- sinatra (>= 0.9.2)
82
- vegas (~> 0.1.2)
83
- resque-loner (1.2.0)
84
- resque (~> 1.0)
85
- shoulda (2.11.3)
86
- sinatra (1.3.2)
87
- rack (~> 1.3, >= 1.3.6)
88
- rack-protection (~> 1.2)
89
- tilt (~> 1.3, >= 1.3.3)
90
- sprockets (2.1.2)
83
+ shoulda (3.0.1)
84
+ shoulda-context (~> 1.0.0)
85
+ shoulda-matchers (~> 1.0.0)
86
+ shoulda-context (1.0.0)
87
+ shoulda-matchers (1.0.0)
88
+ sprockets (2.1.3)
91
89
  hike (~> 1.2)
92
90
  rack (~> 1.0)
93
91
  tilt (~> 1.1, != 1.3.0)
94
- sqlite3 (1.3.5)
95
- thor (0.14.6)
92
+ sqlite3 (1.3.6)
93
+ thor (0.15.4)
96
94
  tilt (1.3.3)
97
95
  treetop (1.4.10)
98
96
  polyglot
99
97
  polyglot (>= 0.3.1)
100
- tzinfo (0.3.31)
101
- vegas (0.1.11)
102
- rack (>= 1.0.0)
98
+ tzinfo (0.3.33)
103
99
  yard (0.6.8)
104
100
 
105
101
  PLATFORMS
106
102
  ruby
107
103
 
108
104
  DEPENDENCIES
109
- bundler (>= 1.0.0)
105
+ bundler
110
106
  miniskirt
111
107
  minitest
112
- rails (>= 3.1.0)
113
- redis (>= 2.2.0)
114
- resque (~> 1.19.0)
115
- resque-loner (~> 1.2.0)
108
+ recommendable!
116
109
  shoulda
117
110
  sqlite3
118
111
  yard (~> 0.6.0)
data/README.markdown CHANGED
@@ -4,8 +4,11 @@ Recommendable is an engine for Rails 3 applications to quickly add the ability f
4
4
 
5
5
  Requirements
6
6
  ------------
7
- * Ruby 1.9.2 (1.9.x if using from git. May run on 1.8.7, I don't know)
8
- * Rails 3.x
7
+ * Ruby 1.9.x
8
+ * Rails 3.x or 4.x
9
+ * Sidekiq or Resque (or DelayedJob)
10
+
11
+ Bundling one of the queueing systems above is highly recommended to avoid having to manually refresh users' recommendations. If running on Rails 4, the built-in queueing system is supported. If you bundle [Sidekiq][sidekiq], [Resque][resque], or [DelayedJob][delayed_job], Recommendable will use your bundled queueing system instead. If bundling Resque, you should also include ['resque-loner'][resque-loner] in your Gemfile to ensure your users only get queued once (Sidekiq does this by default, and there is no current way to avoid duplicate jobs in DelayedJob).
9
12
 
10
13
  Installation
11
14
  ------------
@@ -13,7 +16,7 @@ Installation
13
16
  Add the following to your Rails application's `Gemfile`:
14
17
 
15
18
  ``` ruby
16
- gem "recommendable", :git => "git://github.com/davidcelis/recommendable"
19
+ gem 'recommendable'
17
20
  ```
18
21
 
19
22
  After bundling, run the installation generator:
@@ -22,15 +25,17 @@ After bundling, run the installation generator:
22
25
  $ rails g recommendable:install
23
26
  ```
24
27
 
25
- Double check `config/initializers/recommendable.rb` for options on configuring your Redis connection. After a user likes or dislikes something new, they are placed in a Resque queue to have their recommendation updated. Start up resque like so:
28
+ Double check `config/initializers/recommendable.rb` for options on configuring your Redis connection. After a user likes or dislikes something new, they are placed in a queue to have their recommendations updated. If you're using the basic Rails 4.0 queue, you don't need to do anything explicit. If using Sidekiq, Resque, or DelayedJob, start your workers from the command line:
26
29
 
27
30
  ``` bash
31
+ # sidekiq
32
+ $ bundle exec sidekiq -q recommendable
33
+ # resque
28
34
  $ QUEUE=recommendable rake environment resque:work
35
+ # delayed_job
36
+ $ rake jobs:work
29
37
  ```
30
38
 
31
- You can run this command multiple times if you wish to start more than one
32
- worker. For more options on this task, head over to [defunkt/resque][resque].
33
-
34
39
  Usage
35
40
  -----
36
41
 
@@ -53,28 +58,39 @@ Installing Redis
53
58
 
54
59
  Recommendable requires Redis to deliver recommendations. The collaborative filtering logic is based almost entirely on set math, and Redis is blazing fast for this. _NOTE: Your redis database MUST be persistent._
55
60
 
56
- ### Homebrew
61
+ ### Mac OS X
57
62
 
58
- For Mac OS X users, homebrew is by far the easiest way to install Redis.
63
+ For Mac OS X users, homebrew is by far the easiest way to install Redis. Make sure to read the caveats after installation!
59
64
 
60
65
  ``` bash
61
66
  $ brew install redis
62
67
  ```
63
68
 
64
- ### Via Resque
69
+ ### Linux
65
70
 
66
- Resque (which is also a dependency of recommendable) includes Rake tasks that
67
- will install and run Redis for you:
71
+ For Linux users, there is a package on apt-get.
68
72
 
69
73
  ``` bash
70
- $ git clone git://github.com/defunkt/resque.git
71
- $ cd resque
72
- $ rake redis:install dtach:install
73
- $ rake redis:start
74
+ $ sudo apt-get install redis-server
75
+ $ redis-server
74
76
  ```
75
77
 
76
78
  Redis will now be running on localhost:6379. After a second, you can hit `ctrl-\` to detach and keep Redis running in the background.
77
79
 
80
+ ### Redis problems?
81
+
82
+ Oops, did you kill your Redis database? Not to worry. Likes, Dislikes, Ignores,
83
+ and StashedItems are stored as models in your regular database. As long as these
84
+ still exist, you can regenerate the similarity values and recommendations on the
85
+ fly. But try not to have to do it!
86
+
87
+ ``` ruby
88
+ Users.all.each do |user|
89
+ user.send :update_similarities
90
+ user.send :update_recommendations
91
+ end
92
+ ```
93
+
78
94
  Contributing to recommendable
79
95
  -----------------------------
80
96
 
@@ -102,7 +118,10 @@ Copyright © 2012 David Celis. See LICENSE.txt for
102
118
  further details.
103
119
 
104
120
  [stars]: http://davidcelis.com/blog/2012/02/01/why-i-hate-five-star-ratings/
121
+ [sidekiq]: https://github.com/mperham/sidekiq
122
+ [delayed_job]: https://github.com/tobi/delayed_job
105
123
  [resque]: https://github.com/defunkt/resque
124
+ [resque-loner]: https://github.com/jayniz/resque-loner
106
125
  [forking]: http://help.github.com/forking/
107
126
  [pull requests]: http://help.github.com/pull-requests/
108
127
  [collaborative filtering]: http://davidcelis.com/blog/2012/02/07/collaborative-filtering-with-likes-and-dislikes/
@@ -0,0 +1,17 @@
1
+ module Recommendable
2
+ if defined?(Delayed::Job)
3
+ class DelayedJobWorker
4
+ attr_accessor :user_id
5
+
6
+ def initialize(user_id)
7
+ @user_id = user_id
8
+ end
9
+
10
+ def perform
11
+ user = Recommendable.user_class.find(self.user_id)
12
+ user.send :update_similarities
13
+ user.send :update_recommendations
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Recommendable
2
+ if defined?(Rails::Queueing)
3
+ class RailsWorker
4
+ attr_accessor :user_id
5
+
6
+ def initialize(user_id)
7
+ @user_id = user_id
8
+ end
9
+
10
+ def run
11
+ user = Recommendable.user_class.find(self.user_id)
12
+ user.send :update_similarities
13
+ user.send :update_recommendations
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Recommendable
2
+ if defined?(Resque)
3
+ class ResqueWorker
4
+ include Resque::Plugins::UniqueJob if defined?(Resque::Plugins::UniqueJob)
5
+ @queue = :recommendable
6
+
7
+ def self.perform(user_id)
8
+ user = Recommendable.user_class.find(user_id)
9
+ user.send :update_similarities
10
+ user.send :update_recommendations
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Recommendable
2
+ if defined?(Sidekiq)
3
+ class SidekiqWorker
4
+ include ::Sidekiq::Worker
5
+ sidekiq_options :queue => :recommendable
6
+
7
+ def perform(user_id)
8
+ user = Recommendable.user_class.find(user_id)
9
+ user.send :update_similarities
10
+ user.send :update_recommendations
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,4 @@
1
1
  require "redis"
2
- require "resque"
3
- require "resque-loner"
4
2
 
5
3
  # Recommendable requires a connection to a running redis-server. Either create
6
4
  # a new instance based on a host/port or UNIX socket, or pass in an existing
@@ -10,12 +8,6 @@ require "resque-loner"
10
8
  # Connect to Redis via a UNIX socket instead
11
9
  <% unless options.redis_socket %># <% end %>Recommendable.redis = Redis.new(:sock => "<%= options.redis_socket %>")
12
10
 
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
-
19
11
  # Tell Redis which database to use (usually between 0 and 15). The default of 0
20
12
  # is most likely okay unless you have another application using that database.
21
13
  # Recommendable.redis.select "0"
@@ -25,9 +25,38 @@ module Recommendable
25
25
  include StashMethods
26
26
  include IgnoreMethods
27
27
  include RecommendationMethods
28
+ include Hooks
28
29
 
29
30
  before_destroy :remove_from_similarities, :remove_recommendations
30
31
 
32
+ # This is just until apotonick merges my change into his published gem. I promise.
33
+ define_hook :before_like
34
+ define_hook :after_like
35
+ define_hook :before_unlike
36
+ define_hook :after_unlike
37
+ define_hook :before_dislike
38
+ define_hook :after_dislike
39
+ define_hook :before_undislike
40
+ define_hook :after_undislike
41
+ define_hook :before_stash
42
+ define_hook :after_stash
43
+ define_hook :before_unstash
44
+ define_hook :after_unstash
45
+ define_hook :before_ignore
46
+ define_hook :after_ignore
47
+ define_hook :before_unignore
48
+ define_hook :after_unignore
49
+
50
+ %w(like dislike ignore).each do |action|
51
+ send "before_#{action}", lambda { |obj| completely_unrecommend obj }
52
+ end
53
+
54
+ %w(like unlike dislike undislike).each do |action|
55
+ send "after_#{action}", lambda { |obj| obj.send(:update_score) and Recommendable.enqueue(self.id) }
56
+ end
57
+
58
+ before_stash { |obj| unignore(obj) and unpredict(obj) }
59
+
31
60
  def method_missing method, *args, &block
32
61
  if method.to_s =~ /^(liked|disliked)_(.+)_in_common_with$/
33
62
  begin
@@ -75,14 +104,15 @@ module Recommendable
75
104
  #
76
105
  # @param [Object] object the object you want self to like.
77
106
  # @return true if object has been liked
78
- # @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
107
+ # @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
79
108
  def like object
80
- raise RecordNotRecommendableError unless object.recommendable?
109
+ raise UnrecommendableError unless object.recommendable?
81
110
  return if likes? object
82
- completely_unrecommend object
111
+
112
+ run_hook :before_like, object
83
113
  likes.create! :likeable_id => object.id, :likeable_type => object.class
84
- object.send :update_score
85
- Recommendable.enqueue self.id
114
+ run_hook :after_like, object
115
+
86
116
  true
87
117
  end
88
118
 
@@ -99,9 +129,11 @@ module Recommendable
99
129
  # @param [Object] object the object you want to remove from self's likes
100
130
  # @return true if object is unliked, nil if nothing happened
101
131
  def unlike object
102
- if likes.where(:likeable_id => object.id, :likeable_type => object.class.base_class.to_s).first.try(:destroy)
103
- object.send :update_score
104
- Recommendable.enqueue self.id
132
+ like = likes.where(:likeable_id => object.id, :likeable_type => object.class.base_class.to_s)
133
+ if like.exists?
134
+ run_hook :before_unlike, object
135
+ like.first.destroy
136
+ run_hook :after_unlike, object
105
137
  true
106
138
  end
107
139
  end
@@ -153,14 +185,15 @@ module Recommendable
153
185
  #
154
186
  # @param [Object] object the object you want self to dislike.
155
187
  # @return true if object has been disliked
156
- # @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
188
+ # @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
157
189
  def dislike object
158
- raise RecordNotRecommendableError unless object.recommendable?
190
+ raise UnrecommendableError unless object.recommendable?
159
191
  return if dislikes? object
160
- completely_unrecommend object
192
+
193
+ run_hook :before_dislike, object
161
194
  dislikes.create! :dislikeable_id => object.id, :dislikeable_type => object.class
162
- object.send :update_score
163
- Recommendable.enqueue self.id
195
+ run_hook :after_dislike, object
196
+
164
197
  true
165
198
  end
166
199
 
@@ -177,9 +210,11 @@ module Recommendable
177
210
  # @param [Object] object the object you want to remove from self's dislikes
178
211
  # @return true if object is removed from self's dislikes, nil if nothing happened
179
212
  def undislike object
180
- if dislikes.where(:dislikeable_id => object.id, :dislikeable_type => object.class.base_class.to_s).first.try(:destroy)
181
- object.send :update_score
182
- Recommendable.enqueue self.id
213
+ dislike = dislikes.where(:dislikeable_id => object.id, :dislikeable_type => object.class.base_class.to_s)
214
+ if dislike.exists?
215
+ run_hook :before_undislike, object
216
+ dislike.first.destroy
217
+ run_hook :after_undislike, object
183
218
  true
184
219
  end
185
220
  end
@@ -231,13 +266,15 @@ module Recommendable
231
266
  #
232
267
  # @param [Object] object the object you want self to stash.
233
268
  # @return true if object has been stashed
234
- # @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
269
+ # @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
235
270
  def stash object
236
- raise RecordNotRecommendableError unless object.recommendable?
271
+ raise UnrecommendableError unless object.recommendable?
237
272
  return if rated?(object) || stashed?(object)
238
- unignore object
239
- unpredict object
273
+
274
+ run_hook :before_stash, object
240
275
  stashed_items.create! :stashable_id => object.id, :stashable_type => object.class
276
+ run_hook :after_stash, object
277
+
241
278
  true
242
279
  end
243
280
 
@@ -254,7 +291,13 @@ module Recommendable
254
291
  # @param [Object] object the object you want to remove from self's stash
255
292
  # @return true if object is stashed, nil if nothing happened
256
293
  def unstash object
257
- true if stashed_items.where(:stashable_id => object.id, :stashable_type => object.class.base_class.to_s).first.try(:destroy)
294
+ stash = stashed_items.where(:stashable_id => object.id, :stashable_type => object.class.base_class.to_s)
295
+ if stash.exists?
296
+ run_hook :before_unstash, object
297
+ stash.first.destroy
298
+ run_hook :after_unstash, object
299
+ true
300
+ end
258
301
  end
259
302
 
260
303
  # Get a list of records that self has currently stashed for later
@@ -290,12 +333,15 @@ module Recommendable
290
333
  #
291
334
  # @param [Object] object the object you want self to ignore.
292
335
  # @return true if object has been ignored
293
- # @raise [RecordNotRecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
336
+ # @raise [UnrecommendableError] if you have not declared the passed object's model to `act_as_recommendable`
294
337
  def ignore object
295
- raise RecordNotRecommendableError unless object.recommendable?
338
+ raise UnrecommendableError unless object.recommendable?
296
339
  return if ignored? object
297
- completely_unrecommend object
340
+
341
+ run_hook :before_ignore, object
298
342
  ignores.create! :ignorable_id => object.id, :ignorable_type => object.class
343
+ run_hook :after_ignore, object
344
+
299
345
  true
300
346
  end
301
347
 
@@ -312,7 +358,13 @@ module Recommendable
312
358
  # @param [Object] object the object you want to remove from self's ignores
313
359
  # @return true if object is removed from self's ignores, nil if nothing happened
314
360
  def unignore object
315
- true if ignores.where(:ignorable_id => object.id, :ignorable_type => object.class.base_class.to_s).first.try(:destroy)
361
+ ignore = ignores.where(:ignorable_id => object.id, :ignorable_type => object.class.base_class.to_s)
362
+ if ignore.exists?
363
+ run_hook :before_unignore, object
364
+ ignore.first.destroy
365
+ run_hook :after_unignore, object
366
+ true
367
+ end
316
368
  end
317
369
 
318
370
  # Get a list of records that self is currently ignoring
@@ -694,6 +746,7 @@ module Recommendable
694
746
  destroy_recommended_to_sets
695
747
  end
696
748
 
749
+ # @private
697
750
  def remove_from_similarities
698
751
  Recommendable.redis.del similarity_set
699
752
 
@@ -704,6 +757,7 @@ module Recommendable
704
757
  true
705
758
  end
706
759
 
760
+ # @private
707
761
  def remove_recommendations
708
762
  Recommendable.recommendable_classes.each do |klass|
709
763
  Recommendable.redis.del predictions_set_for(klass)
@@ -1,4 +1,4 @@
1
1
  module Recommendable
2
- class RecordNotRecommendableError < StandardError
2
+ class UnrecommendableError < StandardError
3
3
  end
4
4
  end
@@ -1,3 +1,3 @@
1
1
  module Recommendable
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.1'
3
3
  end
data/lib/recommendable.rb CHANGED
@@ -5,6 +5,7 @@ require 'recommendable/acts_as_recommendable'
5
5
  require 'recommendable/exceptions'
6
6
  require 'recommendable/railtie' if defined?(Rails)
7
7
  require 'recommendable/version'
8
+ require 'hooks'
8
9
 
9
10
  module Recommendable
10
11
  mattr_accessor :redis, :user_class
@@ -15,6 +16,17 @@ module Recommendable
15
16
  end
16
17
 
17
18
  def self.enqueue(user_id)
18
- Resque.enqueue RecommendationRefresher, user_id
19
+ if defined? Sidekiq
20
+ SidekiqWorker.perform_async user_id
21
+ elsif defined? Resque
22
+ Resque.enqueue ResqueWorker, user_id
23
+ elsif defined? Delayed::Job
24
+ Delayed::Job.enqueue DelayedJobWorker.new(user_id)
25
+ elsif defined? Rails::Queueing
26
+ unless Rails.queue.any? { |w| w.user_id == user_id }
27
+ Rails.queue.push RailsWorker.new(user_id)
28
+ Rails.application.queue_consumer.start
29
+ end
30
+ end
19
31
  end
20
32
  end
@@ -2,31 +2,29 @@
2
2
  require File.expand_path('lib/recommendable/version')
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = "recommendable"
5
+ s.name = 'recommendable'
6
6
  s.version = Recommendable::VERSION
7
7
  s.date = Time.now.strftime('%Y-%m-%d')
8
8
 
9
- s.authors = ["David Celis"]
10
- s.email = "david@davidcelis.com"
11
- s.homepage = "http://github.com/davidcelis/recommendable"
9
+ s.authors = ['David Celis']
10
+ s.email = 'david@davidcelis.com'
11
+ s.homepage = 'http://github.com/davidcelis/recommendable'
12
12
 
13
- s.summary = "Add like-based and/or dislike-based recommendations to your app."
14
- s.description = "Allow a model (typically User) to Like and/or Dislike models in your app. Generate recommendations quickly using redis."
13
+ s.summary = 'Add like-based and/or dislike-based recommendations to your app.'
14
+ s.description = 'Allow a model (typically User) to Like and/or Dislike models in your app. Generate recommendations quickly using redis.'
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.has_rdoc = 'yard'
18
18
 
19
- s.add_development_dependency "sqlite3"
20
- s.add_development_dependency "minitest"
21
- s.add_development_dependency "shoulda"
22
- s.add_development_dependency "yard", "~> 0.6.0"
23
- s.add_development_dependency "bundler", "~> 1.0.0"
24
- s.add_development_dependency "jeweler", "~> 1.6.4"
25
- s.add_development_dependency "rcov"
19
+ s.add_development_dependency 'sqlite3'
20
+ s.add_development_dependency 'minitest'
21
+ s.add_development_dependency 'miniskirt'
22
+ s.add_development_dependency 'shoulda'
23
+ s.add_development_dependency 'yard', '~> 0.6.0'
24
+ s.add_development_dependency 'bundler'
26
25
 
27
- s.add_dependency "rails", ">= 3.0.0"
28
- s.add_dependency "redis", "~> 2.2.0"
29
- s.add_dependency "resque", ">= 1.19.0"
30
- s.add_dependency "resque-loner", "~> 1.2.0"
26
+ s.add_dependency 'rails', '>= 3.0.0'
27
+ s.add_dependency 'redis', '~> 2.2.0'
28
+ s.add_dependency 'hooks'
31
29
  end
32
30
 
@@ -1,6 +1,4 @@
1
1
  require "redis"
2
- require "resque"
3
- require "resque-loner"
4
2
 
5
3
  # What class will be liking/disliking objects and receiving recommendations?
6
4
  Recommendable.user_class = "User"
@@ -8,7 +8,7 @@ class DislikeSpec < MiniTest::Spec
8
8
 
9
9
  it "should not be created for an object that does not act_as_recommendedable" do
10
10
  django = PhpFramework.create(:name => "django")
11
- proc { @user.dislike(django) }.must_raise Recommendable::RecordNotRecommendableError
11
+ proc { @user.dislike(django) }.must_raise Recommendable::UnrecommendableError
12
12
  end
13
13
 
14
14
  it "should be created for an object that does act_as_recommendable" do
@@ -24,4 +24,4 @@ class DislikeSpec < MiniTest::Spec
24
24
  Recommendable::Dislike.count.must_equal 1
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -8,7 +8,7 @@ class IgnoreSpec < MiniTest::Spec
8
8
 
9
9
  it "should not be created for an object that does not act_as_recommendedable" do
10
10
  web2py = PhpFramework.create(:name => "web2py")
11
- proc { @user.ignore(web2py) }.must_raise Recommendable::RecordNotRecommendableError
11
+ proc { @user.ignore(web2py) }.must_raise Recommendable::UnrecommendableError
12
12
  end
13
13
 
14
14
  it "should be created for an object that does act_as_recommendable" do
@@ -24,4 +24,4 @@ class IgnoreSpec < MiniTest::Spec
24
24
  Recommendable::Ignore.count.must_equal 1
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -8,7 +8,7 @@ class LikeSpec < MiniTest::Spec
8
8
 
9
9
  it "should not be created for an object that does not act_as_recommendedable" do
10
10
  cake = PhpFramework.create(:name => "CakePHP")
11
- proc { @user.like(cake) }.must_raise Recommendable::RecordNotRecommendableError
11
+ proc { @user.like(cake) }.must_raise Recommendable::UnrecommendableError
12
12
  end
13
13
 
14
14
  it "should be created for an object that does act_as_recommendable" do
@@ -25,4 +25,4 @@ class LikeSpec < MiniTest::Spec
25
25
  Recommendable::Like.count.must_equal 1
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -8,7 +8,7 @@ class StashSpec < MiniTest::Spec
8
8
 
9
9
  it "should not be created for an object that does not act_as_recommendedable" do
10
10
  web2py = PhpFramework.create(:name => "web2py")
11
- proc { @user.stash(web2py) }.must_raise Recommendable::RecordNotRecommendableError
11
+ proc { @user.stash(web2py) }.must_raise Recommendable::UnrecommendableError
12
12
  end
13
13
 
14
14
  it "should be created for an object that does act_as_recommendable" do
@@ -189,10 +189,10 @@ class UserSpec < MiniTest::Spec
189
189
  it "should not be able to rate or ignore an item that is not recommendable." do
190
190
  @cakephp = Factory(:php_framework)
191
191
 
192
- proc { @user.like(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
193
- proc { @user.dislike(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
194
- proc { @user.ignore(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
195
- proc { @user.stash(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
192
+ proc { @user.like(@cakephp) }.must_raise Recommendable::UnrecommendableError
193
+ proc { @user.dislike(@cakephp) }.must_raise Recommendable::UnrecommendableError
194
+ proc { @user.ignore(@cakephp) }.must_raise Recommendable::UnrecommendableError
195
+ proc { @user.stash(@cakephp) }.must_raise Recommendable::UnrecommendableError
196
196
 
197
197
  proc { @cakephp.liked_by }.must_raise NoMethodError
198
198
  proc { @cakephp.disliked_by }.must_raise NoMethodError
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recommendable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-27 00:00:00.000000000 Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sqlite3
@@ -44,7 +44,7 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: shoulda
47
+ name: miniskirt
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
@@ -60,45 +60,29 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: yard
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ~>
68
- - !ruby/object:Gem::Version
69
- version: 0.6.0
70
- type: :development
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 0.6.0
78
- - !ruby/object:Gem::Dependency
79
- name: bundler
63
+ name: shoulda
80
64
  requirement: !ruby/object:Gem::Requirement
81
65
  none: false
82
66
  requirements:
83
- - - ~>
67
+ - - ! '>='
84
68
  - !ruby/object:Gem::Version
85
- version: 1.0.0
69
+ version: '0'
86
70
  type: :development
87
71
  prerelease: false
88
72
  version_requirements: !ruby/object:Gem::Requirement
89
73
  none: false
90
74
  requirements:
91
- - - ~>
75
+ - - ! '>='
92
76
  - !ruby/object:Gem::Version
93
- version: 1.0.0
77
+ version: '0'
94
78
  - !ruby/object:Gem::Dependency
95
- name: jeweler
79
+ name: yard
96
80
  requirement: !ruby/object:Gem::Requirement
97
81
  none: false
98
82
  requirements:
99
83
  - - ~>
100
84
  - !ruby/object:Gem::Version
101
- version: 1.6.4
85
+ version: 0.6.0
102
86
  type: :development
103
87
  prerelease: false
104
88
  version_requirements: !ruby/object:Gem::Requirement
@@ -106,9 +90,9 @@ dependencies:
106
90
  requirements:
107
91
  - - ~>
108
92
  - !ruby/object:Gem::Version
109
- version: 1.6.4
93
+ version: 0.6.0
110
94
  - !ruby/object:Gem::Dependency
111
- name: rcov
95
+ name: bundler
112
96
  requirement: !ruby/object:Gem::Requirement
113
97
  none: false
114
98
  requirements:
@@ -156,13 +140,13 @@ dependencies:
156
140
  - !ruby/object:Gem::Version
157
141
  version: 2.2.0
158
142
  - !ruby/object:Gem::Dependency
159
- name: resque
143
+ name: hooks
160
144
  requirement: !ruby/object:Gem::Requirement
161
145
  none: false
162
146
  requirements:
163
147
  - - ! '>='
164
148
  - !ruby/object:Gem::Version
165
- version: 1.19.0
149
+ version: '0'
166
150
  type: :runtime
167
151
  prerelease: false
168
152
  version_requirements: !ruby/object:Gem::Requirement
@@ -170,23 +154,7 @@ dependencies:
170
154
  requirements:
171
155
  - - ! '>='
172
156
  - !ruby/object:Gem::Version
173
- version: 1.19.0
174
- - !ruby/object:Gem::Dependency
175
- name: resque-loner
176
- requirement: !ruby/object:Gem::Requirement
177
- none: false
178
- requirements:
179
- - - ~>
180
- - !ruby/object:Gem::Version
181
- version: 1.2.0
182
- type: :runtime
183
- prerelease: false
184
- version_requirements: !ruby/object:Gem::Requirement
185
- none: false
186
- requirements:
187
- - - ~>
188
- - !ruby/object:Gem::Version
189
- version: 1.2.0
157
+ version: '0'
190
158
  description: Allow a model (typically User) to Like and/or Dislike models in your
191
159
  app. Generate recommendations quickly using redis.
192
160
  email: david@davidcelis.com
@@ -207,7 +175,10 @@ files:
207
175
  - app/models/recommendable/ignore.rb
208
176
  - app/models/recommendable/like.rb
209
177
  - app/models/recommendable/stash.rb
210
- - app/workers/recommendable/recommendation_refresher.rb
178
+ - app/workers/recommendable/delayed_job_worker.rb
179
+ - app/workers/recommendable/rails_worker.rb
180
+ - app/workers/recommendable/resque_worker.rb
181
+ - app/workers/recommendable/sidekiq_worker.rb
211
182
  - config/routes.rb
212
183
  - db/migrate/20120124193723_create_likes.rb
213
184
  - db/migrate/20120124193728_create_dislikes.rb
@@ -1,12 +0,0 @@
1
- module Recommendable
2
- class RecommendationRefresher
3
- include Resque::Plugins::UniqueJob
4
- @queue = :recommendable
5
-
6
- def self.perform(user_id)
7
- user = Recommendable.user_class.find(user_id)
8
- user.send :update_similarities
9
- user.send :update_recommendations
10
- end
11
- end
12
- end