predictor 1.0.0 → 2.0.0.rc1

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.
@@ -3,6 +3,10 @@ class Predictor::InputMatrix
3
3
  @opts = opts
4
4
  end
5
5
 
6
+ def parent_redis_key(*append)
7
+ ([@opts.fetch(:redis_prefix)] + append).flatten.compact.join(":")
8
+ end
9
+
6
10
  def redis_key(*append)
7
11
  ([@opts.fetch(:redis_prefix), @opts.fetch(:key)] + append).flatten.compact.join(":")
8
12
  end
@@ -11,30 +15,19 @@ class Predictor::InputMatrix
11
15
  (@opts[:weight] || 1).to_f
12
16
  end
13
17
 
14
- def add_set(set_id, item_ids)
15
- Predictor.redis.multi do
16
- item_ids.each { |item| add_single_nomulti(set_id, item) }
17
- end
18
- end
19
-
20
- def add_set!(set_id, item_ids)
21
- add_set(set_id, item_ids)
22
- item_ids.each { |item_id| process_item!(item_id) }
23
- end
24
-
25
- def add_single(set_id, item_id)
18
+ def add_to_set(set, *items)
19
+ items = items.flatten if items.count == 1 && items[0].is_a?(Array)
26
20
  Predictor.redis.multi do
27
- add_single_nomulti(set_id, item_id)
21
+ items.each { |item| add_single_nomulti(set, item) }
28
22
  end
29
23
  end
30
24
 
31
- def add_single!(set_id, item_id)
32
- add_single(set_id, item_id)
33
- process_item!(item_id)
25
+ def add_set(set, items)
26
+ add_to_set(set, *items)
34
27
  end
35
28
 
36
- def all_items
37
- Predictor.redis.smembers(redis_key(:all_items))
29
+ def add_single(set, item)
30
+ add_to_set(set, item)
38
31
  end
39
32
 
40
33
  def items_for(set)
@@ -45,81 +38,26 @@ class Predictor::InputMatrix
45
38
  Predictor.redis.sunion redis_key(:sets, item)
46
39
  end
47
40
 
48
- def related_items(item_id)
49
- sets = Predictor.redis.smembers(redis_key(:sets, item_id))
41
+ def related_items(item)
42
+ sets = Predictor.redis.smembers(redis_key(:sets, item))
50
43
  keys = sets.map { |set| redis_key(:items, set) }
51
- if keys.length > 0
52
- Predictor.redis.sunion(keys) - [item_id]
53
- else
54
- []
55
- end
56
- end
57
-
58
- def similarity(item1, item2)
59
- Predictor.redis.zscore(redis_key(:similarities, item1), item2)
60
- end
61
-
62
- # calculate all similarities to other items in the matrix for item1
63
- def similarities_for(item1, with_scores: false, offset: 0, limit: -1)
64
- Predictor.redis.zrevrange(redis_key(:similarities, item1), offset, limit == -1 ? limit : offset + (limit - 1), with_scores: with_scores)
65
- end
66
-
67
- def process_item!(item)
68
- cache_similarities_for(item)
69
- end
70
-
71
- def process!
72
- all_items.each do |item|
73
- process_item!(item)
74
- end
44
+ keys.length > 0 ? Predictor.redis.sunion(keys) - [item] : []
75
45
  end
76
46
 
77
- # delete item_id from the matrix
78
- def delete_item!(item_id)
79
- Predictor.redis.srem(redis_key(:all_items), item_id)
80
- Predictor.redis.watch(redis_key(:sets, item_id), redis_key(:similarities, item_id)) do
81
- sets = Predictor.redis.smembers(redis_key(:sets, item_id))
82
- items = Predictor.redis.zrange(redis_key(:similarities, item_id), 0, -1)
47
+ # delete item from the matrix
48
+ def delete_item(item)
49
+ Predictor.redis.watch(redis_key(:sets, item)) do
50
+ sets = Predictor.redis.smembers(redis_key(:sets, item))
83
51
  Predictor.redis.multi do |multi|
84
52
  sets.each do |set|
85
- multi.srem(redis_key(:items, set), item_id)
53
+ multi.srem(redis_key(:items, set), item)
86
54
  end
