predictable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +1 -0
- data/lib/predictable.rb +40 -0
- data/lib/predictable/item.rb +97 -0
- data/lib/predictable/recommender.rb +72 -0
- data/lib/predictable/user.rb +78 -0
- data/lib/predictable/version.rb +3 -0
- data/predictable.gemspec +27 -0
- data/spec/predictable/item_spec.rb +105 -0
- data/spec/predictable/recommender_spec.rb +122 -0
- data/spec/predictable/user_spec.rb +55 -0
- data/spec/spec_helper.rb +1 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NjNkNTE5YTc1NjA5MDEyNjc4YTk1NDViMThmYjBiZjY5ZDkzNTQ3Ng==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmY0YmRmMjgxZDhhMjU1OTBjMjhkYmMwMjc2YjM3YmMyYjA3MGQ2NA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWI0NGUzYjkyZThjMzY5ZWEwODA1YmIzMTg3ZjQ4NDUzZGM1OWExOWRmNzVm
|
10
|
+
NzhlOTJjNWYxMjAxMWU1MTk2NzY2YmQ2NzExMDM1OWIyYjFiYTExYzcwMWYz
|
11
|
+
M2RjODllNDRjMjBkZGNmZDFmODlmMzdhYzQxY2I0OTJiMmYyNDc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjBkZjExNGRhYzU2NjU3NjUxYTMzMmY4NzYzYzAyNDE1MzY4OGYxYjlhMjM2
|
14
|
+
MzY3MDViYTkyYWMyMzJmYzZkZTI0NTc4MDBmMTJiMTEzYzRjYmExYzRmOTgw
|
15
|
+
NTk3NGU5MWEyMjY3MjY1YmNmZjJjODY2NDU1ZTJmMzA2OWE4ZDY=
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --require spec_helper
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 JS Boulanger
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Predictable
|
2
|
+
|
3
|
+
Predictable is a Ruby DSL for Prediction.io, an open source machine learning server built on top of Hadoop. It makes it easy to implement predictive features such as personalization, recommendations, and content discovery in your Ruby/Rails application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'predictable'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install predictable
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Include Predictable::User in your application's User model.
|
22
|
+
|
23
|
+
class User < ActiveRecord::Base
|
24
|
+
include Predictable::User
|
25
|
+
|
26
|
+
...
|
27
|
+
end
|
28
|
+
|
29
|
+
user = User.create(email: "js@perkhub.com")
|
30
|
+
user.add_to_recommender
|
31
|
+
|
32
|
+
Include the Predictable::Item in the models of the items you want to recommend.
|
33
|
+
|
34
|
+
class Offer < ActiveRecord::Base
|
35
|
+
include Predictable::Item
|
36
|
+
|
37
|
+
after_create { add_to_recommender }
|
38
|
+
after_destroy { delete_from_recommender }
|
39
|
+
|
40
|
+
...
|
41
|
+
end
|
42
|
+
|
43
|
+
Get 10 recommended items for a user.
|
44
|
+
|
45
|
+
Offer.recommended_for(user)
|
46
|
+
current_user.recommended_offers.where("created_at > ?", 1.week.ago).limit(10)
|
47
|
+
|
48
|
+
Get 10 items most similar to an existing item.
|
49
|
+
|
50
|
+
Offer.similar_to(offer)
|
51
|
+
|
52
|
+
|
53
|
+
current_user.recommended_offers.limit(10)
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/predictable.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "predictable/item"
|
2
|
+
require "predictable/recommender"
|
3
|
+
require "predictable/user"
|
4
|
+
require "predictable/version"
|
5
|
+
|
6
|
+
module Predictable
|
7
|
+
class << self
|
8
|
+
def default_config
|
9
|
+
{
|
10
|
+
:api_url => "http://localhost:8000",
|
11
|
+
:threads => 10,
|
12
|
+
:api_version => ""
|
13
|
+
}.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
def client
|
17
|
+
@client ||= PredictionIO::Client.new(
|
18
|
+
config[:app_key],
|
19
|
+
config[:threads],
|
20
|
+
config[:api_url],
|
21
|
+
config[:api_version]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
@config ||= default_config
|
27
|
+
end
|
28
|
+
|
29
|
+
def config=(new_config)
|
30
|
+
config.merge!(new_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
def engines
|
34
|
+
{
|
35
|
+
:recommendation_engine => config[:recommendation_engine],
|
36
|
+
:similarity_engine => config[:similarity_engine]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module Predictable
|
5
|
+
# == Predictable Item Role
|
6
|
+
#
|
7
|
+
# Defines the Item role for the recommender. This module should
|
8
|
+
# be included in any model that represents items that will
|
9
|
+
# be recommended.
|
10
|
+
#
|
11
|
+
# class Offer < ActiveRecord::Base
|
12
|
+
# include Predictable::Item
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Article < ActiveRecord::Base
|
17
|
+
# include Predictable::Item
|
18
|
+
# ...
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
module Item
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
|
24
|
+
included do
|
25
|
+
# if active_record? defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
26
|
+
after_create { add_to_recommender }
|
27
|
+
after_destroy { delete_from_recommender }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the item id
|
31
|
+
def pio_iid
|
32
|
+
"#{self.class.pio_itype}-#{id}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the item types
|
36
|
+
def pio_itypes
|
37
|
+
[self.class.pio_itype]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates or updates the item in prediction.io
|
41
|
+
# When the item already exists, it updates it.
|
42
|
+
# The operation is done asynchronously.
|
43
|
+
def add_to_recommender(attrs={})
|
44
|
+
recommender.create_item(self, attrs)
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Removes the item from prediction.io
|
49
|
+
# The operation is done asynchronously.
|
50
|
+
def delete_from_recommender
|
51
|
+
recommender.delete_item(self)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def recommender
|
58
|
+
self.class.recommender
|
59
|
+
end
|
60
|
+
|
61
|
+
module ClassMethods
|
62
|
+
attr_writer :pio_engines, :recommender
|
63
|
+
|
64
|
+
def pio_engines
|
65
|
+
@pio_engines || Predictable.engines
|
66
|
+
end
|
67
|
+
|
68
|
+
def recommender
|
69
|
+
@recommender ||= Recommender.new(Predictable.client, pio_engines)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pio_itype
|
73
|
+
to_s.underscore
|
74
|
+
end
|
75
|
+
|
76
|
+
def recommended_for(user, opts = {})
|
77
|
+
options = { "itypes" => [pio_itype] }.merge(opts)
|
78
|
+
limit = options.delete("limit") || 10
|
79
|
+
|
80
|
+
item_ids = recommender.recommended_items(user, limit, options)
|
81
|
+
item_ids = item_ids.map { |id| id.gsub! /[^\d]/, '' }
|
82
|
+
where(:id => item_ids)
|
83
|
+
end
|
84
|
+
|
85
|
+
def similar_to(item, opts={})
|
86
|
+
options = opts.stringify_keys
|
87
|
+
options["pio_itypes"] ||= [pio_itype]
|
88
|
+
limit = options.delete("limit") || 10
|
89
|
+
|
90
|
+
item_ids = recommender.similar_items(item, limit, options)
|
91
|
+
item_ids = item_ids.map { |id| id.gsub! /[^\d]/, '' }
|
92
|
+
where(:id => item_ids)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
require 'predictionio'
|
3
|
+
|
4
|
+
module Predictable
|
5
|
+
# == Recommender
|
6
|
+
#
|
7
|
+
# Defines the Recommender interface and implements it using
|
8
|
+
# the Prediction.io API.
|
9
|
+
#
|
10
|
+
# recommender = Recommender.new(client)
|
11
|
+
# recommender.create_item(item)
|
12
|
+
# recommender.create_user(user)
|
13
|
+
# recommender.record_action(user, "conversion", item)
|
14
|
+
# recommender.recommended_items(user, 10)
|
15
|
+
#
|
16
|
+
class Recommender
|
17
|
+
attr_reader :client
|
18
|
+
attr_accessor :recommendation_engine, :similarity_engine
|
19
|
+
|
20
|
+
def initialize(client, engines={})
|
21
|
+
@client = client
|
22
|
+
@recommendation_engine = engines[:recommendation_engine]
|
23
|
+
@similarity_engine = engines[:similarity_engine]
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_item(item, attrs = {})
|
27
|
+
client.acreate_item(item.pio_iid, item.pio_itypes, attrs)
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_item(item)
|
31
|
+
client.adelete_item(item.pio_iid)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_user(user, attrs = {})
|
35
|
+
client.acreate_user(user.pio_uid, attrs)
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_user(user)
|
39
|
+
client.adelete_user(user.pio_uid)
|
40
|
+
end
|
41
|
+
|
42
|
+
def record_action(user, action, item, opts = {})
|
43
|
+
client.identify(user.pio_uid)
|
44
|
+
client.arecord_action_on_item(action.to_s, item.pio_iid, opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def recommended_items(user, n, opts = {})
|
48
|
+
options = opts.stringify_keys
|
49
|
+
client.identify(user.pio_uid)
|
50
|
+
begin
|
51
|
+
client.get_itemrec_top_n(recommendation_engine, n, opts)
|
52
|
+
rescue PredictionIO::Client::ItemRecNotFoundError => e
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def similar_items(item, n, opts = {})
|
58
|
+
begin
|
59
|
+
client.get_itemsim_top_n(similarity_engine, item.pio_iid, n, opts)
|
60
|
+
rescue PredictionIO::Client::ItemSimNotFoundError
|
61
|
+
[]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Predictable
|
4
|
+
# == Predictable User Role
|
5
|
+
#
|
6
|
+
# Defines the User role for the recommender. This module should
|
7
|
+
# be included in the User model of the appliation.
|
8
|
+
#
|
9
|
+
# class User < ActiveRecord::Base
|
10
|
+
# include Predictable::User
|
11
|
+
# ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
module User
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
|
17
|
+
included do
|
18
|
+
# if active_record? defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
19
|
+
after_create { add_to_recommender }
|
20
|
+
after_destroy { delete_from_recommender }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the user id
|
24
|
+
def pio_uid
|
25
|
+
self.id
|
26
|
+
end
|
27
|
+
|
28
|
+
# Record a user action in the recommender.
|
29
|
+
# Actions cannot be overwritten or deleted in prediction.io
|
30
|
+
# The operation is done asynchronously.
|
31
|
+
#
|
32
|
+
# user.record_action(:conversion, item)
|
33
|
+
#
|
34
|
+
def record_action(action, item, opts={})
|
35
|
+
recommender.record_action(self, action, item, opts)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def record_conversion(item, opts={})
|
40
|
+
record_action(:conversion, item, opts)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates or updates a user in prediction.io
|
45
|
+
# When the user id already exists it updates the user.
|
46
|
+
# The operation is done asynchronously.
|
47
|
+
def add_to_recommender(attrs={})
|
48
|
+
recommender.create_user(self, attrs)
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Removes the user from prediction.io.
|
53
|
+
# The operation is done asynchronously.
|
54
|
+
def delete_from_recommender
|
55
|
+
recommender.delete_user(self)
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def recommender
|
62
|
+
self.class.recommender
|
63
|
+
end
|
64
|
+
|
65
|
+
module ClassMethods
|
66
|
+
attr_writer :pio_engines, :recommender
|
67
|
+
|
68
|
+
def pio_engines
|
69
|
+
@pio_engines || Predictable.engines
|
70
|
+
end
|
71
|
+
|
72
|
+
def recommender
|
73
|
+
@recommender ||= Recommender.new(Predictable.client, pio_engines)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
data/predictable.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'predictable/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "predictable"
|
8
|
+
spec.version = Predictable::VERSION
|
9
|
+
spec.authors = ["JS Boulanger"]
|
10
|
+
spec.email = ["jsboulanger@gmail.com"]
|
11
|
+
spec.description = "Ruby/Rails DSL for PredictionIO, an open source machine learning server."
|
12
|
+
spec.summary = "Ruby/Rails DSL for PredictionIO"
|
13
|
+
spec.homepage = "https://github.com/jsboulanger/predictable"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency('activerecord')
|
22
|
+
spec.add_dependency('predictionio')
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
describe Predictable::Item do
|
4
|
+
let(:recommender) { Predictable::Recommender.new(double("Client").as_null_object) }
|
5
|
+
|
6
|
+
# TODO: Test with ActiveRecord::Base
|
7
|
+
class MyItem < OpenStruct # ActiveRecord::Base
|
8
|
+
include Predictable::Item
|
9
|
+
|
10
|
+
def self.where(args)
|
11
|
+
args[:id].map do |i|
|
12
|
+
self.new(:id => i.to_i)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:item) { MyItem.new(:id => 1) }
|
18
|
+
let(:user) { double("User", :pio_uid => 2) }
|
19
|
+
|
20
|
+
before do
|
21
|
+
MyItem.recommender = recommender
|
22
|
+
end
|
23
|
+
subject { item }
|
24
|
+
|
25
|
+
its(:pio_iid) { should == "my_item-#{item.id}" }
|
26
|
+
its(:pio_itypes) { should == ['my_item'] }
|
27
|
+
|
28
|
+
describe "#add_to_recommender" do
|
29
|
+
it "should create the item in the recommender" do
|
30
|
+
recommender.should_receive(:create_item).with(item, {})
|
31
|
+
item.add_to_recommender
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return nil" do
|
35
|
+
item.add_to_recommender.should be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#delete_from_recommender" do
|
40
|
+
it "should delete the item from the recommender" do
|
41
|
+
recommender.should_receive(:delete_item).with(item)
|
42
|
+
item.delete_from_recommender
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return nil" do
|
46
|
+
item.delete_from_recommender.should be_nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe ".recommended_for" do
|
51
|
+
it "should get recommended items from the recommender" do
|
52
|
+
recommender.should_receive(:recommended_items).with(user, 10, {"itypes" => ["my_item"] }).and_return []
|
53
|
+
MyItem.recommended_for(user, 10)
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when there are recommendations" do
|
57
|
+
before do
|
58
|
+
recommender.stub(:recommended_items).and_return [item.pio_iid]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return the recommended items" do
|
62
|
+
MyItem.recommended_for(user, 1).should == [item]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when there are no recommendations" do
|
67
|
+
before do
|
68
|
+
recommender.stub(:recommended_items).and_return []
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return an empty array" do
|
72
|
+
MyItem.recommended_for(user, 1).should == []
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ".similar_to" do
|
78
|
+
let(:similar_item) { MyItem.new(:id => 1) }
|
79
|
+
|
80
|
+
it "should get similar items from the recommender" do
|
81
|
+
recommender.should_receive(:similar_items).with(item, 10, {"pio_itypes"=>["my_item"]}).and_return []
|
82
|
+
MyItem.similar_to(item, 10)
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when there are similar items" do
|
86
|
+
before do
|
87
|
+
recommender.stub(:similar_items).and_return [similar_item.pio_iid]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return the similar items" do
|
91
|
+
MyItem.similar_to(item, 10).should == [similar_item]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when there are no similar items" do
|
96
|
+
before do
|
97
|
+
recommender.stub(:similar_items).and_return []
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should an empty array" do
|
101
|
+
MyItem.similar_to(item, 10).should == []
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
describe Predictable::Recommender do
|
2
|
+
let(:client) { double("PredictionIO Client") }
|
3
|
+
let(:recommender) { Predictable::Recommender.new(client) }
|
4
|
+
|
5
|
+
let(:item) { double("Item", :pio_iid => "item-123", :pio_itypes => ["item"]) }
|
6
|
+
|
7
|
+
# TODO: item should behave as an item
|
8
|
+
|
9
|
+
describe "#create_item" do
|
10
|
+
it "should call the api" do
|
11
|
+
client.should_receive(:acreate_item)
|
12
|
+
.with("item-123", ["item"], {})
|
13
|
+
recommender.create_item(item)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#delete_item" do
|
18
|
+
it "should call the api" do
|
19
|
+
client.should_receive(:adelete_item)
|
20
|
+
.with("item-123")
|
21
|
+
recommender.delete_item(item)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:user) { double("User", :pio_uid => "user-1") }
|
26
|
+
# TODO: user should behave as a user
|
27
|
+
|
28
|
+
describe "#create_user" do
|
29
|
+
it "should call the api" do
|
30
|
+
client.should_receive(:acreate_user)
|
31
|
+
.with("user-1", {})
|
32
|
+
recommender.create_user(user)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#delete_user" do
|
37
|
+
it "should call the api" do
|
38
|
+
client.should_receive(:adelete_user)
|
39
|
+
.with("user-1")
|
40
|
+
recommender.delete_user(user)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#record_action" do
|
45
|
+
it "should call the api" do
|
46
|
+
client.should_receive(:identify).with("user-1")
|
47
|
+
client.should_receive(:arecord_action_on_item)
|
48
|
+
.with("conversion", "item-123", {})
|
49
|
+
recommender.record_action(user, :conversion, item)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#recommended_items" do
|
54
|
+
before do
|
55
|
+
client.stub(:identify)
|
56
|
+
recommender.recommendation_engine = "itemrec"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should call the api" do
|
60
|
+
client.should_receive(:identify).with("user-1")
|
61
|
+
client.should_receive(:get_itemrec_top_n)
|
62
|
+
.with("itemrec", 10, {})
|
63
|
+
.and_return([])
|
64
|
+
recommender.recommended_items(user, 10)
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when items are found" do
|
68
|
+
before do
|
69
|
+
client.stub(:get_itemrec_top_n).and_return(["item-1", "item-2"])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return an array of item ids" do
|
73
|
+
recommender.recommended_items(user, 10).should == ["item-1", "item-2"]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when no items are found" do
|
78
|
+
before do
|
79
|
+
client.stub(:get_itemrec_top_n).and_raise(PredictionIO::Client::ItemRecNotFoundError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return an empty array" do
|
83
|
+
recommender.recommended_items(user, 10).should == []
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end # recommended_items
|
87
|
+
|
88
|
+
describe "#similar_items" do
|
89
|
+
before do
|
90
|
+
client.stub(:identify)
|
91
|
+
recommender.similarity_engine = "itemsim"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should call the api" do
|
95
|
+
client.should_receive(:get_itemsim_top_n)
|
96
|
+
.with("itemsim", "item-123", 10, {})
|
97
|
+
.and_return([])
|
98
|
+
|
99
|
+
recommender.similar_items(item, 10)
|
100
|
+
end
|
101
|
+
|
102
|
+
context "when items are found" do
|
103
|
+
before do
|
104
|
+
client.stub(:get_itemsim_top_n).and_return(["item-1", "item-2"])
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should return an array of item ids" do
|
108
|
+
recommender.similar_items(item, 10).should == ["item-1", "item-2"]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when no items are found" do
|
113
|
+
before do
|
114
|
+
client.stub(:get_itemsim_top_n).and_raise(PredictionIO::Client::ItemSimNotFoundError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should return an empty array" do
|
118
|
+
recommender.similar_items(item, 10).should == []
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end # similar_items
|
122
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
describe Predictable::User do
|
4
|
+
let(:recommender) { Predictable::Recommender.new(double("Client").as_null_object) }
|
5
|
+
|
6
|
+
# TODO: Test with ActiveRecord::Base
|
7
|
+
class MyUser < OpenStruct
|
8
|
+
include Predictable::User
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
MyUser.recommender = recommender
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:user) { MyUser.new(:id => 1) }
|
16
|
+
let(:item) { double("Item", :pio_iid => 2) }
|
17
|
+
subject { user }
|
18
|
+
|
19
|
+
its(:pio_uid) { should == 1 }
|
20
|
+
|
21
|
+
describe "#record_action" do
|
22
|
+
it "should record an action with the recommender" do
|
23
|
+
recommender.should_receive(:record_action)
|
24
|
+
.with(user, :rate, item, :rating => 4)
|
25
|
+
|
26
|
+
user.record_action(:rate, item, :rating => 4)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return nil" do
|
30
|
+
user.record_action(:rate, item).should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#add_to_recommender" do
|
35
|
+
it "should create a user in the recommender" do
|
36
|
+
recommender.should_receive(:create_user).with(user, {})
|
37
|
+
user.add_to_recommender
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return nil" do
|
41
|
+
user.add_to_recommender.should be_nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#delete_from_recommender" do
|
46
|
+
it "should delete the user from the recommender" do
|
47
|
+
recommender.should_receive(:delete_user).with(user)
|
48
|
+
user.delete_from_recommender
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return nil" do
|
52
|
+
user.delete_from_recommender.should be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'predictable'
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: predictable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JS Boulanger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: predictionio
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Ruby/Rails DSL for PredictionIO, an open source machine learning server.
|
84
|
+
email:
|
85
|
+
- jsboulanger@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- .gitignore
|
91
|
+
- .rspec
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/predictable.rb
|
97
|
+
- lib/predictable/item.rb
|
98
|
+
- lib/predictable/recommender.rb
|
99
|
+
- lib/predictable/user.rb
|
100
|
+
- lib/predictable/version.rb
|
101
|
+
- predictable.gemspec
|
102
|
+
- spec/predictable/item_spec.rb
|
103
|
+
- spec/predictable/recommender_spec.rb
|
104
|
+
- spec/predictable/user_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
homepage: https://github.com/jsboulanger/predictable
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 2.1.11
|
127
|
+
signing_key:
|
128
|
+
specification_version: 4
|
129
|
+
summary: Ruby/Rails DSL for PredictionIO
|
130
|
+
test_files:
|
131
|
+
- spec/predictable/item_spec.rb
|
132
|
+
- spec/predictable/recommender_spec.rb
|
133
|
+
- spec/predictable/user_spec.rb
|
134
|
+
- spec/spec_helper.rb
|