predictor 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce936c35fc81b3f16b757d6e98a90e0a61c83c36
4
- data.tar.gz: 839d3b0b314273ef857fd88a67f3a6e0b8d2c45f
3
+ metadata.gz: f06d8361ac24ffaedb43dc650bba9af6ad62374a
4
+ data.tar.gz: c2815b5b8a507026bae58773ac32a1d7188debcb
5
5
  SHA512:
6
- metadata.gz: 14e5aacf724794effdaf7e6f092e6b19be17b30885aefe2f71d1414607bf243006142346678aac70fecef0b794cc0e14cb80c71f385242b090f2e2f5328d3ec2
7
- data.tar.gz: 7f8477ed02993b787b52e74805952f75eb9f45cab316aa4d191e64ffc89dce1f5a66f6c1aa68dea3457c4d5ce2fb85e311b32ea3d53193494138f052e2911f8d
6
+ metadata.gz: 2988190b65071a5d155974db67bc9815614720f57d9e5131ac429c0fcae5d7210527a07548aca73066a63fee542ee6edb7fb9209304041374df04304e72650ff
7
+ data.tar.gz: aa8215990ac119de3ca275c9ab666cbe246ffa53f28f5c428361734d7a3a79186bffc007d83056d3b1511beead9ab9ec0aa1f941a97fc8d41378b48b61775b53
data/Changelog.md CHANGED
@@ -2,6 +2,18 @@
2
2
  Predictor Changelog
3
3
  =========
4
4
 
5
+ 2.2.0 (Unreleased)
6
+ ---------------------
7
+ * The namespace used for keys in Redis is now configurable on a global or per-class basis. See the readme for more information. If you were overriding the redis_prefix instance method before, it is recommended that you use the new redis_prefix class method instead.
8
+ * Data stored in Redis is now namespaced by the class name of the recommender it is stored by. This change ensures that different recommenders with input matrices of the same name don't overwrite each others' data. After upgrading you'll need to either reindex your data in Redis or configure Predictor to use the naming system you were using before. If you were using the defaults before and you're not worried about matrix name collisions, you can mimic the old behavior with:
9
+ ```ruby
10
+ class MyRecommender
11
+ include Predictor::Base
12
+ redis_prefix [nil]
13
+ end
14
+ ```
15
+ * The #predictions_for method on recommenders now accepts a :boost option to give more weight to items with particular attributes. See the readme for more information.
16
+
5
17
  2.1.0 (2014-06-19)
6
18
  ---------------------
7
19
  * The similarity limit now defaults to 128, instead of being unlimited. This is intended to save space in Redis. See the Readme for more information. It is strongly recommended that you run `ensure_similarity_limit_is_obeyed!` to shrink existing similarity sets.
data/README.md CHANGED
@@ -172,6 +172,53 @@ You can also use `limit_similarities_to(nil)` to remove the limit entirely. This
172
172
 
173
173
  If at some point you decide to lower your similarity limits, you'll want to be sure to shrink the size of the sorted sets already in Redis. You can do this with `CourseRecommender.new.ensure_similarity_limit_is_obeyed!`.
174
174
 
175
+ Boost
176
+ ---------------------
177
+ What if you want to recommend courses to users based not only on what courses they've taken, but on other attributes of courses that they may be interested in? You can do that by passing the :boost argument to predictions_for:
178
+
179
+ ```ruby
180
+ class CourseRecommender
181
+ include Predictor::Base
182
+
183
+ # Courses are compared to one another by the users taking them and their tags.
184
+ input_matrix :users, weight: 3.0
185
+ input_matrix :tags, weight: 2.0
186
+ input_matrix :topics, weight: 2.0
187
+ end
188
+
189
+ recommender = CourseRecommender.new
190
+
191
+ # We want to find recommendations for Billy, who's told us that he's
192
+ # especially interested in free, interactive courses on Photoshop. So, we give
193
+ # a boost to courses that are tagged as free and interactive and have
194
+ # Photoshop as a topic:
195
+ recommender.predictions_for("Billy", matrix_label: :users, boost: {tags: ['free', 'interactive'], topics: ["Photoshop"]})
196
+
197
+ # We can also modify how much these tags and topics matter by specifying a
198
+ # weight. The default is 1.0, but if that's too much we can just tweak it:
199
+ recommender.predictions_for("Billy", matrix_label: :users, boost: {tags: {values: ['free', 'interactive'], weight: 0.4}, topics: {values: ["Photoshop"], weight: 0.3}})
200
+ ```
201
+
202
+ Key Prefixes
203
+ ---------------------
204
+ As of 2.2.0, there is much more control available over the format of the keys Predictor will use in Redis. By default, the CourseRecommender given as an example above will use keys like "predictor:CourseRecommender:users:items:user1". You can configure the global namespace like so:
205
+
206
+ ```ruby
207
+ Predictor.redis_prefix 'my_namespace' # => "my_namespace:CourseRecommender:users:items:user1"
208
+ # Or, for a multitenanted setup:
209
+ Predictor.redis_prefix { "user-#{User.current.id}" } # => "user-7:CourseRecommender:users:items:user1"
210
+ ```
211
+
212
+ You can also configure the namespace used by each class you create:
213
+
214
+ ```ruby
215
+ class CourseRecommender
216
+ include Predictor::Base
217
+ redis_prefix "courses" # => "predictor:courses:users:items:user1"
218
+ redis_prefix { "courses_for_user-#{User.current.id}" } # => "predictor:courses_for_user-7:users:items:user1"
219
+ end
220
+ ```
221
+
175
222
  Upgrading from 1.0 to 2.0
176
223
  ---------------------
177
224
  As mentioned, 2.0.0 is quite a bit different than 1.0.0, so simply upgrading with no changes likely won't work. My apologies for this. I promise this won't happen in future releases, as I'm much more confident in this Predictor release than the last. Anywho, upgrading really shouldn't be that much of a pain if you follow these steps:
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -30,17 +30,33 @@ module Predictor::Base
30
30
  def input_matrices
31
31
  @matrices
32
32
  end
33
+
34
+ def redis_prefix(prefix = nil, &block)
35
+ @redis_prefix = block_given? ? block : prefix
36
+ end
37
+
38
+ def get_redis_prefix
39
+ if @redis_prefix
40
+ if @redis_prefix.respond_to?(:call)
41
+ @redis_prefix.call
42
+ else
43
+ @redis_prefix
44
+ end
45
+ else
46
+ to_s
47
+ end
48
+ end
33
49
  end
34
50
 
35
51
  def input_matrices