87
55
 
88
- items.each do |item|
89
- multi.zrem(redis_key(:similarities, item), item_id)
90
- end
91
-
92
- multi.del redis_key(:sets, item_id), redis_key(:similarities, item_id)
56
+ multi.del redis_key(:sets, item)
93
57
  end
94
58
  end
95
59
  end
96
60
 
97
- private
98
-
99
- def add_single_nomulti(set_id, item_id)
100
- Predictor.redis.sadd(redis_key(:all_items), item_id)
101
- Predictor.redis.sadd(redis_key(:items, set_id), item_id)
102
- # add the set_id to the item_id's set--inverting the sets
103
- Predictor.redis.sadd(redis_key(:sets, item_id), set_id)
104
- end
105
-
106
- def cache_similarity(item1, item2)
107
- score = calculate_jaccard(item1, item2)
108
-
109
- if score > 0
110
- Predictor.redis.multi do |multi|
111
- multi.zadd(redis_key(:similarities, item1), score, item2)
112
- multi.zadd(redis_key(:similarities, item2), score, item1)
113
- end
114
- end
115
- end
116
-
117
- def cache_similarities_for(item)
118
- related_items(item).each do |related_item|
119
- cache_similarity(item, related_item)
120
- end
121
- end
122
-
123
61
  def calculate_jaccard(item1, item2)
124
62
  x = nil
125
63
  y = nil
@@ -135,4 +73,13 @@ class Predictor::InputMatrix
135
73
  return 0.0
136
74
  end
137
75
  end
76
+
77
+ private
78
+
79
+ def add_single_nomulti(set, item)
80
+ Predictor.redis.sadd(parent_redis_key(:all_items), item)
81
+ Predictor.redis.sadd(redis_key(:items, set), item)
82
+ # add the set to the item's set--inverting the sets
83
+ Predictor.redis.sadd(redis_key(:sets, item), set)
84
+ end
138
85
  end
@@ -1,3 +1,3 @@
1
1
  module Predictor
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0.rc1"
3
3
  end
@@ -8,6 +8,7 @@ describe Predictor::Base do
8
8
  before(:each) do
9
9
  flush_redis!
10
10
  BaseRecommender.input_matrices = {}
11
+ BaseRecommender.limit_similarities_to(nil)
11
12
  end
12
13
 
13
14
  describe "configuration" do
@@ -16,6 +17,11 @@ describe Predictor::Base do
16
17
  BaseRecommender.input_matrices.keys.should == [:myinput]
17
18
  end
18
19
 
20
+ it "should allow a similarity limit" do
21
+ BaseRecommender.limit_similarities_to(100)
22
+ BaseRecommender.similarity_limit.should == 100
23
+ end
24
+
19
25
  it "should retrieve an input_matrix on a new instance" do
20
26
  BaseRecommender.input_matrix(:myinput)
21
27
  sm = BaseRecommender.new
@@ -37,67 +43,56 @@ describe Predictor::Base do
37
43
  end
38
44
  end
39
45
 
40
- describe "process_item!" do
41
- it "should call process_item! on each input_matrix" do
42
- BaseRecommender.input_matrix(:myfirstinput)
43
- BaseRecommender.input_matrix(:mysecondinput)
44
- sm = BaseRecommender.new
45
- sm.myfirstinput.should_receive(:process_item!).with("fnorditem").and_return([["fooitem",0.5]])
46
- sm.mysecondinput.should_receive(:process_item!).with("fnorditem").and_return([["fooitem",0.5]])
47
- sm.process_item!("fnorditem")
48
- end
49
-
50
- it "should call process_item! on each input_matrix and add all outputs to the similarity matrix" do
51
- BaseRecommender.input_matrix(:myfirstinput)
52
- BaseRecommender.input_matrix(:mysecondinput)
46
+ describe "all_items" do
47
+ it "returns all items across all matrices" do
48
+ BaseRecommender.input_matrix(:anotherinput)
49
+ BaseRecommender.input_matrix(:yetanotherinput)
53
50
  sm = BaseRecommender.new
