recommendify_whosv 0.5.8

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.
@@ -0,0 +1,63 @@
1
+ class Recommendify::SimilarityMatrix
2
+
3
+ attr_reader :write_queue
4
+
5
+ def initialize(opts={})
6
+ @opts = opts
7
+ @write_queue = Hash.new{ |h,k| h[k] = {} }
8
+ end
9
+
10
+ def redis_key(append=nil)
11
+ [@opts.fetch(:redis_prefix), @opts.fetch(:key), append].flatten.compact.join(":")
12
+ end
13
+
14
+ def max_neighbors
15
+ @opts[:max_neighbors] || Recommendify::DEFAULT_MAX_NEIGHBORS
16
+ end
17
+
18
+ def update(item_id, neighbors)
19
+ neighbors.each do |neighbor_id, score|
20
+ if @write_queue[item_id].has_key?(neighbor_id)
21
+ @write_queue[item_id][neighbor_id] += score
22
+ else
23
+ @write_queue[item_id][neighbor_id] = score
24
+ end
25
+ end
26
+ end
27
+
28
+ def [](item_id)
29
+ if @write_queue.has_key?(item_id)
30
+ @write_queue[item_id]
31
+ else
32
+ retrieve_item(item_id)
33
+ end
34
+ end
35
+
36
+ def commit_item!(item_id)
37
+ serialized = serialize_item(item_id)
38
+ Recommendify.redis.hset(redis_key, item_id, serialized)
39
+ @write_queue.delete(item_id)
40
+ end
41
+
42
+ # optimize: the items are already stored in a sorted fashion. we shouldn't
43
+ # throw away this info by storing them in a hash (and re-sorting later). maybe
44
+ # use activesupport's orderedhash?
45
+ def retrieve_item(item_id)
46
+ data = Recommendify.redis.hget(redis_key, item_id)
47
+ return {} if data.nil?
48
+ Hash[data.split("|").map{ |i| (k,s=i.split(":")) && [k,s.to_f] }]
49
+ end
50
+
51
+ private
52
+
53
+ # optimize: implement a better sort. never add more than 50 items the the array
54
+ def serialize_item(item_id, max_precision=5)
55
+ items = @write_queue[item_id].to_a
56
+ items.sort!{ |a,b| b[1] <=> a[1] }
57
+ #items = items[0..max_neighbors-1]
58
+ #items = items.map{ |i,s| s>0 ? "#{i}:#{s.to_s[0..max_precision]}" : nil }
59
+ items = items.map{ |i,s| "#{i}:#{s.to_s[0..max_precision]}" }
60
+ items.compact * "|"
61
+ end
62
+
63
+ end
@@ -0,0 +1,53 @@
1
+ class Recommendify::SparseMatrix
2
+
3
+ def initialize(opts={})
4
+ @opts = opts
5
+ end
6
+
7
+ def redis_key
8
+ [@opts.fetch(:redis_prefix), @opts.fetch(:key)].join(":")
9
+ end
10
+
11
+ def [](x,y)
12
+ k_get(key(x,y))
13
+ end
14
+
15
+ def []=(x,y,v)
16
+ v == 0 ? k_del(key(x,y)) : k_set(key(x,y), v)
17
+ end
18
+
19
+ def incr(x,y)
20
+ k_incr(key(x,y))
21
+ end
22
+
23
+ private
24
+
25
+ def key(x,y)
26
+ [x,y].sort.join(":")
27
+ end
28
+
29
+ def k_set(key, val)
30
+ Recommendify.redis.hset(redis_key, key, val)
31
+ end
32
+
33
+ def k_del(key)
34
+ Recommendify.redis.hdel(redis_key, key)
35
+ end
36
+
37
+ def k_get(key)
38
+ Recommendify.redis.hget(redis_key, key).to_f
39
+ end
40
+
41
+ def k_incr(key)
42
+ Recommendify.redis.hincrby(redis_key, key, 1)
43
+ end
44
+
45
+ # OPTIMIZE: use scripting/lua in redis 2.6
46
+ def k_delall(*keys)
47
+ Recommendify.redis.hkeys(redis_key).each do |iikey|
48
+ next unless (iikey.split(":") & keys).size > 0
49
+ Recommendify.redis.hdel(redis_key, iikey)
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,9 @@
1
+ require "recommendify/recommendify"
2
+ require "recommendify/sparse_matrix"
3
+ require "recommendify/cc_matrix"
4
+ require "recommendify/similarity_matrix"
5
+ require "recommendify/input_matrix"
6
+ require "recommendify/jaccard_input_matrix"
7
+ require "recommendify/cosine_input_matrix"
8
+ require "recommendify/base"
9
+ require "recommendify/neighbor"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "recommendify_whosv"
6
+ s.version = "0.5.8"
7
+ s.date = Date.today.to_s
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Asmuth"]
10
+ s.email = ["paul@paulasmuth.com"]
11
+ s.homepage = "http://github.com/paulasmuth/recommendify"
12
+ s.summary = %q{ruby/redis based recommendation engine (collaborative filtering)}
13
+ s.description = %q{Recommendify is a distributed, incremental item-based recommendation engine for binary input ratings. It's based on ruby and redis and uses an approach called "Collaborative Filtering"}
14
+ s.licenses = ["MIT"]
15
+
16
+ s.extensions = ['ext/extconf.rb']
17
+
18
+ s.add_dependency "redis", ">= 2.2.2"
19
+
20
+ s.add_development_dependency "rspec", "~> 2.8.0"
21
+
22
+ s.files = `git ls-files`.split("\n") - [".gitignore", ".rspec", ".travis.yml"]
23
+ s.test_files = `git ls-files -- spec/*`.split("\n")
24
+ s.require_paths = ["lib"]
25
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,188 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::Base do
4
+
5
+ before(:each) do
6
+ flush_redis!
7
+ Recommendify::Base.send(:class_variable_set, :@@max_neighbors, nil)
8
+ Recommendify::Base.send(:class_variable_set, :@@input_matrices, {})
9
+ end
10
+
11
+ describe "configuration" do
12
+
13
+ it "should return default max_neighbors if not configured" do
14
+ Recommendify::DEFAULT_MAX_NEIGHBORS.should == 50
15
+ sm = Recommendify::Base.new
16
+ sm.max_neighbors.should == 50
17
+ end
18
+
19
+ it "should remember max_neighbors if configured" do
20
+ Recommendify::Base.max_neighbors(23)
21
+ sm = Recommendify::Base.new
22
+ sm.max_neighbors.should == 23
23
+ end
24
+
25
+ it "should add an input_matrix by 'key'" do
26
+ Recommendify::Base.input_matrix(:myinput, :similarity_func => :jaccard)
27
+ Recommendify::Base.send(:class_variable_get, :@@input_matrices).keys.should == [:myinput]
28
+ end
29
+
30
+ it "should retrieve an input_matrix on a new instance" do
31
+ Recommendify::Base.input_matrix(:myinput, :similarity_func => :jaccard)
32
+ sm = Recommendify::Base.new
33
+ lambda{ sm.myinput }.should_not raise_error
34
+ end
35
+
36
+ it "should retrieve an input_matrix on a new instance and correctly overload respond_to?" do
37
+ Recommendify::Base.input_matrix(:myinput, :similarity_func => :jaccard)
38
+ sm = Recommendify::Base.new
39
+ sm.respond_to?(:process!).should be_true
40
+ sm.respond_to?(:myinput).should be_true
41
+ sm.respond_to?(:fnord).should be_false
42
+ end
43
+
44
+ it "should retrieve an input_matrix on a new instance and intialize the correct class" do
45
+ Recommendify::Base.input_matrix(:myinput, :similarity_func => :jaccard)
46
+ sm = Recommendify::Base.new
47
+ sm.myinput.should be_a(Recommendify::JaccardInputMatrix)
48
+ end
49
+
50
+ end
51
+
52
+ describe "process_item!" do
53
+
54
+ it "should call similarities_for on each input_matrix" do
55
+ Recommendify::Base.input_matrix(:myfirstinput, :similarity_func => :jaccard)
56
+ Recommendify::Base.input_matrix(:mysecondinput, :similarity_func => :jaccard)
57
+ sm = Recommendify::Base.new
58
+ sm.myfirstinput.should_receive(:similarities_for).with("fnorditem").and_return([["fooitem",0.5]])
59
+ sm.mysecondinput.should_receive(:similarities_for).with("fnorditem").and_return([["fooitem",0.5]])
60
+ sm.similarity_matrix.stub!(:update)
61
+ sm.process_item!("fnorditem")
62
+ end
63
+
64
+ it "should call similarities_for on each input_matrix and add all outputs to the similarity matrix" do
65
+ Recommendify::Base.input_matrix(:myfirstinput, :similarity_func => :jaccard)
66
+ Recommendify::Base.input_matrix(:mysecondinput, :similarity_func => :jaccard)
67
+ sm = Recommendify::Base.new
68
+ sm.myfirstinput.should_receive(:similarities_for).and_return([["fooitem",0.5]])
69
+ sm.mysecondinput.should_receive(:similarities_for).and_return([["fooitem",0.75], ["baritem", 1.0]])
70
+ sm.similarity_matrix.should_receive(:update).with("fnorditem", [["fooitem",0.5]])
71
+ sm.similarity_matrix.should_receive(:update).with("fnorditem", [["fooitem",0.75], ["baritem", 1.0]])
72
+ sm.process_item!("fnorditem")
73
+ end
74
+
75
+ it "should call similarities_for on each input_matrix and add all outputs to the similarity matrix with weight" do
76
+ Recommendify::Base.input_matrix(:myfirstinput, :similarity_func => :jaccard, :weight => 4.0)
77
+ Recommendify::Base.input_matrix(:mysecondinput, :similarity_func => :jaccard)
78
+ sm = Recommendify::Base.new
79
+ sm.myfirstinput.should_receive(:similarities_for).and_return([["fooitem",0.5]])
80
+ sm.mysecondinput.should_receive(:similarities_for).and_return([["fooitem",0.75], ["baritem", 1.0]])
81
+ sm.similarity_matrix.should_receive(:update).with("fnorditem", [["fooitem",2.0]])
82
+ sm.similarity_matrix.should_receive(:update).with("fnorditem", [["fooitem",0.75], ["baritem", 1.0]])
83
+ sm.process_item!("fnorditem")
84
+ end
85
+
86
+ it "should retrieve all items from all input matrices" do
87
+ Recommendify::Base.input_matrix(:anotherinput, :similarity_func => :test, :all_items => ["foo", "bar"])
88
+ Recommendify::Base.input_matrix(:yetanotherinput, :similarity_func => :test, :all_items => ["fnord", "shmoo"])
89
+ sm = Recommendify::Base.new
90
+ sm.all_items.length.should == 4
91
+ sm.all_items.should include("foo")
92
+ sm.all_items.should include("bar")
93
+ sm.all_items.should include("fnord")
94
+ sm.all_items.should include("shmoo")
95
+ end
96
+
97
+ it "should retrieve all items from all input matrices (uniquely)" do
98
+ Recommendify::Base.input_matrix(:anotherinput, :similarity_func => :test, :all_items => ["foo", "bar"])
99
+ Recommendify::Base.input_matrix(:yetanotherinput, :similarity_func => :test, :all_items => ["fnord", "bar"])
100
+ sm = Recommendify::Base.new
101
+ sm.all_items.length.should == 3
102
+ sm.all_items.should include("foo")
103
+ sm.all_items.should include("bar")
104
+ sm.all_items.should include("fnord")
105
+ end
106
+
107
+ end
108
+
109
+ describe "process!" do
110
+
111
+ it "should call process_item for all input_matrix.all_items's" do
112
+ Recommendify::Base.input_matrix(:anotherinput, :similarity_func => :test, :all_items => ["foo", "bar"])
113
+ Recommendify::Base.input_matrix(:yetanotherinput, :similarity_func => :test, :all_items => ["fnord", "shmoo"])
114
+ sm = Recommendify::Base.new
115
+ sm.should_receive(:process_item!).exactly(4).times
116
+ sm.process!
117
+ end
118
+
119
+ it "should call process_item for all input_matrix.all_items's (uniquely)" do
120
+ Recommendify::Base.input_matrix(:anotherinput, :similarity_func => :test, :all_items => ["foo", "bar"])
121
+ Recommendify::Base.input_matrix(:yetanotherinput, :similarity_func => :test, :all_items => ["fnord", "bar"])
122
+ sm = Recommendify::Base.new
123
+ sm.should_receive(:process_item!).exactly(3).times
124
+ sm.process!
125
+ end
126
+
127
+ end
128
+
129
+ describe "for(item_id)" do
130
+
131
+ it "should retrieve the n-most similar neighbors" do
132
+ sm = Recommendify::Base.new
133
+ sm.similarity_matrix.should_receive(:[]).with("fnorditem").and_return({:fooitem => 0.4, :baritem => 1.5})
134
+ sm.for("fnorditem").length.should == 2
135
+ end
136
+
137
+ it "should not throw exception for non existing items" do
138
+ sm = Recommendify::Base.new
139
+ sm.for("not_existing_item").length.should == 0
140
+ end
141
+
142
+ it "should retrieve the n-most similar neighbors as Recommendify::Neighbor objects" do
143
+ sm = Recommendify::Base.new
144
+ sm.similarity_matrix.should_receive(:[]).exactly(2).times.with("fnorditem").and_return({:fooitem => 0.4, :baritem => 1.5})
145
+ sm.for("fnorditem").first.should be_a(Recommendify::Neighbor)
146
+ sm.for("fnorditem").last.should be_a(Recommendify::Neighbor)
147
+ end
148
+
149
+ it "should retrieve the n-most similar neighbors in the correct order" do
150
+ sm = Recommendify::Base.new
151
+ sm.similarity_matrix.should_receive(:[]).exactly(4).times.with("fnorditem").and_return({:fooitem => 0.4, :baritem => 1.5})
152
+ sm.for("fnorditem").first.similarity.should == 1.5
153
+ sm.for("fnorditem").first.item_id.should == "baritem"
154
+ sm.for("fnorditem").last.similarity.should == 0.4
155
+ sm.for("fnorditem").last.item_id.should == "fooitem"
156
+ end
157
+
158
+ it "should return an empty array if the item if no neighbors were found" do
159
+ sm = Recommendify::Base.new
160
+ sm.similarity_matrix.should_receive(:[]).with("fnorditem").and_return({})
161
+ sm.for("fnorditem").should == []
162
+ end
163
+
164
+ it "should not call split on nil when retrieving a non-existent item (return an empty array)" do
165
+ sm = Recommendify::Base.new
166
+ sm.for("NONEXISTENT").should == []
167
+ end
168
+
169
+ end
170
+
171
+ describe "delete_item!" do
172
+
173
+ it "should call delete_item on each input_matrix" do
174
+ Recommendify::Base.input_matrix(:myfirstinput, :similarity_func => :jaccard)
175
+ Recommendify::Base.input_matrix(:mysecondinput, :similarity_func => :jaccard)
176
+ sm = Recommendify::Base.new
177
+ sm.myfirstinput.should_receive(:delete_item).with("fnorditem")
178
+ sm.mysecondinput.should_receive(:delete_item).with("fnorditem")
179
+ sm.delete_item!("fnorditem")
180
+ end
181
+
182
+ it "should delete the item from the similarity matrix"
183
+
184
+ it "should delete all occurences of the item in other similarity sets from the similarity matrix"
185
+
186
+ end
187
+
188
+ end
@@ -0,0 +1,89 @@
1
+ share_examples_for Recommendify::CCMatrix do
2
+
3
+ it "should build a sparsematrix with the correct key" do
4
+ @matrix.ccmatrix.redis_key.should == "recommendify-test:mymatrix:ccmatrix"
5
+ end
6
+
7
+ it "should increment all item counts on set addition" do
8
+ Recommendify.redis.hset("recommendify-test:mymatrix:items", "bar", 2)
9
+ @matrix.add_set("user123", ["foo", "bar"])
10
+ Recommendify.redis.hget("recommendify-test:mymatrix:items", "bar").to_i.should == 3
11
+ Recommendify.redis.hget("recommendify-test:mymatrix:items", "foo").to_i.should == 1
12
+ end
13
+
14
+ it "should increment all item<->item pairs on set addition" do
15
+ @matrix.ccmatrix["bar", "foo"] = 2
16
+ res = @matrix.add_set("user123", ["foo", "bar", "fnord"])
17
+ res.length.should == 3
18
+ @matrix.ccmatrix["bar", "foo"].should == 3
19
+ @matrix.ccmatrix["foo", "fnord"].should == 1
20
+ end
21
+
22
+ it "should increment all item<->item paris on single item addition" do
23
+ @matrix.ccmatrix["bar", "fnord"] = 2
24
+ @matrix.add_single("user123", "fnord", ["foo", "bar"])
25
+ @matrix.ccmatrix["bar", "fnord"].should == 3
26
+ @matrix.ccmatrix["foo", "fnord"].should == 1
27
+ end
28
+
29
+ it "should increment the item count on single item addition" do
30
+ @matrix.send(:item_count_incr, "fnordfnord")
31
+ @matrix.send(:item_count_incr, "fnordfnord")
32
+ @matrix.send(:item_count_incr, "foofnord")
33
+ @matrix.add_single("user123", "fnordfnord", ["foofnord", "barfnord"])
34
+ @matrix.send(:item_count, "foofnord").should == 1
35
+ @matrix.send(:item_count, "barfnord").should == 0
36
+ @matrix.send(:item_count, "fnordfnord").should == 3
37
+ end
38
+
39
+ it "should calculate all item<->item pairs (3)" do
40
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord"])
41
+ res.length.should == 3
42
+ res.should include("bar:foo")
43
+ res.should include("fnord:foo")
44
+ res.should include("bar:fnord")
45
+ end
46
+
47
+ it "should calculate all item<->item pairs (6)" do
48
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord", "blubb"])
49
+ res.length.should == 6
50
+ res.should include("bar:foo")
51
+ res.should include("fnord:foo")
52
+ res.should include("bar:fnord")
53
+ res.should include("blubb:foo")
54
+ res.should include("blubb:fnord")
55
+ res.should include("bar:blubb")
56
+ end
57
+
58
+ it "should return all_items" do
59
+ @matrix.add_set("user42", ["fnord", "blubb"])
60
+ @matrix.add_set("user23", ["hans", "wurst"])
61
+ @matrix.all_items.length.should == 4
62
+ @matrix.all_items.should include("wurst")
63
+ @matrix.all_items.should include("fnord")
64
+ @matrix.all_items.should include("hans")
65
+ @matrix.all_items.should include("wurst")
66
+ end
67
+
68
+ it "should delete all item<->item pairs on item deletion" do
69
+ @matrix.ccmatrix["foo", "fnord"] = 2
70
+ @matrix.add_set("user123", ["foo", "bar", "fnord"])
71
+ @matrix.add_set("user456", ["fnord", "blubb"])
72
+ @matrix.ccmatrix["bar", "foo"].should == 1
73
+ @matrix.ccmatrix["foo", "fnord"].should == 3
74
+ @matrix.ccmatrix["blubb", "fnord"].should == 1
75
+ @matrix.delete_item("fnord")
76
+ @matrix.ccmatrix["bar", "foo"].should == 1
77
+ @matrix.ccmatrix["foo", "fnord"].should == 0
78
+ @matrix.ccmatrix["blubb", "fnord"].should == 0
79
+ end
80
+
81
+ it "should delete the item count on deletion" do
82
+ @matrix.add_set("user123", ["foo", "bar", "fnord"])
83
+ @matrix.add_set("user456", ["fnord", "blubb"])
84
+ @matrix.send(:item_count, "fnord").should == 2
85
+ @matrix.delete_item("fnord")
86
+ @matrix.send(:item_count, "fnord").should == 0
87
+ end
88
+
89
+ end
@@ -0,0 +1,18 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::CosineInputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::CosineInputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+ it_should_behave_like Recommendify::CCMatrix
15
+
16
+ it "should calculate the correct cosine similarity (here be dragons)"
17
+
18
+ end
@@ -0,0 +1,27 @@
1
+ share_examples_for Recommendify::InputMatrix do
2
+
3
+ it "should build the correct keys" do
4
+ @matrix.redis_key.should == "recommendify-test:mymatrix"
5
+ end
6
+
7
+ it "should respond to add_set" do
8
+ @matrix.respond_to?(:add_set).should == true
9
+ end
10
+
11
+ it "should respond to add_single" do
12
+ @matrix.respond_to?(:add_single).should == true
13
+ end
14
+
15
+ it "should respond to similarity" do
16
+ @matrix.respond_to?(:similarity).should == true
17
+ end
18
+
19
+ it "should respond to similarities_for" do
20
+ @matrix.respond_to?(:similarities_for).should == true
21
+ end
22
+
23
+ it "should respond to all_items" do
24
+ @matrix.respond_to?(:all_items).should == true
25
+ end
26
+
27
+ end
@@ -0,0 +1,29 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::InputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::InputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+
15
+ describe "object creation" do
16
+
17
+ it "should create an object with the correct class" do
18
+ obj = Recommendify::InputMatrix.create(:key => "fubar", :similarity_func => :jaccard)
19
+ obj.should be_a(Recommendify::JaccardInputMatrix)
20
+ end
21
+
22
+ it "should create an object with the correct class and pass opts" do
23
+ obj = Recommendify::InputMatrix.create(:key => "fubar", :similarity_func => :jaccard)
24
+ obj.instance_variable_get(:@opts)[:key].should == "fubar"
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,95 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::JaccardInputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::JaccardInputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+ it_should_behave_like Recommendify::CCMatrix
15
+
16
+ it "should calculate the correct jaccard index" do
17
+ @matrix.send(:calculate_jaccard,
18
+ ["foo", "bar", "fnord", "blubb"],
19
+ ["bar", "fnord", "shmoo", "snafu"]
20
+ ).should == 2.0/6.0
21
+ end
22
+
23
+ it "should calculate the correct similarity between to items" do
24
+ add_two_item_test_data!(@matrix)
25
+ # sim(fnord,blubb) = (users(fnord) & users(blub)) / (users(fnord) + users(blubb))
26
+ # => {user42 user48} / {user42 user46 user48 user50} + {user42 user44 user48}
27
+ # => {user42 user48} / {user42 user44 user46 user48 user50}
28
+ # => 2 / 5 => 0.4
29
+ @matrix.similarity("fnord", "blubb").should == 0.4
30
+ @matrix.similarity("blubb", "fnord").should == 0.4
31
+ end
32
+
33
+ it "should calculate all similarities for an item (1/3)" do
34
+ add_three_item_test_data!(@matrix)
35
+ res = @matrix.similarities_for("fnord")
36
+ res.length.should == 2
37
+ res.should include ["shmoo", 0.75]
38
+ res.should include ["blubb", 0.4]
39
+ end
40
+
41
+ it "should calculate all similarities for an item (2/3)" do
42
+ add_three_item_test_data!(@matrix)
43
+ res = @matrix.similarities_for("shmoo")
44
+ res.length.should == 2
45
+ res.should include ["blubb", 0.2]
46
+ res.should include ["fnord", 0.75]
47
+ end
48
+
49
+
50
+ it "should calculate all similarities for an item (3/3)" do
51
+ add_three_item_test_data!(@matrix)
52
+ res = @matrix.similarities_for("blubb")
53
+ res.length.should == 2
54
+ res.should include ["shmoo", 0.2]
55
+ res.should include ["fnord", 0.4]
56
+ end
57
+
58
+ it "should call run_native when the native option was passed" do
59
+ Recommendify::JaccardInputMatrix.class_eval do
60
+ def check_native; true; end
61
+ end
62
+ matrix = Recommendify::JaccardInputMatrix.new(
63
+ :redis_prefix => "recommendify-test",
64
+ :native => true,
65
+ :key => "mymatrix"
66
+ )
67
+ matrix.should_receive(:run_native).with("fnord").and_return(true)
68
+ matrix.similarities_for("fnord")
69
+ end
70
+
71
+ it "should return the correct redis url" do
72
+ @matrix.send(:redis_url).should == "127.0.0.1:6379"
73
+ end
74
+
75
+ private
76
+
77
+ def add_two_item_test_data!(matrix)
78
+ matrix.add_set("user42", ["fnord", "blubb"])
79
+ matrix.add_set("user44", ["blubb"])
80
+ matrix.add_set("user46", ["fnord"])
81
+ matrix.add_set("user48", ["fnord", "blubb"])
82
+ matrix.add_set("user50", ["fnord"])
83
+ end
84
+
85
+ def add_three_item_test_data!(matrix)
86
+ matrix.add_set("user42", ["fnord", "blubb", "shmoo"])
87
+ matrix.add_set("user44", ["blubb"])
88
+ matrix.add_set("user46", ["fnord", "shmoo"])
89
+ matrix.add_set("user48", ["fnord", "blubb"])
90
+ matrix.add_set("user50", ["fnord", "shmoo"])
91
+ end
92
+
93
+ end
94
+
95
+
@@ -0,0 +1,28 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify do
4
+
5
+ it "should store a redis connection" do
6
+ Recommendify.redis = "asd"
7
+ Recommendify.redis.should == "asd"
8
+ end
9
+
10
+ it "should raise an exception if unconfigured redis connection is accessed" do
11
+ Recommendify.redis = nil
12
+ lambda{ ecommendify.redis }.should raise_error
13
+ end
14
+
15
+ it "should capitalize a string" do
16
+ Recommendify.capitalize("fuubar").should == "Fuubar"
17
+ Recommendify.capitalize("fuUBar").should == "Fuubar"
18
+ Recommendify.capitalize("FUUBAR").should == "Fuubar"
19
+ Recommendify.capitalize("Fuubar").should == "Fuubar"
20
+ end
21
+
22
+ it "should constantize a string" do
23
+ obj = Recommendify.constantize("JaccardInputMatrix")
24
+ # should_be doesn't work here...
25
+ obj.inspect.should == "Recommendify::JaccardInputMatrix"
26
+ end
27
+
28
+ end