36
52
  @input_matrices ||= Hash[self.class.input_matrices.map{ |key, opts|
37
- opts.merge!(:key => key, :redis_prefix => redis_prefix)
53
+ opts.merge!(:key => key, :base => self)
38
54
  [ key, Predictor::InputMatrix.new(opts) ]
39
55
  }]
40
56
  end
41
57
 
42
58
  def redis_prefix
43
- "predictor"
59
+ [Predictor.get_redis_prefix, self.class.get_redis_prefix]
44
60
  end
45
61
 
46
62
  def similarity_limit
@@ -88,7 +104,7 @@ module Predictor::Base
88
104
  keys.empty? ? [] : (Predictor.redis.sunion(keys) - [item.to_s])
89
105
  end
90
106
 
91
- def predictions_for(set=nil, item_set: nil, matrix_label: nil, with_scores: false, offset: 0, limit: -1, exclusion_set: [])
107
+ def predictions_for(set=nil, item_set: nil, matrix_label: nil, with_scores: false, offset: 0, limit: -1, exclusion_set: [], boost: {})
92
108
  fail "item_set or matrix_label and set is required" unless item_set || (matrix_label && set)
93
109
 
94
110
  if matrix_label
@@ -96,16 +112,48 @@ module Predictor::Base
96
112
  item_set = Predictor.redis.smembers(matrix.redis_key(:items, set))
97
113
  end
98
114
 
99
- item_keys = item_set.map { |item| redis_key(:similarities, item) }
115
+ item_keys = []
116
+ weights = []
117
+
118
+ item_set.each do |item|
119
+ item_keys << redis_key(:similarities, item)
120
+ weights << 1.0
121
+ end
122
+
123
+ boost.each do |matrix_label, values|
124
+ m = input_matrices[matrix_label]
125
+
126
+ # Passing plain sets to zunionstore is undocumented, but tested and supported:
127
+ # https://github.com/antirez/redis/blob/2.8.11/tests/unit/type/zset.tcl#L481-L489
128
+
129
+ case values
130
+ when Hash
131
+ values[:values].each do |value|
132
+ item_keys << m.redis_key(:items, value)
133
+ weights << values[:weight]
134
+ end
135
+ when Array
136
+ values.each do |value|
137
+ item_keys << m.redis_key(:items, value)
138
+ weights << 1.0
139
+ end
140
+ else
141
+ raise "Bad value for boost: #{boost.inspect}"
142
+ end
143
+ end
144
+
100
145
  return [] if item_keys.empty?
146
+
101
147
  predictions = nil
148
+
102
149
  Predictor.redis.multi do |multi|
103
- multi.zunionstore 'temp', item_keys
104
- multi.zrem 'temp', item_set
150
+ multi.zunionstore 'temp', item_keys, weights: weights
151
+ multi.zrem 'temp', item_set if item_set.any?
105
152
  multi.zrem 'temp', exclusion_set if exclusion_set.length > 0
106
153
  predictions = multi.zrevrange 'temp', offset, limit == -1 ? limit : offset + (limit - 1), with_scores: with_scores
107
154
  multi.del 'temp'
108
155
  end
156
+
109
157
  predictions.value
110
158
  end
111
159
 
@@ -169,7 +217,7 @@ module Predictor::Base
169
217
  end
170
218
 
171
219
  def clean!
172
- keys = Predictor.redis.keys("#{self.redis_prefix}:*")
220
+ keys = Predictor.redis.keys(redis_key('*'))
173
221
  unless keys.empty?
174
222
  Predictor.redis.del(keys)
175
223
  end
@@ -4,12 +4,16 @@ module Predictor
4
4
  @opts = opts
5
5
  end
6
6
 
7
+ def base
8
+ @opts[:base]
9
+ end
10
+
7
11
  def parent_redis_key(*append)
8
- ([@opts.fetch(:redis_prefix)] + append).flatten.compact.join(":")
12
+ base.redis_key(*append)
9
13
  end
10
14
 
11
15
  def redis_key(*append)
12
- ([@opts.fetch(:redis_prefix), @opts.fetch(:key)] + append).flatten.compact.join(":")
16
+ base.redis_key(@opts.fetch(:key), *append)
13
17
  end
14
18
 
15
19
  def weight
@@ -1,5 +1,6 @@
1
1
  module Predictor
2
2
  @@redis = nil
3
+ @@redis_prefix = nil
3
4
 
4
5
  def self.redis=(redis)
5
6
  @@redis = redis
@@ -10,6 +11,22 @@ module Predictor
10
11
  raise "redis not configured! - Predictor.redis = Redis.new"
11
12
  end
12
13
 
14
+ def self.redis_prefix(prefix = nil, &block)
15
+ @@redis_prefix = block_given? ? block : prefix
16
+ end
17
+
18
+ def self.get_redis_prefix
19
+ if @@redis_prefix
20
+ if @@redis_prefix.respond_to?(:call)
21
+ @@redis_prefix.call
22
+ else
23
+ @@redis_prefix
24
+ end
25
+ else
26
+ 'predictor'
27
+ end
28
+ end
29
+
13
30
  def self.capitalize(str_or_sym)
14
31
  str = str_or_sym.to_s.each_char.to_a
15
32
  str.first.upcase + str[1..-1].join("").downcase
@@ -18,4 +35,4 @@ module Predictor
18
35
  def self.constantize(klass)
19
36
  Object.module_eval("Predictor::#{klass}", __FILE__, __LINE__)
20
37
  end
21
- end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module Predictor
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.0"
3
3
  end
data/spec/base_spec.rb CHANGED
@@ -1,54 +1,135 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Predictor::Base do
4
- class BaseRecommender
5
- include Predictor::Base
6
- end
7
-
8
4
  before(:each) do
9
5
  flush_redis!
10
6
  BaseRecommender.input_matrices = {}
11
7
  BaseRecommender.reset_similarity_limit!
8
+ BaseRecommender.redis_prefix(nil)
9
+ UserRecommender.input_matrices = {}
10
+ UserRecommender.reset_similarity_limit!
12
11
  end
13
12
 
14
13
  describe "configuration" do
15
14
  it "should add an input_matrix by 'key'" do
16
15
  BaseRecommender.input_matrix(:myinput)
17
- BaseRecommender.input_matrices.keys.should == [:myinput]
16
+ expect(BaseRecommender.input_matrices.keys).to eq([:myinput])
18
17
  end
19
18
 
20
19
  it "should default the similarity_limit to 128" do
21
- BaseRecommender.similarity_limit.should == 128
20
+ expect(BaseRecommender.similarity_limit).to eq(128)
22
21
  end
23
22
 