54
- sm.myfirstinput.should_receive(:process_item!).and_return([["fooitem",0.5]])
55
- sm.mysecondinput.should_receive(:process_item!).and_return([["fooitem",0.75], ["baritem", 1.0]])
56
- sm.process_item!("fnorditem")
51
+ sm.add_to_matrix(:anotherinput, 'a', "foo", "bar")
52
+ sm.add_to_matrix(:yetanotherinput, 'b', "fnord", "shmoo", "bar")
53
+ sm.all_items.should include('foo', 'bar', 'fnord', 'shmoo')
54
+ sm.all_items.length.should == 4
57
55
  end
56
+ end
58
57
 
59
- it "should call process_item! on each input_matrix and add all outputs to the similarity matrix with weight" do
60
- BaseRecommender.input_matrix(:myfirstinput, :weight => 4.0)
61
- BaseRecommender.input_matrix(:mysecondinput)
58
+ describe "add_to_matrix" do
59
+ it "calls add_to_set on the given matrix" do
60
+ BaseRecommender.input_matrix(:anotherinput)
62
61
  sm = BaseRecommender.new
63
- sm.myfirstinput.should_receive(:process_item!).and_return([["fooitem",0.5]])
64
- sm.mysecondinput.should_receive(:process_item!).and_return([["fooitem",0.75], ["baritem", 1.0]])
65
- sm.process_item!("fnorditem")
62
+ sm.anotherinput.should_receive(:add_to_set).with('a', 'foo', 'bar')
63
+ sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
66
64
  end
67
- end
68
65
 
69
- describe "all_items" do
70
- it "should retrieve all items from all input matrices" do
66
+ it "adds the items to the all_items storage" do
71
67
  BaseRecommender.input_matrix(:anotherinput)
72
- BaseRecommender.input_matrix(:yetanotherinput)
73
68
  sm = BaseRecommender.new
74
- sm.anotherinput.add_set('a', ["foo", "bar"])
75
- sm.yetanotherinput.add_set('b', ["fnord", "shmoo"])
76
- sm.all_items.length.should == 4
77
- sm.all_items.should include("foo", "bar", "fnord", "shmoo")
69
+ sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
70
+ sm.all_items.should include('foo', 'bar')
78
71
  end
72
+ end
79
73
 
80
- it "should retrieve all items from all input matrices (uniquely)" do
74
+ describe "add_to_matrix!" do
75
+ it "calls add_to_matrix and process_items! for the given items" do
81
76
  BaseRecommender.input_matrix(:anotherinput)
82
- BaseRecommender.input_matrix(:yetanotherinput)
83
77
  sm = BaseRecommender.new
84
- sm.anotherinput.add_set('a', ["foo", "bar"])
85
- sm.yetanotherinput.add_set('b', ["fnord", "bar"])
86
- sm.all_items.length.should == 3
87
- sm.all_items.should include("foo", "bar", "fnord")
78
+ sm.should_receive(:add_to_matrix).with(:anotherinput, 'a', 'foo')
79
+ sm.should_receive(:process_items!).with('foo')
80
+ sm.add_to_matrix!(:anotherinput, 'a', 'foo')
88
81
  end
89
82
  end
90
83
 
91
- describe "process!" do
92
- it "should call process_item for all input_matrix.all_items's" do
84
+ describe "related_items" do
85
+ it "returns items in the sets across all matrices that the given item is also in" do
93
86
  BaseRecommender.input_matrix(:anotherinput)
94
87
  BaseRecommender.input_matrix(:yetanotherinput)
88
+ BaseRecommender.input_matrix(:finalinput)
95
89
  sm = BaseRecommender.new
96
- sm.anotherinput.add_set('a', ["foo", "bar"])
97
- sm.yetanotherinput.add_set('b', ["fnord", "shmoo"])
98
- sm.anotherinput.should_receive(:process!).exactly(1).times
99
- sm.yetanotherinput.should_receive(:process!).exactly(1).times
90
+ sm.anotherinput.add_to_set('a', "foo", "bar")
91
+ sm.yetanotherinput.add_to_set('b', "fnord", "shmoo", "bar")
92
+ sm.finalinput.add_to_set('c', "nada")
100
93
  sm.process!
