recommendable 0.1.2
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/.gitignore +57 -0
- data/.travis.yml +3 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +118 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +340 -0
- data/Rakefile +26 -0
- data/TODO +6 -0
- data/app/jobs/recommendable/recommendation_refresher.rb +12 -0
- data/app/models/recommendable/dislike.rb +9 -0
- data/app/models/recommendable/ignore.rb +9 -0
- data/app/models/recommendable/like.rb +9 -0
- data/app/models/recommendable/stashed_item.rb +9 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20120124193723_create_likes.rb +17 -0
- data/db/migrate/20120124193728_create_dislikes.rb +17 -0
- data/db/migrate/20120127092558_create_ignores.rb +17 -0
- data/db/migrate/20120131173909_create_stashed_items.rb +17 -0
- data/lib/generators/recommendable/USAGE +8 -0
- data/lib/generators/recommendable/install_generator.rb +47 -0
- data/lib/generators/recommendable/templates/initializer.rb +18 -0
- data/lib/recommendable.rb +19 -0
- data/lib/recommendable/acts_as_recommendable.rb +85 -0
- data/lib/recommendable/acts_as_recommended_to.rb +659 -0
- data/lib/recommendable/engine.rb +13 -0
- data/lib/recommendable/exceptions.rb +4 -0
- data/lib/recommendable/railtie.rb +6 -0
- data/lib/recommendable/version.rb +3 -0
- data/lib/tasks/recommendable_tasks.rake +1 -0
- data/recommendable.gemspec +32 -0
- data/script/rails +8 -0
- data/spec/configuration_spec.rb +9 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/bully.rb +2 -0
- data/spec/dummy/app/models/movie.rb +3 -0
- data/spec/dummy/app/models/php_framework.rb +2 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +56 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/recommendable.rb +16 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/db/migrate/20120128005553_create_likes.recommendable.rb +18 -0
- data/spec/dummy/db/migrate/20120128005554_create_dislikes.recommendable.rb +18 -0
- data/spec/dummy/db/migrate/20120128005555_create_ignores.recommendable.rb +18 -0
- data/spec/dummy/db/migrate/20120128020228_create_users.rb +9 -0
- data/spec/dummy/db/migrate/20120128020413_create_movies.rb +10 -0
- data/spec/dummy/db/migrate/20120128024632_create_php_frameworks.rb +9 -0
- data/spec/dummy/db/migrate/20120128024804_create_bullies.rb +9 -0
- data/spec/dummy/db/migrate/20120131195416_create_stashed_items.recommendable.rb +18 -0
- data/spec/dummy/db/schema.rb +89 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- 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 +6 -0
- data/spec/factories.rb +16 -0
- data/spec/models/dislike_spec.rb +27 -0
- data/spec/models/ignore_spec.rb +27 -0
- data/spec/models/like_spec.rb +28 -0
- data/spec/models/stashed_item_spec.rb +27 -0
- data/spec/models/user_benchmark_spec.rb +49 -0
- data/spec/models/user_spec.rb +320 -0
- data/spec/spec_helper.rb +28 -0
- metadata +254 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# This migration comes from recommendable (originally 20120131173909)
|
2
|
+
class CreateStashedItems < ActiveRecord::Migration
|
3
|
+
def up
|
4
|
+
create_table :stashed_items do |t|
|
5
|
+
t.references :user
|
6
|
+
t.references :stashable, :polymorphic => true
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :stashed_items, :stashable_id
|
11
|
+
add_index :stashed_items, :stashable_type
|
12
|
+
add_index :stashed_items, [:user_id, :stashable_id, :stashable_type], :unique => true, :name => "user_stashed_constraint"
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
drop_table :stashed_items
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
5
|
+
#
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
7
|
+
# database schema. If you need to create the application database on another
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
|
+
#
|
12
|
+
# It's strongly recommended to check this file into your version control system.
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define(:version => 20120131195416) do
|
15
|
+
|
16
|
+
create_table "bullies", :force => true do |t|
|
17
|
+
t.string "username"
|
18
|
+
t.datetime "created_at", :null => false
|
19
|
+
t.datetime "updated_at", :null => false
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table "dislikes", :force => true do |t|
|
23
|
+
t.integer "user_id"
|
24
|
+
t.integer "dislikeable_id"
|
25
|
+
t.string "dislikeable_type"
|
26
|
+
t.datetime "created_at", :null => false
|
27
|
+
t.datetime "updated_at", :null => false
|
28
|
+
end
|
29
|
+
|
30
|
+
add_index "dislikes", ["dislikeable_id"], :name => "index_dislikes_on_dislikeable_id"
|
31
|
+
add_index "dislikes", ["dislikeable_type"], :name => "index_dislikes_on_dislikeable_type"
|
32
|
+
add_index "dislikes", ["user_id", "dislikeable_id", "dislikeable_type"], :name => "user_dislike_constraint", :unique => true
|
33
|
+
|
34
|
+
create_table "ignores", :force => true do |t|
|
35
|
+
t.integer "user_id"
|
36
|
+
t.integer "ignoreable_id"
|
37
|
+
t.string "ignoreable_type"
|
38
|
+
t.datetime "created_at", :null => false
|
39
|
+
t.datetime "updated_at", :null => false
|
40
|
+
end
|
41
|
+
|
42
|
+
add_index "ignores", ["ignoreable_id"], :name => "index_ignores_on_ignoreable_id"
|
43
|
+
add_index "ignores", ["ignoreable_type"], :name => "index_ignores_on_ignoreable_type"
|
44
|
+
add_index "ignores", ["user_id", "ignoreable_id", "ignoreable_type"], :name => "user_ignore_constraint", :unique => true
|
45
|
+
|
46
|
+
create_table "likes", :force => true do |t|
|
47
|
+
t.integer "user_id"
|
48
|
+
t.integer "likeable_id"
|
49
|
+
t.string "likeable_type"
|
50
|
+
t.datetime "created_at", :null => false
|
51
|
+
t.datetime "updated_at", :null => false
|
52
|
+
end
|
53
|
+
|
54
|
+
add_index "likes", ["likeable_id"], :name => "index_likes_on_likeable_id"
|
55
|
+
add_index "likes", ["likeable_type"], :name => "index_likes_on_likeable_type"
|
56
|
+
add_index "likes", ["user_id", "likeable_id", "likeable_type"], :name => "user_like_constraint", :unique => true
|
57
|
+
|
58
|
+
create_table "movies", :force => true do |t|
|
59
|
+
t.string "title"
|
60
|
+
t.integer "year"
|
61
|
+
t.datetime "created_at", :null => false
|
62
|
+
t.datetime "updated_at", :null => false
|
63
|
+
end
|
64
|
+
|
65
|
+
create_table "php_frameworks", :force => true do |t|
|
66
|
+
t.string "name"
|
67
|
+
t.datetime "created_at", :null => false
|
68
|
+
t.datetime "updated_at", :null => false
|
69
|
+
end
|
70
|
+
|
71
|
+
create_table "stashed_items", :force => true do |t|
|
72
|
+
t.integer "user_id"
|
73
|
+
t.integer "stashable_id"
|
74
|
+
t.string "stashable_type"
|
75
|
+
t.datetime "created_at", :null => false
|
76
|
+
t.datetime "updated_at", :null => false
|
77
|
+
end
|
78
|
+
|
79
|
+
add_index "stashed_items", ["stashable_id"], :name => "index_stashed_items_on_stashable_id"
|
80
|
+
add_index "stashed_items", ["stashable_type"], :name => "index_stashed_items_on_stashable_type"
|
81
|
+
add_index "stashed_items", ["user_id", "stashable_id", "stashable_type"], :name => "user_stashed_constraint", :unique => true
|
82
|
+
|
83
|
+
create_table "users", :force => true do |t|
|
84
|
+
t.string "username"
|
85
|
+
t.datetime "created_at", :null => false
|
86
|
+
t.datetime "updated_at", :null => false
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
Binary file
|
Binary file
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Factory.define :user do |f|
|
2
|
+
f.username "test_user_%d"
|
3
|
+
end
|
4
|
+
|
5
|
+
Factory.define :movie do |f|
|
6
|
+
f.title "test_movie_%d"
|
7
|
+
f.year 2001
|
8
|
+
end
|
9
|
+
|
10
|
+
Factory.define :bully do |f|
|
11
|
+
f.username "bad_user_%d"
|
12
|
+
end
|
13
|
+
|
14
|
+
Factory.define :php_framework do |f|
|
15
|
+
f.name "CakePHP"
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class DislikeSpec < MiniTest::Spec
|
4
|
+
describe Recommendable::Dislike do
|
5
|
+
before :each do
|
6
|
+
@user = User.create(:username => "dave")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should not be created for an object that does not act_as_recommendedable" do
|
10
|
+
django = PhpFramework.create(:name => "django")
|
11
|
+
proc { @user.dislike(django) }.must_raise Recommendable::RecordNotRecommendableError
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be created for an object that does act_as_recommendable" do
|
15
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
16
|
+
@user.dislike(movie).must_equal true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not be created twice for the same user-object pair" do
|
20
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
21
|
+
|
22
|
+
@user.dislike(movie).must_equal true
|
23
|
+
@user.dislike(movie).must_be_nil
|
24
|
+
Recommendable::Dislike.count.must_equal 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class IgnoreSpec < MiniTest::Spec
|
4
|
+
describe Recommendable::Ignore do
|
5
|
+
before :each do
|
6
|
+
@user = User.create(:username => "dave")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should not be created for an object that does not act_as_recommendedable" do
|
10
|
+
web2py = PhpFramework.create(:name => "web2py")
|
11
|
+
proc { @user.ignore(web2py) }.must_raise Recommendable::RecordNotRecommendableError
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be created for an object that does act_as_recommendable" do
|
15
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
16
|
+
@user.ignore(movie).must_equal true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not be created twice for the same user-object pair" do
|
20
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
21
|
+
|
22
|
+
@user.ignore(movie).must_equal true
|
23
|
+
@user.ignore(movie).must_be_nil
|
24
|
+
Recommendable::Ignore.count.must_equal 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class LikeSpec < MiniTest::Spec
|
4
|
+
describe Recommendable::Like do
|
5
|
+
before :each do
|
6
|
+
@user = User.create(:username => "dave")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should not be created for an object that does not act_as_recommendedable" do
|
10
|
+
cake = PhpFramework.create(:name => "CakePHP")
|
11
|
+
proc { @user.like(cake) }.must_raise Recommendable::RecordNotRecommendableError
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be created for an object that does act_as_recommendable" do
|
15
|
+
movie = Movie.create(:title => "2001: A Space Odyssey", :year => 1968)
|
16
|
+
|
17
|
+
@user.like(movie).must_equal true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not be created twice for the same user-object pair" do
|
21
|
+
movie = Movie.create(:title => "2001: A Space Odyssey", :year => 1968)
|
22
|
+
|
23
|
+
@user.like(movie).must_equal true
|
24
|
+
@user.like(movie).must_be_nil
|
25
|
+
Recommendable::Like.count.must_equal 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class StashedItemSpec < MiniTest::Spec
|
4
|
+
describe Recommendable::StashedItem do
|
5
|
+
before :each do
|
6
|
+
@user = User.create(:username => "dave")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should not be created for an object that does not act_as_recommendedable" do
|
10
|
+
web2py = PhpFramework.create(:name => "web2py")
|
11
|
+
proc { @user.stash(web2py) }.must_raise Recommendable::RecordNotRecommendableError
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be created for an object that does act_as_recommendable" do
|
15
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
16
|
+
@user.stash(movie).must_equal true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not be created twice for the same user-object pair" do
|
20
|
+
movie = Movie.create(:title => "Star Wars: Episode I - The Phantom Menace", :year => 1999)
|
21
|
+
|
22
|
+
@user.stash(movie).must_equal true
|
23
|
+
@user.stash(movie).must_be_nil
|
24
|
+
Recommendable::StashedItem.count.must_equal 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class UserBenchmarkSpec < MiniTest::Unit::TestCase
|
4
|
+
def test_update_recommendations
|
5
|
+
if ENV["BENCH"] then
|
6
|
+
Recommendable.redis.flushdb
|
7
|
+
@actions = [:like, :dislike]
|
8
|
+
|
9
|
+
puts "\n"
|
10
|
+
|
11
|
+
assert_performance_exponential do |n|
|
12
|
+
@user = Factory(:user)
|
13
|
+
@users = []
|
14
|
+
@movies = []
|
15
|
+
|
16
|
+
# Make n users, 5n movies
|
17
|
+
n.times do
|
18
|
+
@users << Factory(:user)
|
19
|
+
@movies << Factory(:movie)
|
20
|
+
@movies << Factory(:movie)
|
21
|
+
@movies << Factory(:movie)
|
22
|
+
@movies << Factory(:movie)
|
23
|
+
@movies << Factory(:movie)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Main user randomly likes/dislikes 1/4 of the movies
|
27
|
+
@movies.sample(n/4).each do |m|
|
28
|
+
@user.send(@actions.sample, m)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Other users randomly like/dislike some movies
|
32
|
+
@movies.sample(n/2).each do |m|
|
33
|
+
@users.sample.send(@actions.sample, m)
|
34
|
+
@users.sample.send(@actions.sample, m)
|
35
|
+
@users.sample.send(@actions.sample, m)
|
36
|
+
end
|
37
|
+
|
38
|
+
@user.update_similarities
|
39
|
+
@user.update_recommendations
|
40
|
+
Recommendable.redis.flushdb
|
41
|
+
|
42
|
+
User.delete_all
|
43
|
+
Movie.delete_all
|
44
|
+
Recommendable::Like.delete_all
|
45
|
+
Recommendable::Dislike.delete_all
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class UserSpec < MiniTest::Spec
|
4
|
+
# User and Bully are skeleton classes defined in the dummy application for
|
5
|
+
# the sole purpose of running these tests. They're in spec/dummy/models/ but
|
6
|
+
# really, there's nothing there of note.
|
7
|
+
|
8
|
+
describe User do
|
9
|
+
describe "that does not act_as_recommendable" do
|
10
|
+
before :each do
|
11
|
+
@user = Factory(:bully)
|
12
|
+
@movie = Factory(:movie)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not be able to rate, ignore, or stash an item, and trying should not be enough to get Redis keys" do
|
16
|
+
proc { @user.like(@movie) }.must_raise NoMethodError
|
17
|
+
proc { @user.dislike(@movie) }.must_raise NoMethodError
|
18
|
+
proc { @user.ignore(@movie) }.must_raise NoMethodError
|
19
|
+
proc { @user.stash(@movie) }.must_raise NoMethodError
|
20
|
+
|
21
|
+
@movie.liked_by.must_be_empty
|
22
|
+
@movie.disliked_by.must_be_empty
|
23
|
+
|
24
|
+
proc { @user.create_recommended_to_sets }.must_raise NoMethodError
|
25
|
+
|
26
|
+
proc { @user.likes_set_for(Movie) }.must_raise NoMethodError
|
27
|
+
proc { @user.dislikes_set_for(Movie) }.must_raise NoMethodError
|
28
|
+
|
29
|
+
Recommendable.redis.smembers("Bully:#{@user.id}:likes:Movie").must_be_empty
|
30
|
+
Recommendable.redis.smembers("Bully:#{@user.id}:dislikes:Movie").must_be_empty
|
31
|
+
Recommendable.redis.smembers("Bully:#{@user.id}:similarities").must_be_empty
|
32
|
+
Recommendable.redis.smembers("Bully:#{@user.id}:predictions:Movie").must_be_empty
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "that does act_as_recommendable" do
|
37
|
+
before :each do
|
38
|
+
@user = Factory(:user)
|
39
|
+
@movie = Factory(:movie)
|
40
|
+
end
|
41
|
+
|
42
|
+
after :each do
|
43
|
+
Recommendable.redis.flushdb
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should be able to like a recommendable item" do
|
47
|
+
@user.like(@movie).must_equal true
|
48
|
+
@user.send(:create_recommended_to_sets)
|
49
|
+
Recommendable.redis.smembers("User:#{@user.id}:likes:Movie").must_include @movie.id.to_s
|
50
|
+
@user.send(:destroy_recommended_to_sets)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not dislike an item after liking it" do
|
54
|
+
@user.dislike(@movie)
|
55
|
+
@user.like(@movie).must_equal true
|
56
|
+
@user.dislikes?(@movie).must_equal false
|
57
|
+
@user.likes?(@movie).must_equal true
|
58
|
+
|
59
|
+
@user.send(:create_recommended_to_sets)
|
60
|
+
Recommendable.redis.smembers("User:#{@user.id}:dislikes:Movie").wont_include @movie.id.to_s
|
61
|
+
@user.send(:destroy_recommended_to_sets)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should not be ignoring an item after liking it" do
|
65
|
+
@user.ignore(@movie)
|
66
|
+
@user.like(@movie).must_equal true
|
67
|
+
@user.has_ignored?(@movie).must_equal false
|
68
|
+
@user.likes?(@movie).must_equal true
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not have an item stashed after liking it" do
|
72
|
+
@user.stash(@movie)
|
73
|
+
@user.like(@movie).must_equal true
|
74
|
+
@user.has_stashed?(@movie).must_equal false
|
75
|
+
@user.likes?(@movie).must_equal true
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be able to dislike a recommendable item" do
|
79
|
+
@user.dislike(@movie).must_equal true
|
80
|
+
@user.send(:create_recommended_to_sets)
|
81
|
+
Recommendable.redis.smembers("User:#{@user.id}:dislikes:Movie").must_include @movie.id.to_s
|
82
|
+
@user.send(:destroy_recommended_to_sets)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should not like an item after disliking it" do
|
86
|
+
@user.like(@movie)
|
87
|
+
@user.dislike(@movie).must_equal true
|
88
|
+
@user.likes?(@movie).must_equal false
|
89
|
+
@user.dislikes?(@movie).must_equal true
|
90
|
+
|
91
|
+
@user.send(:create_recommended_to_sets)
|
92
|
+
Recommendable.redis.smembers("User:#{@user.id}:dislikes:Movie").must_include @movie.id.to_s
|
93
|
+
Recommendable.redis.smembers("User:#{@user.id}:likes:Movie").wont_include @movie.id.to_s
|
94
|
+
@user.send(:destroy_recommended_to_sets)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should not be ignoring an item after disliking it" do
|
98
|
+
@user.ignore(@movie)
|
99
|
+
@user.dislike(@movie).must_equal true
|
100
|
+
@user.has_ignored?(@movie).must_equal false
|
101
|
+
@user.dislikes?(@movie).must_equal true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should not have an item stashed after disliking it" do
|
105
|
+
@user.stash(@movie)
|
106
|
+
@user.dislike(@movie).must_equal true
|
107
|
+
@user.has_stashed?(@movie).must_equal false
|
108
|
+
@user.dislikes?(@movie).must_equal true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should be able to ignore a recommendable item" do
|
112
|
+
@user.ignore(@movie).must_equal true
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should not like an item after ignoring it" do
|
116
|
+
@user.like(@movie)
|
117
|
+
@user.ignore(@movie).must_equal true
|
118
|
+
@user.likes?(@movie).must_equal false
|
119
|
+
@user.has_ignored?(@movie).must_equal true
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should not dislike an item after ignoring it" do
|
123
|
+
@user.dislike(@movie)
|
124
|
+
@user.ignore(@movie).must_equal true
|
125
|
+
@user.dislikes?(@movie).must_equal false
|
126
|
+
@user.has_ignored?(@movie).must_equal true
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should not have an item stashed after ignoring it" do
|
130
|
+
@user.stash(@movie)
|
131
|
+
@user.ignore(@movie).must_equal true
|
132
|
+
@user.has_stashed?(@movie).must_equal false
|
133
|
+
@user.has_ignored?(@movie).must_equal true
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should be able to stash a recommendable item" do
|
137
|
+
@user.stash(@movie).must_equal true
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not stash a liked item" do
|
141
|
+
@user.like(@movie)
|
142
|
+
@user.stash(@movie).must_be_nil
|
143
|
+
@user.likes?(@movie).must_equal true
|
144
|
+
@user.has_stashed?(@movie).must_equal false
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should not stash a disliked item" do
|
148
|
+
@user.dislike(@movie)
|
149
|
+
@user.stash(@movie).must_be_nil
|
150
|
+
@user.dislikes?(@movie).must_equal true
|
151
|
+
@user.has_stashed?(@movie).must_equal false
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should not have an item ignored after stashing it" do
|
155
|
+
@user.ignore(@movie)
|
156
|
+
@user.stash(@movie).must_equal true
|
157
|
+
@user.has_ignored?(@movie).must_equal false
|
158
|
+
@user.has_stashed?(@movie).must_equal true
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should not be able to rate or ignore an item that is not recommendable. doing so should not be enough to create Redis keys" do
|
162
|
+
@cakephp = Factory(:php_framework)
|
163
|
+
|
164
|
+
proc { @user.like(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
|
165
|
+
proc { @user.dislike(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
|
166
|
+
proc { @user.ignore(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
|
167
|
+
proc { @user.stash(@cakephp) }.must_raise Recommendable::RecordNotRecommendableError
|
168
|
+
|
169
|
+
proc { @cakephp.liked_by }.must_raise NoMethodError
|
170
|
+
proc { @cakephp.disliked_by }.must_raise NoMethodError
|
171
|
+
|
172
|
+
@user.send(:create_recommended_to_sets)
|
173
|
+
|
174
|
+
Recommendable.redis.smembers("User:#{@user.id}:likes:PhpFramework").must_be_empty
|
175
|
+
Recommendable.redis.smembers("User:#{@user.id}:dislikes:PhpFramework").must_be_empty
|
176
|
+
Recommendable.redis.smembers("User:#{@user.id}:similarities").must_be_empty
|
177
|
+
Recommendable.redis.smembers("User:#{@user.id}:predictions:Movie").must_be_empty
|
178
|
+
|
179
|
+
@user.send(:destroy_recommended_to_sets)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should not freak out when re-rating, re-ignoring, or re-stashing items" do
|
183
|
+
@user.like(@movie)
|
184
|
+
@user.like(@movie).must_be_nil
|
185
|
+
|
186
|
+
@user.dislike(@movie)
|
187
|
+
@user.dislike(@movie).must_be_nil
|
188
|
+
|
189
|
+
@user.ignore(@movie)
|
190
|
+
@user.ignore(@movie).must_be_nil
|
191
|
+
|
192
|
+
@user.stash(@movie)
|
193
|
+
@user.stash(@movie).must_be_nil
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should be able to unrate, unignore or unstash items" do
|
197
|
+
@user.like(@movie)
|
198
|
+
@user.unlike(@movie).must_equal true
|
199
|
+
|
200
|
+
@user.dislike(@movie)
|
201
|
+
@user.undislike(@movie).must_equal true
|
202
|
+
|
203
|
+
@user.ignore(@movie)
|
204
|
+
@user.unignore(@movie).must_equal true
|
205
|
+
|
206
|
+
@user.stash(@movie)
|
207
|
+
@user.unstash(@movie).must_equal true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "while getting recommendations" do
|
212
|
+
before :each do
|
213
|
+
@dave = Factory(:user)
|
214
|
+
@frank = Factory(:user)
|
215
|
+
@hal = Factory(:user)
|
216
|
+
@movie1 = Factory(:movie)
|
217
|
+
@movie2 = Factory(:movie)
|
218
|
+
@movie3 = Factory(:movie)
|
219
|
+
@movie4 = Factory(:movie)
|
220
|
+
@movie5 = Factory(:movie)
|
221
|
+
end
|
222
|
+
|
223
|
+
after :each do
|
224
|
+
Recommendable.redis.del "User:#{@dave.id}:similarities"
|
225
|
+
Recommendable.redis.del "User:#{@dave.id}:predictions:Movie"
|
226
|
+
Recommendable.redis.del "User:#{@frank.id}:similarities"
|
227
|
+
Recommendable.redis.del "User:#{@frank.id}:predictions:Movie"
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should get populated sorted sets for similarities and recommendations" do
|
231
|
+
@dave.like(@movie1)
|
232
|
+
@frank.like(@movie1)
|
233
|
+
@frank.like(@movie2)
|
234
|
+
@dave.send :update_similarities
|
235
|
+
@dave.send :update_recommendations
|
236
|
+
|
237
|
+
@dave.similar_raters.must_include @frank
|
238
|
+
@dave.recommendations_for(Movie).must_include @movie2
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should order similar users by similarity" do
|
242
|
+
@dave.like(@movie1)
|
243
|
+
@dave.like(@movie2)
|
244
|
+
@frank.like(@movie1)
|
245
|
+
@frank.dislike(@movie2)
|
246
|
+
@hal.like(@movie1)
|
247
|
+
@hal.like(@movie2)
|
248
|
+
|
249
|
+
# hal should be more similar to dave than frank
|
250
|
+
@dave.send :update_similarities
|
251
|
+
@dave.send :update_recommendations
|
252
|
+
|
253
|
+
@dave.similar_raters.must_equal [@hal, @frank]
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should correctly recommend an awesome movie or two. In the correct order, of course." do
|
257
|
+
@dave.like(@movie1)
|
258
|
+
@dave.like(@movie2)
|
259
|
+
@frank.like(@movie1)
|
260
|
+
@frank.like(@movie2)
|
261
|
+
@frank.like(@movie4)
|
262
|
+
@hal.like(@movie1)
|
263
|
+
@hal.like(@movie3)
|
264
|
+
|
265
|
+
@dave.send :update_similarities
|
266
|
+
@dave.send :update_recommendations
|
267
|
+
|
268
|
+
@dave.recommendations.must_equal [@movie4, @movie3]
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should not recommend rated items" do
|
272
|
+
@dave.like(@movie1)
|
273
|
+
@dave.dislike(@movie2)
|
274
|
+
@frank.like(@movie1)
|
275
|
+
@frank.like(@movie2)
|
276
|
+
@frank.like(@movie4)
|
277
|
+
@hal.like(@movie1)
|
278
|
+
@hal.like(@movie3)
|
279
|
+
|
280
|
+
@dave.send :update_similarities
|
281
|
+
@dave.send :update_recommendations
|
282
|
+
|
283
|
+
@dave.recommendations.wont_include @movie1
|
284
|
+
@dave.recommendations.wont_include @movie2
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should not recommend ignored items" do
|
288
|
+
@dave.like(@movie1)
|
289
|
+
@dave.like(@movie2)
|
290
|
+
@dave.ignore(@movie4)
|
291
|
+
@frank.like(@movie1)
|
292
|
+
@frank.like(@movie2)
|
293
|
+
@frank.like(@movie4)
|
294
|
+
@hal.like(@movie1)
|
295
|
+
@hal.like(@movie3)
|
296
|
+
|
297
|
+
@dave.send :update_similarities
|
298
|
+
@dave.send :update_recommendations
|
299
|
+
|
300
|
+
@dave.recommendations.wont_include @movie4
|
301
|
+
end
|
302
|
+
|
303
|
+
it "should not recommend stashed items" do
|
304
|
+
@dave.like(@movie1)
|
305
|
+
@dave.like(@movie2)
|
306
|
+
@dave.stash(@movie4)
|
307
|
+
@frank.like(@movie1)
|
308
|
+
@frank.like(@movie2)
|
309
|
+
@frank.like(@movie4)
|
310
|
+
@hal.like(@movie1)
|
311
|
+
@hal.like(@movie3)
|
312
|
+
|
313
|
+
@dave.send :update_similarities
|
314
|
+
@dave.send :update_recommendations
|
315
|
+
|
316
|
+
@dave.recommendations.wont_include @movie4
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|