24
23
  it "should allow the similarity limit to be configured" do
25
24
  BaseRecommender.limit_similarities_to(500)
26
- BaseRecommender.similarity_limit.should == 500
25
+ expect(BaseRecommender.similarity_limit).to eq(500)
27
26
  end
28
27
 
29
28
  it "should allow the similarity limit to be removed" do
30
29
  BaseRecommender.limit_similarities_to(nil)
31
- BaseRecommender.similarity_limit.should == nil
30
+ expect(BaseRecommender.similarity_limit).to eq(nil)
32
31
  end
33
32
 
34
33
  it "should retrieve an input_matrix on a new instance" do
35
34
  BaseRecommender.input_matrix(:myinput)
36
35
  sm = BaseRecommender.new
37
- lambda{ sm.myinput }.should_not raise_error
36
+ expect{ sm.myinput }.not_to raise_error
38
37
  end
39
38
 
40
39
  it "should retrieve an input_matrix on a new instance and correctly overload respond_to?" do
41
40
  BaseRecommender.input_matrix(:myinput)
42
41
  sm = BaseRecommender.new
43
- sm.respond_to?(:process!).should be_true
44
- sm.respond_to?(:myinput).should be_true
45
- sm.respond_to?(:fnord).should be_false
42
+ expect(sm.respond_to?(:process!)).to be_true
43
+ expect(sm.respond_to?(:myinput)).to be_true
44
+ expect(sm.respond_to?(:fnord)).to be_false
46
45
  end
47
46
 
48
47
  it "should retrieve an input_matrix on a new instance and intialize the correct class" do
49
48
  BaseRecommender.input_matrix(:myinput)
50
49
  sm = BaseRecommender.new