94
+ sm.related_items("bar").should include("foo", "fnord", "shmoo")
95
+ sm.related_items("bar").length.should == 3
101
96
  end
102
97
  end
103
98
 
@@ -106,13 +101,13 @@ describe Predictor::Base do
106
101
  BaseRecommender.input_matrix(:users, weight: 4.0)
107
102
  BaseRecommender.input_matrix(:tags, weight: 1.0)
108
103
  sm = BaseRecommender.new
109
- sm.users.add_set('me', ["foo", "bar", "fnord"])
110
- sm.users.add_set('not_me', ["foo", "shmoo"])
111
- sm.users.add_set('another', ["fnord", "other"])
112
- sm.users.add_set('another', ["nada"])
113
- sm.tags.add_set('tag1', ["foo", "fnord", "shmoo"])
114
- sm.tags.add_set('tag2', ["bar", "shmoo"])
115
- sm.tags.add_set('tag3', ["shmoo", "nada"])
104
+ sm.users.add_to_set('me', "foo", "bar", "fnord")
105
+ sm.users.add_to_set('not_me', "foo", "shmoo")
106
+ sm.users.add_to_set('another', "fnord", "other")
107
+ sm.users.add_to_set('another', "nada")
108
+ sm.tags.add_to_set('tag1', "foo", "fnord", "shmoo")
109
+ sm.tags.add_to_set('tag2', "bar", "shmoo")
110
+ sm.tags.add_to_set('tag3', "shmoo", "nada")
116
111
  sm.process!
117
112
  predictions = sm.predictions_for('me', matrix_label: :users)
118
113
  predictions.should == ["shmoo", "other", "nada"]
@@ -123,39 +118,9 @@ describe Predictor::Base do
123
118
  predictions = sm.predictions_for('me', matrix_label: :users, offset: 1)
124
119
  predictions.should == ["other", "nada"]
125
120
  end
126
-
127
- it "correctly normalizes predictions" do
128
- BaseRecommender.input_matrix(:users, weight: 1.0)
129
- BaseRecommender.input_matrix(:tags, weight: 2.0)
130
- BaseRecommender.input_matrix(:topics, weight: 4.0)
131
-
132
- sm = BaseRecommender.new
133
-
134
- sm.users.add_set('user1', ["c1", "c2", "c4"])
135
- sm.users.add_set('user2', ["c3", "c4"])
136
- sm.topics.add_set('topic1', ["c1", "c4"])
137
- sm.topics.add_set('topic2', ["c2", "c3"])
138
- sm.tags.add_set('tag1', ["c1", "c2", "c4"])
139
- sm.tags.add_set('tag2', ["c1", "c4"])
140
-
141
- sm.process!
142
-
143
- predictions = sm.predictions_for('user1', matrix_label: :users, with_scores: true, normalize: false)
144
- predictions.should eq([["c3", 4.5]])
145
- predictions = sm.predictions_for('user2', matrix_label: :users, with_scores: true, normalize: false)
146
- predictions.should eq([["c1", 6.5], ["c2", 5.5]])
147
- predictions = sm.predictions_for('user1', matrix_label: :users, with_scores: true, normalize: true)
148
- predictions[0][0].should eq("c3")
149
- predictions[0][1].should be_within(0.001).of(0.592)
150
- predictions = sm.predictions_for('user2', matrix_label: :users, with_scores: true, normalize: true)
151
- predictions[0][0].should eq("c2")
152
- predictions[0][1].should be_within(0.001).of(1.065)
153
- predictions[1][0].should eq("c1")
154
- predictions[1][1].should be_within(0.001).of(0.764)
155
- end
156
121
  end
157
122
 
158
- describe "similarities_for(item_id)" do
123
+ describe "similarities_for" do
159
124
  it "should not throw exception for non existing items" do
160
125
  sm = BaseRecommender.new
161
126
  sm.similarities_for("not_existing_item").length.should == 0
@@ -168,12 +133,12 @@ describe Predictor::Base do
168
133
 
169
134
  sm = BaseRecommender.new
170
135
 