51
- sm.myinput.should be_a(Predictor::InputMatrix)
50
+ expect(sm.myinput).to be_a(Predictor::InputMatrix)
51
+ end
52
+ end
53
+
54
+ describe "redis_key" do
55
+ it "should vary based on the class name" do
56
+ expect(BaseRecommender.new.redis_key).to eq('predictor-test:BaseRecommender')
57
+ expect(UserRecommender.new.redis_key).to eq('predictor-test:UserRecommender')
58
+ end
59
+ end
60
+
61
+ describe "redis_key" do
62
+ it "should vary based on the class name" do
63
+ expect(BaseRecommender.new.redis_key).to eq('predictor-test:BaseRecommender')
64
+ expect(UserRecommender.new.redis_key).to eq('predictor-test:UserRecommender')
65
+ end
66
+
67
+ it "should be able to mimic the old naming defaults" do
68
+ BaseRecommender.redis_prefix([nil])
69
+ expect(BaseRecommender.new.redis_key(:key)).to eq('predictor-test:key')
70
+ end
71
+
72
+ it "should respect the Predictor prefix configuration setting" do
73
+ br = BaseRecommender.new
74
+
75
+ expect(br.redis_key).to eq("predictor-test:BaseRecommender")
76
+ expect(br.redis_key(:another)).to eq("predictor-test:BaseRecommender:another")
77
+ expect(br.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:another:key")
78
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:another:set:of:keys")
79
+
80
+ i = 0
81
+ Predictor.redis_prefix { i += 1 }
82
+ expect(br.redis_key).to eq("1:BaseRecommender")
83
+ expect(br.redis_key(:another)).to eq("2:BaseRecommender:another")
84
+ expect(br.redis_key(:another, :key)).to eq("3:BaseRecommender:another:key")
85
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("4:BaseRecommender:another:set:of:keys")
86
+
87
+ Predictor.redis_prefix nil
88
+ expect(br.redis_key).to eq("predictor:BaseRecommender")
89
+ expect(br.redis_key(:another)).to eq("predictor:BaseRecommender:another")
90
+ expect(br.redis_key(:another, :key)).to eq("predictor:BaseRecommender:another:key")
91
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor:BaseRecommender:another:set:of:keys")
92
+
93
+ Predictor.redis_prefix [nil]
94
+ expect(br.redis_key).to eq("BaseRecommender")
95
+ expect(br.redis_key(:another)).to eq("BaseRecommender:another")
96
+ expect(br.redis_key(:another, :key)).to eq("BaseRecommender:another:key")
97
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("BaseRecommender:another:set:of:keys")
98
+
99
+ Predictor.redis_prefix { [1, 2, 3] }
100
+ expect(br.redis_key).to eq("1:2:3:BaseRecommender")
101
+ expect(br.redis_key(:another)).to eq("1:2:3:BaseRecommender:another")
102
+ expect(br.redis_key(:another, :key)).to eq("1:2:3:BaseRecommender:another:key")
103
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("1:2:3:BaseRecommender:another:set:of:keys")
104
+
105
+ Predictor.redis_prefix 'predictor-test'
106
+ expect(br.redis_key).to eq("predictor-test:BaseRecommender")
107
+ expect(br.redis_key(:another)).to eq("predictor-test:BaseRecommender:another")
108
+ expect(br.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:another:key")
109
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:another:set:of:keys")
110
+ end
111
+
112
+ it "should respect the class prefix configuration setting" do
113
+ br = BaseRecommender.new
114
+
115
+ BaseRecommender.redis_prefix('base')
116
+ expect(br.redis_key).to eq("predictor-test:base")
117
+ expect(br.redis_key(:another)).to eq("predictor-test:base:another")
118
+ expect(br.redis_key(:another, :key)).to eq("predictor-test:base:another:key")
119
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:base:another:set:of:keys")
120
+
121
+ i = 0
122
+ BaseRecommender.redis_prefix { i += 1 }
123
+ expect(br.redis_key).to eq("predictor-test:1")
124
+ expect(br.redis_key(:another)).to eq("predictor-test:2:another")
125
+ expect(br.redis_key(:another, :key)).to eq("predictor-test:3:another:key")
126
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:4:another:set:of:keys")
127
+
128
+ BaseRecommender.redis_prefix(nil)
129
+ expect(br.redis_key).to eq("predictor-test:BaseRecommender")
130
+ expect(br.redis_key(:another)).to eq("predictor-test:BaseRecommender:another")
131
+ expect(br.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:another:key")
132
+ expect(br.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:another:set:of:keys")
52
133
  end
53
134
  end
54
135
 
@@ -59,8 +140,23 @@ describe Predictor::Base do
59
140
  sm = BaseRecommender.new
60
141
  sm.add_to_matrix(:anotherinput, 'a', "foo", "bar")
61
142
  sm.add_to_matrix(:yetanotherinput, 'b', "fnord", "shmoo", "bar")
62
- sm.all_items.should include('foo', 'bar', 'fnord', 'shmoo')
63
- sm.all_items.length.should == 4
143
+ expect(sm.all_items).to include('foo', 'bar', 'fnord', 'shmoo')
144
+ expect(sm.all_items.length).to eq(4)
145
+ end
146
+
147
+ it "doesn't return items from other recommenders" do
148
+ BaseRecommender.input_matrix(:anotherinput)
149
+ BaseRecommender.input_matrix(:yetanotherinput)
150
+ UserRecommender.input_matrix(:anotherinput)
151
+ UserRecommender.input_matrix(:yetanotherinput)
152
+ sm = BaseRecommender.new
153
+ sm.add_to_matrix(:anotherinput, 'a', "foo", "bar")
154
+ sm.add_to_matrix(:yetanotherinput, 'b', "fnord", "shmoo", "bar")
155
+ expect(sm.all_items).to include('foo', 'bar', 'fnord', 'shmoo')
156
+ expect(sm.all_items.length).to eq(4)
157
+
158
+ ur = UserRecommender.new
159
+ expect(ur.all_items).to eq([])
64
160
  end
65
161
  end
66
162
 
@@ -68,7 +164,7 @@ describe Predictor::Base do
68
164
  it "calls add_to_set on the given matrix" do
69
165
  BaseRecommender.input_matrix(:anotherinput)
70
166
  sm = BaseRecommender.new
71
- sm.anotherinput.should_receive(:add_to_set).with('a', 'foo', 'bar')
167
+ expect(sm.anotherinput).to receive(:add_to_set).with('a', 'foo', 'bar')
72
168
  sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
73
169
  end
74
170
 
@@ -76,7 +172,7 @@ describe Predictor::Base do
76
172
  BaseRecommender.input_matrix(:anotherinput)
77
173
  sm = BaseRecommender.new
78
174
  sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
79
- sm.all_items.should include('foo', 'bar')
175
+ expect(sm.all_items).to include('foo', 'bar')
80
176
  end
81
177
  end
82
178
 
@@ -84,8 +180,8 @@ describe Predictor::Base do
84
180
  it "calls add_to_matrix and process_items! for the given items" do
85
181
  BaseRecommender.input_matrix(:anotherinput)
86
182
  sm = BaseRecommender.new
87
- sm.should_receive(:add_to_matrix).with(:anotherinput, 'a', 'foo')
88
- sm.should_receive(:process_items!).with('foo')
183
+ expect(sm).to receive(:add_to_matrix).with(:anotherinput, 'a', 'foo')
184
+ expect(sm).to receive(:process_items!).with('foo')
89
185
  sm.add_to_matrix!(:anotherinput, 'a', 'foo')
90
186
  end
91
187
  end
@@ -100,8 +196,8 @@ describe Predictor::Base do
100
196
  sm.yetanotherinput.add_to_set('b', "fnord", "shmoo", "bar")
101
197
  sm.finalinput.add_to_set('c', "nada")
102
198
  sm.process!
103
- sm.related_items("bar").should include("foo", "fnord", "shmoo")
104
- sm.related_items("bar").length.should == 3
199
+ expect(sm.related_items("bar")).to include("foo", "fnord", "shmoo")
200
+ expect(sm.related_items("bar").length).to eq(3)
105
201
  end
106
202
  end
107
203
 
@@ -119,20 +215,96 @@ describe Predictor::Base do
119
215
  sm.tags.add_to_set('tag3', "shmoo", "nada")
120
216
  sm.process!
121
217
  predictions = sm.predictions_for('me', matrix_label: :users)
122
- predictions.should == ["shmoo", "other", "nada"]
218
+ expect(predictions).to eq(["shmoo", "other", "nada"])
123
219
  predictions = sm.predictions_for(item_set: ["foo", "bar", "fnord"])
124
- predictions.should == ["shmoo", "other", "nada"]
220
+ expect(predictions).to eq(["shmoo", "other", "nada"])
125
221
  predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, limit: 1)
126
- predictions.should == ["other"]
222
+ expect(predictions).to eq(["other"])
127
223
  predictions = sm.predictions_for('me', matrix_label: :users, offset: 1)
128
- predictions.should == ["other", "nada"]
224
+ expect(predictions).to eq(["other", "nada"])
225
+ end
226
+
227
+ it "accepts a :boost option" do
228
+ BaseRecommender.input_matrix(:users, weight: 4.0)
229
+ BaseRecommender.input_matrix(:tags, weight: 1.0)
230
+ sm = BaseRecommender.new
231
+ sm.users.add_to_set('me', "foo", "bar", "fnord")
232
+ sm.users.add_to_set('not_me', "foo", "shmoo")
233
+ sm.users.add_to_set('another', "fnord", "other")
234
+ sm.users.add_to_set('another', "nada")
235
+ sm.tags.add_to_set('tag1', "foo", "fnord", "shmoo")
236
+ sm.tags.add_to_set('tag2', "bar", "shmoo")
237
+ sm.tags.add_to_set('tag3', "shmoo", "nada")
238
+ sm.process!
239
+
240
+ # Syntax #1: Tags passed as array, weights assumed to be 1.0
241
+ predictions = sm.predictions_for('me', matrix_label: :users, boost: {tags: ['tag3']})
242
+ expect(predictions).to eq(["shmoo", "nada", "other"])
243
+ predictions = sm.predictions_for(item_set: ["foo", "bar", "fnord"], boost: {tags: ['tag3']})
244
+ expect(predictions).to eq(["shmoo", "nada", "other"])
245
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, limit: 1, boost: {tags: ['tag3']})
246
+ expect(predictions).to eq(["nada"])
247
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, boost: {tags: ['tag3']})
248
+ expect(predictions).to eq(["nada", "other"])
249
+
250
+ # Syntax #2: Weights explicitly set.
251
+ predictions = sm.predictions_for('me', matrix_label: :users, boost: {tags: {values: ['tag3'], weight: 1.0}})
252
+ expect(predictions).to eq(["shmoo", "nada", "other"])
253
+ predictions = sm.predictions_for(item_set: ["foo", "bar", "fnord"], boost: {tags: {values: ['tag3'], weight: 1.0}})
254
+ expect(predictions).to eq(["shmoo", "nada", "other"])
255
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, limit: 1, boost: {tags: {values: ['tag3'], weight: 1.0}})
256
+ expect(predictions).to eq(["nada"])
257
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, boost: {tags: {values: ['tag3'], weight: 1.0}})
258
+ expect(predictions).to eq(["nada", "other"])
259
+
260
+ # Make sure weights are actually being passed to Redis.
261
+ shmoo, nada, other = sm.predictions_for('me', matrix_label: :users, boost: {tags: {values: ['tag3'], weight: 10000.0}}, with_scores: true)
262
+ expect(shmoo[0]).to eq('shmoo')
263
+ expect(shmoo[1]).to be > 10000
264
+ expect(nada[0]).to eq('nada')
265
+ expect(nada[1]).to be > 10000
266
+ expect(other[0]).to eq('other')
267
+ expect(other[1]).to be < 10
268
+ end
269
+
270
+ it "accepts a :boost option, even with an empty item set" do
271
+ BaseRecommender.input_matrix(:users, weight: 4.0)
272
+ BaseRecommender.input_matrix(:tags, weight: 1.0)
273
+ sm = BaseRecommender.new
274
+ sm.users.add_to_set('not_me', "foo", "shmoo")
275
+ sm.users.add_to_set('another', "fnord", "other")
276
+ sm.users.add_to_set('another', "nada")
277
+ sm.tags.add_to_set('tag1', "foo", "fnord", "shmoo")
278
+ sm.tags.add_to_set('tag2', "bar", "shmoo")
279
+ sm.tags.add_to_set('tag3', "shmoo", "nada")
280
+ sm.process!
281
+
282
+ # Syntax #1: Tags passed as array, weights assumed to be 1.0
283
+ predictions = sm.predictions_for('me', matrix_label: :users, boost: {tags: ['tag3']})
284
+ expect(predictions).to eq(["shmoo", "nada"])
285
+ predictions = sm.predictions_for(item_set: [], boost: {tags: ['tag3']})
286
+ expect(predictions).to eq(["shmoo", "nada"])
287
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, limit: 1, boost: {tags: ['tag3']})
288
+ expect(predictions).to eq(["nada"])
289
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, boost: {tags: ['tag3']})
290
+ expect(predictions).to eq(["nada"])
291
+
292
+ # Syntax #2: Weights explicitly set.
293
+ predictions = sm.predictions_for('me', matrix_label: :users, boost: {tags: {values: ['tag3'], weight: 1.0}})
294
+ expect(predictions).to eq(["shmoo", "nada"])
295
+ predictions = sm.predictions_for(item_set: [], boost: {tags: {values: ['tag3'], weight: 1.0}})
296
+ expect(predictions).to eq(["shmoo", "nada"])
297
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, limit: 1, boost: {tags: {values: ['tag3'], weight: 1.0}})
298
+ expect(predictions).to eq(["nada"])
299
+ predictions = sm.predictions_for('me', matrix_label: :users, offset: 1, boost: {tags: {values: ['tag3'], weight: 1.0}})
300
+ expect(predictions).to eq(["nada"])
129
301
  end
130
302
  end
131
303
 
132
304
  describe "similarities_for" do
133
305
  it "should not throw exception for non existing items" do
134
306
  sm = BaseRecommender.new
135
- sm.similarities_for("not_existing_item").length.should == 0
307
+ expect(sm.similarities_for("not_existing_item").length).to eq(0)
136
308
  end
137
309
 
138
310
  it "correctly weighs and sums input matrices" do
@@ -150,10 +322,10 @@ describe Predictor::Base do
150
322
  sm.tags.add_to_set('tag2', "c1", "c4")
151
323
 
152
324
  sm.process!
153
- sm.similarities_for("c1", with_scores: true).should eq([["c4", 6.5], ["c2", 2.0]])
154
- sm.similarities_for("c2", with_scores: true).should eq([["c3", 4.0], ["c1", 2.0], ["c4", 1.5]])
155
- sm.similarities_for("c3", with_scores: true).should eq([["c2", 4.0], ["c4", 0.5]])
156
- sm.similarities_for("c4", with_scores: true, exclusion_set: ["c3"]).should eq([["c1", 6.5], ["c2", 1.5]])
325
+ expect(sm.similarities_for("c1", with_scores: true)).to eq([["c4", 6.5], ["c2", 2.0]])
326
+ expect(sm.similarities_for("c2", with_scores: true)).to eq([["c3", 4.0], ["c1", 2.0], ["c4", 1.5]])
327
+ expect(sm.similarities_for("c3", with_scores: true)).to eq([["c2", 4.0], ["c4", 0.5]])
328
+ expect(sm.similarities_for("c4", with_scores: true, exclusion_set: ["c3"])).to eq([["c1", 6.5], ["c2", 1.5]])
157
329
  end
158
330
  end
159
331
 
@@ -165,9 +337,9 @@ describe Predictor::Base do
165
337
  sm.set1.add_to_set "item1", "foo", "bar"
166
338
  sm.set1.add_to_set "item2", "nada", "bar"
167
339
  sm.set2.add_to_set "item3", "bar", "other"
168
- sm.sets_for("bar").length.should == 3
169
- sm.sets_for("bar").should include("item1", "item2", "item3")
170
- sm.sets_for("other").should == ["item3"]
340
+ expect(sm.sets_for("bar").length).to eq(3)
341
+ expect(sm.sets_for("bar")).to include("item1", "item2", "item3")
342
+ expect(sm.sets_for("other")).to eq(["item3"])
171
343
  end
172
344
  end
173
345
 
@@ -182,10 +354,10 @@ describe Predictor::Base do
182
354
  sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
183
355
  sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
184
356
  sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
185
- sm.similarities_for('item2').should be_empty
357
+ expect(sm.similarities_for('item2')).to be_empty
186
358
  sm.process_items!('item2')
187
359
  similarities = sm.similarities_for('item2', with_scores: true)
188
- similarities.should include(["item3", 4.0], ["item1", 2.5])
360
+ expect(similarities).to include(["item3", 4.0], ["item1", 2.5])
189
361
  end
190
362
  end
191
363
 
@@ -200,11 +372,11 @@ describe Predictor::Base do
200
372
  sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
201
373
  sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
202
374
  sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
203
- sm.similarities_for('item2').should be_empty
375
+ expect(sm.similarities_for('item2')).to be_empty
204
376
  sm.process_items!('item2')
205
377
  similarities = sm.similarities_for('item2', with_scores: true)
206
- similarities.should include(["item3", 4.0])
207
- similarities.length.should == 1
378
+ expect(similarities).to include(["item3", 4.0])
379
+ expect(similarities.length).to eq(1)
208
380
  end
209
381
  end
210
382
  end
@@ -216,8 +388,8 @@ describe Predictor::Base do
216
388
  sm = BaseRecommender.new
217
389
  sm.anotherinput.add_to_set('a', "foo", "bar")