171
- sm.users.add_set('user1', ["c1", "c2", "c4"])
172
- sm.users.add_set('user2', ["c3", "c4"])
173
- sm.topics.add_set('topic1', ["c1", "c4"])
174
- sm.topics.add_set('topic2', ["c2", "c3"])
175
- sm.tags.add_set('tag1', ["c1", "c2", "c4"])
176
- sm.tags.add_set('tag2', ["c1", "c4"])
136
+ sm.users.add_to_set('user1', "c1", "c2", "c4")
137
+ sm.users.add_to_set('user2', "c3", "c4")
138
+ sm.topics.add_to_set('topic1', "c1", "c4")
139
+ sm.topics.add_to_set('topic2', "c2", "c3")
140
+ sm.tags.add_to_set('tag1', "c1", "c2", "c4")
141
+ sm.tags.add_to_set('tag2', "c1", "c4")
177
142
 
178
143
  sm.process!
179
144
  sm.similarities_for("c1", with_scores: true).should eq([["c4", 6.5], ["c2", 2.0]])
@@ -188,24 +153,125 @@ describe Predictor::Base do
188
153
  BaseRecommender.input_matrix(:set1)
189
154
  BaseRecommender.input_matrix(:set2)
190
155
  sm = BaseRecommender.new
191
- sm.set1.add_set "item1", ["foo", "bar"]
192
- sm.set1.add_set "item2", ["nada", "bar"]
193
- sm.set2.add_set "item3", ["bar", "other"]
156
+ sm.set1.add_to_set "item1", "foo", "bar"
157
+ sm.set1.add_to_set "item2", "nada", "bar"
158
+ sm.set2.add_to_set "item3", "bar", "other"
194
159
  sm.sets_for("bar").length.should == 3
195
160
  sm.sets_for("bar").should include("item1", "item2", "item3")
196
161
  sm.sets_for("other").should == ["item3"]
197
162
  end
198
163
  end
199
164
 
165
+ describe "process_items!" do
166
+ context "with no similarity_limit" do
167
+ it "calculates the similarity between the item and all related_items (other items in a set the given item is in)" do
168
+ BaseRecommender.input_matrix(:myfirstinput)
169
+ BaseRecommender.input_matrix(:mysecondinput)
170
+ BaseRecommender.input_matrix(:mythirdinput, weight: 3.0)
171
+ sm = BaseRecommender.new
172
+ sm.myfirstinput.add_to_set 'set1', 'item1', 'item2'
173
+ sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
174
+ sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
175
+ sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
176
+ sm.similarities_for('item2').should be_empty
177
+ sm.process_items!('item2')
178
+ similarities = sm.similarities_for('item2', with_scores: true)
179
+ similarities.should include(["item3", 4.0], ["item1", 2.5])
180
+ end
181
+ end
182
+
183
+ context "with a similarity_limit" do
184
+ it "calculates the similarity between the item and all related_items (other items in a set the given item is in), but obeys the similarity_limit" do
185
+ BaseRecommender.input_matrix(:myfirstinput)
186
+ BaseRecommender.input_matrix(:mysecondinput)
187
+ BaseRecommender.input_matrix(:mythirdinput, weight: 3.0)
188
+ BaseRecommender.limit_similarities_to(1)
189
+ sm = BaseRecommender.new
190
+ sm.myfirstinput.add_to_set 'set1', 'item1', 'item2'
191
+ sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
192
+ sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
193
+ sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
194
+ sm.similarities_for('item2').should be_empty
195
+ sm.process_items!('item2')
196
+ similarities = sm.similarities_for('item2', with_scores: true)
197
+ similarities.should include(["item3", 4.0])
198
+ similarities.length.should == 1
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "process!" do
204
+ it "should call process_items for all_items's" do
205
+ BaseRecommender.input_matrix(:anotherinput)
206
+ BaseRecommender.input_matrix(:yetanotherinput)
207
+ sm = BaseRecommender.new
208
+ sm.anotherinput.add_to_set('a', "foo", "bar")
209
+ sm.yetanotherinput.add_to_set('b', "fnord", "shmoo")
210
+ sm.all_items.should include("foo", "bar", "fnord", "shmoo")
211
+ sm.should_receive(:process_items!).with(*sm.all_items)
212
+ sm.process!
213
+ end
214
+ end
215
+
216
+ describe "delete_from_matrix!" do
217
+ it "calls delete_item on the matrix" do
218
+ BaseRecommender.input_matrix(:anotherinput)
219
+ BaseRecommender.input_matrix(:yetanotherinput)
220
+ sm = BaseRecommender.new
221
+ sm.anotherinput.add_to_set('a', "foo", "bar")
222
+ sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
223
+ sm.process!
224
+ sm.similarities_for('bar').should include('foo', 'shmoo')
225
+ sm.anotherinput.should_receive(:delete_item).with('foo')
226
+ sm.delete_from_matrix!(:anotherinput, 'foo')
227
+ end
228
+
229
+ it "updates similarities" do
230
+ BaseRecommender.input_matrix(:anotherinput)
231
+ BaseRecommender.input_matrix(:yetanotherinput)
232
+ sm = BaseRecommender.new
233
+ sm.anotherinput.add_to_set('a', "foo", "bar")
234
+ sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
235
+ sm.process!
236
+ sm.similarities_for('bar').should include('foo', 'shmoo')
237
+ sm.delete_from_matrix!(:anotherinput, 'foo')
238
+ sm.similarities_for('bar').should == ['shmoo']
239
+ end
240
+ end
241
+
200
242
  describe "delete_item!" do
201
243
  it "should call delete_item on each input_matrix" do
202
244
  BaseRecommender.input_matrix(:myfirstinput)
203
245
  BaseRecommender.input_matrix(:mysecondinput)
204
246
  sm = BaseRecommender.new
205
- sm.myfirstinput.should_receive(:delete_item!).with("fnorditem")
206
- sm.mysecondinput.should_receive(:delete_item!).with("fnorditem")
247
+ sm.myfirstinput.should_receive(:delete_item).with("fnorditem")
248
+ sm.mysecondinput.should_receive(:delete_item).with("fnorditem")
207
249
  sm.delete_item!("fnorditem")
208
250
  end
251
+
252
+ it "should remove the item from all_items" do
253
+ BaseRecommender.input_matrix(:anotherinput)
254
+ sm = BaseRecommender.new
255
+ sm.anotherinput.add_to_set('a', "foo", "bar")
256
+ sm.process!
257
+ sm.all_items.should include('foo')
258
+ sm.delete_item!('foo')
259
+ sm.all_items.should_not include('foo')
260
+ end
261
+
262
+ it "should remove the item's similarities and also remove the item from related_items' similarities" do
263
+ BaseRecommender.input_matrix(:anotherinput)
264
+ BaseRecommender.input_matrix(:yetanotherinput)
265
+ sm = BaseRecommender.new
266
+ sm.anotherinput.add_to_set('a', "foo", "bar")
267
+ sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
268
+ sm.process!
269
+ sm.similarities_for('bar').should include('foo', 'shmoo')
270
+ sm.similarities_for('shmoo').should include('bar')
271
+ sm.delete_item!('shmoo')
272
+ sm.similarities_for('bar').should_not include('shmoo')
273
+ sm.similarities_for('shmoo').should be_empty
274
+ end
209
275
  end
210
276
 
211
277
  describe "clean!" do
@@ -213,9 +279,9 @@ describe Predictor::Base do
213
279
  BaseRecommender.input_matrix(:set1)
214
280
  BaseRecommender.input_matrix(:set2)
215
281
  sm = BaseRecommender.new
216
- sm.set1.add_set "item1", ["foo", "bar"]
217
- sm.set1.add_set "item2", ["nada", "bar"]
218
- sm.set2.add_set "item3", ["bar", "other"]
282
+ sm.set1.add_to_set "item1", "foo", "bar"
283
+ sm.set1.add_to_set "item2", "nada", "bar"
284
+ sm.set2.add_to_set "item3", "bar", "other"
219
285
  Predictor.redis.keys("#{sm.redis_prefix}:*").should_not be_empty
220
286
  sm.clean!
221
287
  Predictor.redis.keys("#{sm.redis_prefix}:*").should be_empty