218
390
  sm.yetanotherinput.add_to_set('b', "fnord", "shmoo")
219
- sm.all_items.should include("foo", "bar", "fnord", "shmoo")
220
- sm.should_receive(:process_items!).with(*sm.all_items)
391
+ expect(sm.all_items).to include("foo", "bar", "fnord", "shmoo")
392
+ expect(sm).to receive(:process_items!).with(*sm.all_items)
221
393
  sm.process!
222
394
  end
223
395
  end
@@ -230,8 +402,8 @@ describe Predictor::Base do
230
402
  sm.anotherinput.add_to_set('a', "foo", "bar")
231
403
  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
232
404
  sm.process!
233
- sm.similarities_for('bar').should include('foo', 'shmoo')
234
- sm.anotherinput.should_receive(:delete_item).with('foo')
405
+ expect(sm.similarities_for('bar')).to include('foo', 'shmoo')
406
+ expect(sm.anotherinput).to receive(:delete_item).with('foo')
235
407
  sm.delete_from_matrix!(:anotherinput, 'foo')
236
408
  end
237
409
 
@@ -242,9 +414,9 @@ describe Predictor::Base do
242
414
  sm.anotherinput.add_to_set('a', "foo", "bar")
243
415
  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
244
416
  sm.process!
245
- sm.similarities_for('bar').should include('foo', 'shmoo')
417
+ expect(sm.similarities_for('bar')).to include('foo', 'shmoo')
246
418
  sm.delete_from_matrix!(:anotherinput, 'foo')
247
- sm.similarities_for('bar').should == ['shmoo']
419
+ expect(sm.similarities_for('bar')).to eq(['shmoo'])
248
420
  end
249
421
  end
250
422
 
@@ -253,8 +425,8 @@ describe Predictor::Base do
253
425
  BaseRecommender.input_matrix(:myfirstinput)
254
426
  BaseRecommender.input_matrix(:mysecondinput)
255
427
  sm = BaseRecommender.new
256
- sm.myfirstinput.should_receive(:delete_item).with("fnorditem")
257
- sm.mysecondinput.should_receive(:delete_item).with("fnorditem")
428
+ expect(sm.myfirstinput).to receive(:delete_item).with("fnorditem")
429
+ expect(sm.mysecondinput).to receive(:delete_item).with("fnorditem")
258
430
  sm.delete_item!("fnorditem")
259
431
  end
260
432
 
@@ -263,9 +435,9 @@ describe Predictor::Base do
263
435
  sm = BaseRecommender.new
264
436
  sm.anotherinput.add_to_set('a', "foo", "bar")
265
437
  sm.process!
266
- sm.all_items.should include('foo')
438
+ expect(sm.all_items).to include('foo')
267
439
  sm.delete_item!('foo')
268
- sm.all_items.should_not include('foo')
440
+ expect(sm.all_items).not_to include('foo')
269
441
  end
270
442
 
271
443
  it "should remove the item's similarities and also remove the item from related_items' similarities" do
@@ -275,11 +447,11 @@ describe Predictor::Base do
275
447
  sm.anotherinput.add_to_set('a', "foo", "bar")
276
448
  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
277
449
  sm.process!
278
- sm.similarities_for('bar').should include('foo', 'shmoo')
279
- sm.similarities_for('shmoo').should include('bar')
450
+ expect(sm.similarities_for('bar')).to include('foo', 'shmoo')
451
+ expect(sm.similarities_for('shmoo')).to include('bar')
280
452
  sm.delete_item!('shmoo')
281
- sm.similarities_for('bar').should_not include('shmoo')
282
- sm.similarities_for('shmoo').should be_empty
453
+ expect(sm.similarities_for('bar')).not_to include('shmoo')
454
+ expect(sm.similarities_for('shmoo')).to be_empty
283
455
  end
284
456
  end
285
457
 
@@ -291,9 +463,10 @@ describe Predictor::Base do
291
463
  sm.set1.add_to_set "item1", "foo", "bar"
292
464
  sm.set1.add_to_set "item2", "nada", "bar"
293
465
  sm.set2.add_to_set "item3", "bar", "other"
294
- Predictor.redis.keys("#{sm.redis_prefix}:*").should_not be_empty
466
+
467
+ expect(Predictor.redis.keys(sm.redis_key('*'))).not_to be_empty
295
468
  sm.clean!
296
- Predictor.redis.keys("#{sm.redis_prefix}:*").should be_empty
469
+ expect(Predictor.redis.keys(sm.redis_key('*'))).to be_empty
297
470
  end
298
471
  end
299
472
 
@@ -304,20 +477,20 @@ describe Predictor::Base do
304
477
  BaseRecommender.input_matrix(:myfirstinput)
305
478
  sm = BaseRecommender.new
306
479
  sm.myfirstinput.add_to_set *(['set1'] + 130.times.map{|i| "item#{i}"})
307
- sm.similarities_for('item2').should be_empty
480
+ expect(sm.similarities_for('item2')).to be_empty
308
481
  sm.process_items!('item2')
309
- sm.similarities_for('item2').length.should == 129
482
+ expect(sm.similarities_for('item2').length).to eq(129)
310
483
 
311
484
  redis = Predictor.redis
312
485
  key = sm.redis_key(:similarities, 'item2')
313
- redis.zcard(key).should == 129
314
- redis.object(:encoding, key).should == 'skiplist' # Inefficient
486
+ expect(redis.zcard(key)).to eq(129)
487
+ expect(redis.object(:encoding, key)).to eq('skiplist') # Inefficient
315
488
 
316
489
  BaseRecommender.reset_similarity_limit!
317
490
  sm.ensure_similarity_limit_is_obeyed!
318
491
 
319
- redis.zcard(key).should == 128
320
- redis.object(:encoding, key).should == 'ziplist' # Efficient
492
+ expect(redis.zcard(key)).to eq(128)
493
+ expect(redis.object(:encoding, key)).to eq('ziplist') # Efficient
321
494
  end
322
495
  end
323
496
  end
@@ -6,7 +6,8 @@ describe Predictor::InputMatrix do
6
6
  before(:each) { @options = {} }
7
7
 
8
8
  before(:all) do
9
- @default_options = { redis_prefix: "predictor-test", key: "mymatrix" }
9
+ @base = BaseRecommender.new
10
+ @default_options = { base: @base, key: "mymatrix" }
10
11
  @matrix = Predictor::InputMatrix.new(@default_options)
11
12
  end
12
13
 
@@ -14,45 +15,96 @@ describe Predictor::InputMatrix do
14
15
  flush_redis!
15
16
  end
16
17
 
17
- it "should build the correct keys" do
18
- @matrix.redis_key.should == "predictor-test:mymatrix"
18
+ describe "redis_key" do
19
+ it "should respect the global namespace configuration" do
20
+ expect(@matrix.redis_key).to eq("predictor-test:BaseRecommender:mymatrix")
21
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:BaseRecommender:mymatrix:another")
22
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:mymatrix:another:key")
23
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:mymatrix:another:set:of:keys")
24
+
25
+ i = 0
26
+ Predictor.redis_prefix { i += 1 }
27
+ expect(@matrix.redis_key).to eq("1:BaseRecommender:mymatrix")
28
+ expect(@matrix.redis_key(:another)).to eq("2:BaseRecommender:mymatrix:another")
29
+ expect(@matrix.redis_key(:another, :key)).to eq("3:BaseRecommender:mymatrix:another:key")
30
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("4:BaseRecommender:mymatrix:another:set:of:keys")
31
+
32
+ Predictor.redis_prefix(nil)
33
+ expect(@matrix.redis_key).to eq("predictor:BaseRecommender:mymatrix")
34
+ expect(@matrix.redis_key(:another)).to eq("predictor:BaseRecommender:mymatrix:another")
35
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor:BaseRecommender:mymatrix:another:key")
36
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor:BaseRecommender:mymatrix:another:set:of:keys")
37
+
38
+ Predictor.redis_prefix('predictor-test')
39
+ expect(@matrix.redis_key).to eq("predictor-test:BaseRecommender:mymatrix")
40
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:BaseRecommender:mymatrix:another")
41
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:mymatrix:another:key")
42
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:mymatrix:another:set:of:keys")
43
+ end
44
+
45
+ it "should respect the class-level configuration" do
46
+ i = 0
47
+ BaseRecommender.redis_prefix { i += 1 }
48
+ expect(@matrix.redis_key).to eq("predictor-test:1:mymatrix")
49
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:2:mymatrix:another")
50
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:3:mymatrix:another:key")
51
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:4:mymatrix:another:set:of:keys")
52
+
53
+ BaseRecommender.redis_prefix([nil])
54
+ expect(@matrix.redis_key).to eq("predictor-test:mymatrix")
55
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:mymatrix:another")
56
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:mymatrix:another:key")
57
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:mymatrix:another:set:of:keys")
58
+
59
+ BaseRecommender.redis_prefix(['a', 'b'])
60
+ expect(@matrix.redis_key).to eq("predictor-test:a:b:mymatrix")
61
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:a:b:mymatrix:another")
62
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:a:b:mymatrix:another:key")
63
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:a:b:mymatrix:another:set:of:keys")
64
+
65
+ BaseRecommender.redis_prefix(nil)
66
+ expect(@matrix.redis_key).to eq("predictor-test:BaseRecommender:mymatrix")
67
+ expect(@matrix.redis_key(:another)).to eq("predictor-test:BaseRecommender:mymatrix:another")
68
+ expect(@matrix.redis_key(:another, :key)).to eq("predictor-test:BaseRecommender:mymatrix:another:key")
69
+ expect(@matrix.redis_key(:another, [:set, :of, :keys])).to eq("predictor-test:BaseRecommender:mymatrix:another:set:of:keys")
70
+ end
19
71
  end
20
72
 
21
73
  describe "weight" do
22
74
  it "returns the weight configured or a default of 1" do
23
- @matrix.weight.should == 1.0 # default weight
75
+ expect(@matrix.weight).to eq(1.0) # default weight
24
76
  matrix = Predictor::InputMatrix.new(redis_prefix: "predictor-test", key: "mymatrix", weight: 5.0)
25
- matrix.weight.should == 5.0
77
+ expect(matrix.weight).to eq(5.0)
26
78
  end
27
79
  end
28
80
 
29
81
  describe "add_to_set" do
30
82
  it "adds each member of the set to the key's 'sets' set" do
31
- @matrix.items_for("item1").should_not include("foo", "bar", "fnord", "blubb")
83
+ expect(@matrix.items_for("item1")).not_to include("foo", "bar", "fnord", "blubb")
32
84
  @matrix.add_to_set "item1", "foo", "bar", "fnord", "blubb"
33
- @matrix.items_for("item1").should include("foo", "bar", "fnord", "blubb")
85
+ expect(@matrix.items_for("item1")).to include("foo", "bar", "fnord", "blubb")
34
86
  end
35
87
 
36
88
  it "adds the key to each set member's 'items' set" do
37
- @matrix.sets_for("foo").should_not include("item1")
38
- @matrix.sets_for("bar").should_not include("item1")
39
- @matrix.sets_for("fnord").should_not include("item1")
40
- @matrix.sets_for("blubb").should_not include("item1")
89
+ expect(@matrix.sets_for("foo")).not_to include("item1")
90
+ expect(@matrix.sets_for("bar")).not_to include("item1")
91
+ expect(@matrix.sets_for("fnord")).not_to include("item1")
92
+ expect(@matrix.sets_for("blubb")).not_to include("item1")
41
93
  @matrix.add_to_set "item1", "foo", "bar", "fnord", "blubb"
42
- @matrix.sets_for("foo").should include("item1")
43
- @matrix.sets_for("bar").should include("item1")
44
- @matrix.sets_for("fnord").should include("item1")
45
- @matrix.sets_for("blubb").should include("item1")
94
+ expect(@matrix.sets_for("foo")).to include("item1")
95
+ expect(@matrix.sets_for("bar")).to include("item1")
96
+ expect(@matrix.sets_for("fnord")).to include("item1")
97
+ expect(@matrix.sets_for("blubb")).to include("item1")
46
98
  end
47
99
  end
48
100
 
49
101
  describe "items_for" do
50
102
  it "returns the items in the given set ID" do
51
103
  @matrix.add_to_set "item1", ["foo", "bar", "fnord", "blubb"]
52
- @matrix.items_for("item1").should include("foo", "bar", "fnord", "blubb")
104
+ expect(@matrix.items_for("item1")).to include("foo", "bar", "fnord", "blubb")
53
105
  @matrix.add_to_set "item2", ["foo", "bar", "snafu", "nada"]
54
- @matrix.items_for("item2").should include("foo", "bar", "snafu", "nada")
55
- @matrix.items_for("item1").should_not include("snafu", "nada")
106
+ expect(@matrix.items_for("item2")).to include("foo", "bar", "snafu", "nada")
107
+ expect(@matrix.items_for("item1")).not_to include("snafu", "nada")
56
108
  end
57
109
  end
58
110
 
@@ -60,8 +112,8 @@ describe Predictor::InputMatrix do
60
112
  it "returns the set IDs the given item is in" do
61
113
  @matrix.add_to_set "item1", ["foo", "bar", "fnord", "blubb"]
62
114
  @matrix.add_to_set "item2", ["foo", "bar", "snafu", "nada"]
63
- @matrix.sets_for("foo").should include("item1", "item2")
64
- @matrix.sets_for("snafu").should == ["item2"]
115
+ expect(@matrix.sets_for("foo")).to include("item1", "item2")
116
+ expect(@matrix.sets_for("snafu")).to eq(["item2"])
65
117
  end
66
118
  end
67
119
 
@@ -70,11 +122,11 @@ describe Predictor::InputMatrix do
70
122
  @matrix.add_to_set "item1", ["foo", "bar", "fnord", "blubb"]
71
123
  @matrix.add_to_set "item2", ["foo", "bar", "snafu", "nada"]
72
124
  @matrix.add_to_set "item3", ["nada", "other"]
73
- @matrix.related_items("bar").should include("foo", "fnord", "blubb", "snafu", "nada")
74
- @matrix.related_items("bar").length.should == 5
75
- @matrix.related_items("other").should == ["nada"]
76
- @matrix.related_items("snafu").should include("foo", "bar", "nada")
77
- @matrix.related_items("snafu").length.should == 3
125
+ expect(@matrix.related_items("bar")).to include("foo", "fnord", "blubb", "snafu", "nada")
126
+ expect(@matrix.related_items("bar").length).to eq(5)
127
+ expect(@matrix.related_items("other")).to eq(["nada"])
128
+ expect(@matrix.related_items("snafu")).to include("foo", "bar", "nada")
129
+ expect(@matrix.related_items("snafu").length).to eq(3)
78
130
  end
79
131
  end
80
132
 
@@ -86,13 +138,13 @@ describe Predictor::InputMatrix do
86
138
  end
87
139
 
88
140
  it "should delete the item from sets it is in" do
89
- @matrix.items_for("item1").should include("bar")
90
- @matrix.items_for("item2").should include("bar")
91
- @matrix.sets_for("bar").should include("item1", "item2")
141
+ expect(@matrix.items_for("item1")).to include("bar")
142
+ expect(@matrix.items_for("item2")).to include("bar")
143
+ expect(@matrix.sets_for("bar")).to include("item1", "item2")
92
144
  @matrix.delete_item("bar")
93
- @matrix.items_for("item1").should_not include("bar")
94
- @matrix.items_for("item2").should_not include("bar")
95
- @matrix.sets_for("bar").should be_empty
145
+ expect(@matrix.items_for("item1")).not_to include("bar")
146
+ expect(@matrix.items_for("item2")).not_to include("bar")
147
+ expect(@matrix.sets_for("bar")).to be_empty
96
148
  end
97
149
  end
98
150
 
@@ -105,7 +157,7 @@ describe Predictor::InputMatrix do
105
157
  matrix.add_to_set "item2", "bar", "fnord", "shmoo", "snafu"
106
158
  matrix.add_to_set "item3", "bar", "nada", "snafu"
107
159
 
108
- matrix.score("bar", "snafu").should == 2.0/3.0
160
+ expect(matrix.score("bar", "snafu")).to eq(2.0/3.0)
109
161
  end
110
162
 
111
163
  it "scores as jaccard index when given option" do
@@ -114,13 +166,13 @@ describe Predictor::InputMatrix do
114
166
  matrix.add_to_set "item2", "bar", "fnord", "shmoo", "snafu"
115
167
  matrix.add_to_set "item3", "bar", "nada", "snafu"
116
168
 
117
- matrix.score("bar", "snafu").should == 2.0/3.0
169
+ expect(matrix.score("bar", "snafu")).to eq(2.0/3.0)
118
170
  end
119
171
 
120
172
  it "should handle missing sets" do
121
173
  matrix.add_to_set "item1", "foo", "bar", "fnord", "blubb"
122
174
 
123
- matrix.score("is", "missing").should == 0.0
175
+ expect(matrix.score("is", "missing")).to eq(0.0)
124
176
  end
125
177
  end
126
178
 
@@ -132,13 +184,13 @@ describe Predictor::InputMatrix do
132
184
  matrix.add_to_set "item2", "fnord", "shmoo", "snafu"
133
185
  matrix.add_to_set "item3", "bar", "nada", "snafu"
134
186
 
135
- matrix.score("bar", "snafu").should == 2.0/4.0
187
+ expect(matrix.score("bar", "snafu")).to eq(2.0/4.0)
136
188
  end
137
189
 
138
190
  it "should handle missing sets" do
139
191
  matrix.add_to_set "item1", "foo", "bar", "fnord", "blubb"
140
192
 
141
- matrix.score("is", "missing").should == 0.0
193
+ expect(matrix.score("is", "missing")).to eq(0.0)
142
194
  end
143
195
  end
144
196
  end
@@ -4,12 +4,12 @@ describe Predictor do
4
4
 
5
5
  it "should store a redis connection" do
6
6
  Predictor.redis = "asd"
7
- Predictor.redis.should == "asd"
7
+ expect(Predictor.redis).to eq("asd")
8
8
  end
9
9
 
10
10
  it "should raise an exception if unconfigured redis connection is accessed" do
11
11
  Predictor.redis = nil
12
- lambda{ Predictor.redis }.should raise_error(/not configured/i)
12
+ expect{ Predictor.redis }.to raise_error(/not configured/i)
13
13
  end
14
14
 
15
15
  end
data/spec/spec_helper.rb CHANGED
@@ -8,12 +8,14 @@ def flush_redis!
8
8
  end
9
9
  end
10
10
 
11
- module Predictor::Base
11
+ Predictor.redis_prefix "predictor-test"
12
12
 
13
- def redis_prefix
14
- "predictor-test"
15
- end
13
+ class BaseRecommender
14
+ include Predictor::Base
15
+ end
16
16
 
17
+ class UserRecommender
18
+ include Predictor::Base
17
19
  end
18
20
 
19
21
  class TestRecommender
@@ -23,7 +25,6 @@ class TestRecommender
23
25
  end
24
26
 
25
27
  class Predictor::TestInputMatrix
26
-
27
28
  def initialize(opts)
28
29
  @opts = opts
29
30
  end
@@ -31,5 +32,4 @@ class Predictor::TestInputMatrix
31
32
  def method_missing(method, *args)
32
33
  @opts[method]
33
34
  end
34
-
35
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: predictor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pathgather
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-19 00:00:00.000000000 Z
11
+ date: 2014-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis