redisrank 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +297 -0
  8. data/Rakefile +69 -0
  9. data/lib/redisrank.rb +106 -0
  10. data/lib/redisrank/buffer.rb +110 -0
  11. data/lib/redisrank/collection.rb +20 -0
  12. data/lib/redisrank/connection.rb +89 -0
  13. data/lib/redisrank/core_ext.rb +5 -0
  14. data/lib/redisrank/core_ext/bignum.rb +8 -0
  15. data/lib/redisrank/core_ext/date.rb +8 -0
  16. data/lib/redisrank/core_ext/fixnum.rb +8 -0
  17. data/lib/redisrank/core_ext/hash.rb +20 -0
  18. data/lib/redisrank/core_ext/time.rb +3 -0
  19. data/lib/redisrank/date.rb +88 -0
  20. data/lib/redisrank/event.rb +98 -0
  21. data/lib/redisrank/finder.rb +245 -0
  22. data/lib/redisrank/finder/date_set.rb +99 -0
  23. data/lib/redisrank/key.rb +84 -0
  24. data/lib/redisrank/label.rb +69 -0
  25. data/lib/redisrank/mixins/database.rb +11 -0
  26. data/lib/redisrank/mixins/date_helper.rb +8 -0
  27. data/lib/redisrank/mixins/options.rb +41 -0
  28. data/lib/redisrank/mixins/synchronize.rb +52 -0
  29. data/lib/redisrank/model.rb +77 -0
  30. data/lib/redisrank/result.rb +18 -0
  31. data/lib/redisrank/scope.rb +18 -0
  32. data/lib/redisrank/summary.rb +90 -0
  33. data/lib/redisrank/version.rb +3 -0
  34. data/redisrank.gemspec +31 -0
  35. data/spec/Find Results +3349 -0
  36. data/spec/buffer_spec.rb +104 -0
  37. data/spec/collection_spec.rb +20 -0
  38. data/spec/connection_spec.rb +67 -0
  39. data/spec/core_ext/hash_spec.rb +26 -0
  40. data/spec/database_spec.rb +10 -0
  41. data/spec/date_spec.rb +95 -0
  42. data/spec/event_spec.rb +86 -0
  43. data/spec/finder/date_set_spec.rb +527 -0
  44. data/spec/finder_spec.rb +205 -0
  45. data/spec/key_spec.rb +129 -0
  46. data/spec/label_spec.rb +86 -0
  47. data/spec/model_helper.rb +31 -0
  48. data/spec/model_spec.rb +191 -0
  49. data/spec/options_spec.rb +36 -0
  50. data/spec/redis-test.conf +9 -0
  51. data/spec/result_spec.rb +23 -0
  52. data/spec/scope_spec.rb +27 -0
  53. data/spec/spec_helper.rb +18 -0
  54. data/spec/summary_spec.rb +177 -0
  55. data/spec/synchronize_spec.rb +125 -0
  56. data/spec/thread_safety_spec.rb +39 -0
  57. metadata +235 -0
@@ -0,0 +1,191 @@
1
+ require "spec_helper"
2
+ require "model_helper"
3
+
4
+ describe Redisrank::Model do
5
+ include Redisrank::Database
6
+
7
+ before(:each) do
8
+ @time = Time.utc(2010, 8, 28, 12, 0, 0)
9
+ ModelHelper1.redis.flushdb
10
+ ModelHelper2.redis.flushdb
11
+ ModelHelper3.redis.flushdb
12
+ ModelHelper4.redis.flushdb
13
+ end
14
+
15
+ it "should should name itself correctly" do
16
+ ModelHelper1.send(:name).should == "ModelHelper1"
17
+ ModelHelper2.send(:name).should == "ModelHelper2"
18
+ end
19
+
20
+ it "should return a Finder" do
21
+ two_hours_ago = 2.hours.ago
22
+ one_hour_ago = 1.hour.ago
23
+ finder = ModelHelper1.find('label', two_hours_ago, one_hour_ago)
24
+ finder.should be_a(Redisrank::Finder)
25
+ finder.options[:scope].to_s.should == 'ModelHelper1'
26
+ finder.options[:label].to_s.should == 'label'
27
+ finder.options[:from].should == two_hours_ago
28
+ finder.options[:till].should == one_hour_ago
29
+ end
30
+
31
+ it "should #find_event" do
32
+ Redisrank::Event.should_receive(:find).with('ModelHelper1', 1)
33
+ ModelHelper1.find_event(1)
34
+ end
35
+
36
+ it "should listen to model-defined options" do
37
+ ModelHelper2.depth.should == :day
38
+ ModelHelper2.store_event.should == true
39
+ ModelHelper2.hashed_label.should == true
40
+ ModelHelper2.scope.should be_nil
41
+ ModelHelper2.expire.should be_nil
42
+
43
+ ModelHelper1.depth.should == nil
44
+ ModelHelper1.store_event.should == nil
45
+ ModelHelper1.hashed_label.should == nil
46
+ ModelHelper1.depth(:hour)
47
+ ModelHelper1.depth.should == :hour
48
+ ModelHelper1.store_event(true)
49
+ ModelHelper1.store_event.should == true
50
+ ModelHelper1.hashed_label(true)
51
+ ModelHelper1.hashed_label.should == true
52
+ ModelHelper1.options[:depth] = nil
53
+ ModelHelper1.options[:store_event] = nil
54
+ ModelHelper1.options[:hashed_label] = nil
55
+ ModelHelper1.depth.should == nil
56
+ ModelHelper1.store_event.should == nil
57
+ ModelHelper1.hashed_label.should == nil
58
+
59
+ ModelHelper4.scope.should == "FancyHelper"
60
+ ModelHelper4.send(:name).should == "FancyHelper"
61
+ ModelHelper4.expire.should == {:hour => 24*3600}
62
+ end
63
+
64
+ it "should store and fetch stats" do
65
+ ModelHelper1.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4))
66
+ ModelHelper1.store("sheep.black", {:count => 2, :weight => 156}, @time)
67
+
68
+ stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1))
69
+ stats.rank["count"].should == 2
70
+ stats.rank["weight"].should == 156
71
+
72
+ stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
73
+ stats.rank[:count].should == 6
74
+ stats.rank[:weight].should == 461
75
+
76
+ ModelHelper1.store("sheep.white", {:count => 5, :weight => 50}, @time.hours_ago(4))
77
+ ModelHelper1.store("sheep.white", {:count => 4, :weight => 500}, @time)
78
+
79
+ stats = ModelHelper1.fetch("sheep.white", @time.hours_ago(2), @time.hours_since(1))
80
+ stats.rank[:count].should == 4
81
+ stats.rank[:weight].should == 500
82
+
83
+ stats = ModelHelper1.fetch("sheep.white", @time.hours_ago(5), @time.hours_since(1))
84
+ stats.rank[:count].should == 5
85
+ stats.rank[:weight].should == 500
86
+ end
87
+
88
+ it "should store and fetch grouping enabled stats" do
89
+ ModelHelper1.store("sheep/black", {:count => 6, :weight => 461}, @time.hours_ago(4))
90
+ ModelHelper1.store("sheep/black", {:count => 2, :weight => 156}, @time)
91
+ ModelHelper1.store("sheep/white", {:count => 5, :weight => 393}, @time.hours_ago(4))
92
+ ModelHelper1.store("sheep/white", {:count => 4, :weight => 100}, @time)
93
+
94
+ stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(2), @time.hours_since(1))
95
+ stats.rank["count"].should == 2
96
+ stats.rank["weight"].should == 156
97
+
98
+ stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(5), @time.hours_since(1))
99
+ stats.rank[:count].should == 6
100
+ stats.rank[:weight].should == 461
101
+
102
+ stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(2), @time.hours_since(1))
103
+ stats.rank[:count].should == 4
104
+ stats.rank[:weight].should == 100
105
+
106
+ stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(5), @time.hours_since(1))
107
+ stats.rank[:count].should == 5
108
+ stats.rank[:weight].should == 393
109
+
110
+ stats = ModelHelper1.fetch("sheep", @time.hours_ago(2), @time.hours_since(1))
111
+ stats.rank[:count].should == 4
112
+ stats.rank[:weight].should == 156
113
+
114
+ stats = ModelHelper1.fetch("sheep", @time.hours_ago(5), @time.hours_since(1))
115
+ stats.rank[:count].should == 6
116
+ stats.rank[:weight].should == 461
117
+ end
118
+
119
+ it "should connect to different Redis servers on a per-model basis" do
120
+ ModelHelper3.redis.client.db.should == 14
121
+
122
+ ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4), :label_indexing => false)
123
+ ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time, :label_indexing => false)
124
+
125
+ db.keys("*").should be_empty
126
+ ModelHelper1.redis.keys("*").should be_empty
127
+ db("ModelHelper3").keys("*").count.should be(5)
128
+ ModelHelper3.redis.keys("*").count.should be(5)
129
+
130
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1), :label_indexing => false)
131
+ stats.rank["count"].should == 2
132
+ stats.rank["weight"].should == 156
133
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
134
+ stats.rank[:count].should == 6
135
+ stats.rank[:weight].should == 461
136
+
137
+ ModelHelper3.connect_to(:port => 8379, :db => 13)
138
+ ModelHelper3.redis.client.db.should == 13
139
+
140
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
141
+ stats.rank.should == {}
142
+
143
+ ModelHelper3.connect_to(:port => 8379, :db => 14)
144
+ ModelHelper3.redis.client.db.should == 14
145
+
146
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
147
+ stats.rank[:count].should == 6
148
+ stats.rank[:weight].should == 461
149
+ end
150
+
151
+ describe "Write Buffer" do
152
+ before(:each) do
153
+ Redisrank.buffer_size = 20
154
+ end
155
+
156
+ after(:each) do
157
+ Redisrank.buffer_size = 0
158
+ end
159
+
160
+ it "should buffer calls in memory before committing to Redis" do
161
+ 14.times do
162
+ ModelHelper1.store("sheep.black", {:count => 1, :weight => 461}, @time.hours_ago(4))
163
+ end
164
+ ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1)).rank.should == {}
165
+
166
+ 5.times do
167
+ ModelHelper1.store("sheep.black", {:count => 1, :weight => 156}, @time)
168
+ end
169
+ ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1)).rank.should == {}
170
+
171
+ ModelHelper1.store("sheep.black", {:count => 1, :weight => 156}, @time)
172
+ stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
173
+ stats.rank["count"].should == 1
174
+ stats.rank["weight"].should == 461
175
+ end
176
+
177
+ it "should force flush buffer when #flush(true) is called" do
178
+ ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1)).rank.should == {}
179
+ 14.times do
180
+ ModelHelper1.store("sheep.black", {:count => 1, :weight => 461}, @time.hours_ago(4))
181
+ end
182
+ ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1)).rank.should == {}
183
+ Redisrank.buffer.flush(true)
184
+
185
+ stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
186
+ stats.rank["count"].should == 1
187
+ stats.rank["weight"].should == 461
188
+ end
189
+ end
190
+
191
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Options do
4
+
5
+ before(:each) do
6
+ @helper = OptionsHelper.new
7
+ @helper.parse_options(:wtf => 'dude', :foo => 'booze')
8
+ end
9
+
10
+ it "should #parse_options" do
11
+ @helper.options[:hello].should == 'world'
12
+ @helper.options[:foo].should == 'booze'
13
+ @helper.options[:wtf].should == 'dude'
14
+ @helper.raw_options.should_not have_key(:hello)
15
+ end
16
+
17
+ it "should create option_accessors" do
18
+ @helper.hello.should == 'world'
19
+ @helper.hello('woooo')
20
+ @helper.hello.should == 'woooo'
21
+ end
22
+
23
+ end
24
+
25
+ class OptionsHelper
26
+ include Redisrank::Options
27
+
28
+ option_accessor :hello
29
+
30
+ def default_options
31
+ { :hello => 'world',
32
+ :foo => 'bar' }
33
+ end
34
+
35
+
36
+ end
@@ -0,0 +1,9 @@
1
+ daemonize yes
2
+ dir ./spec/db
3
+ pidfile ./redis.pid
4
+ port 8379
5
+ bind 127.0.0.1
6
+ timeout 300
7
+ loglevel debug
8
+ logfile stdout
9
+ databases 16
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Result do
4
+
5
+ it "should should initialize properly" do
6
+ options = {:from => "from", :till => "till"}
7
+ result = Redisrank::Result.new(options)
8
+ result.from.should == "from"
9
+ result.till.should == "till"
10
+ end
11
+
12
+ it "should have merge_to_max method" do
13
+ result = Redisrank::Result.new
14
+ result[:world].should be_nil
15
+ result.merge_to_max(:world, 3)
16
+ result[:world].should == 3
17
+ result.merge_to_max(:world, 8)
18
+ result[:world].should == 8
19
+ result.merge_to_max(:world, 3)
20
+ result[:world].should == 8
21
+ end
22
+
23
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Scope do
4
+ include Redisrank::Database
5
+
6
+ before(:all) do
7
+ db.flushdb
8
+ end
9
+
10
+ before(:each) do
11
+ @name = "PageViews"
12
+ @scope = Redisrank::Scope.new(@name)
13
+ end
14
+
15
+ it "should initialize properly" do
16
+ @scope.to_s.should == @name
17
+ end
18
+
19
+ it "should increment next_id" do
20
+ scope = Redisrank::Scope.new("Visitors")
21
+ @scope.next_id.should == 1
22
+ scope.next_id.should == 1
23
+ @scope.next_id.should == 2
24
+ scope.next_id.should == 2
25
+ end
26
+
27
+ end
@@ -0,0 +1,18 @@
1
+ # add project-relative load paths
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ add_filter '/spec'
8
+ add_filter '/vendor'
9
+ end
10
+
11
+ # require stuff
12
+ require 'redisrank'
13
+ require 'rspec'
14
+ require 'rspec/autorun'
15
+
16
+ # use the test Redisrank instance
17
+ Redisrank.connect(:port => 8379, :db => 15, :thread_safe => true)
18
+ Redisrank.redis.flushdb
@@ -0,0 +1,177 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Summary do
4
+ include Redisrank::Database
5
+
6
+ before(:each) do
7
+ db.flushdb
8
+ @scope = "PageViews"
9
+ @label = "about_us"
10
+ @date = Time.now
11
+ @key = Redisrank::Key.new(@scope, @label, @date, {:depth => :day})
12
+ @stats = {"views" => 3, "visitors" => 2}
13
+ @expire = {:hour => 24*3600}
14
+ end
15
+
16
+ it "should update a single summary properly" do
17
+ Redisrank::Summary.send(:update_fields, @key, @stats, :hour)
18
+ summary = db.zrevrange(@key.to_s(:hour), 0, -1, :with_scores => true)
19
+ expect(summary.count).to be 2
20
+ views = summary.first
21
+ expect(views.first).to eq('views')
22
+ expect(views.last).to be 3.0
23
+ visitors = summary.last
24
+ expect(visitors.first).to eq('visitors')
25
+ expect(visitors.last).to be 2.0
26
+
27
+ Redisrank::Summary.send(:update_fields, @key, @stats, :hour)
28
+ summary = db.zrevrange(@key.to_s(:hour), 0, -1, :with_scores => true)
29
+ expect(summary.count).to be 2
30
+ views = summary.first
31
+ expect(views.first).to eq('views')
32
+ expect(views.last).to be 3.0
33
+ visitors = summary.last
34
+ expect(visitors.first).to eq('visitors')
35
+ expect(visitors.last).to be 2.0
36
+ end
37
+
38
+ it "should set key expiry properly" do
39
+ Redisrank::Summary.update_all(@key, @stats, :hour,{:expire => @expire})
40
+ ((24*3600)-1..(24*3600)+1).should include(db.ttl(@key.to_s(:hour)))
41
+ [:day, :month, :year].each do |depth|
42
+ db.ttl(@key.to_s(depth)).should == -1
43
+ end
44
+
45
+ db.flushdb
46
+ Redisrank::Summary.update_all(@key, @stats, :hour, {:expire => {}})
47
+ [:hour, :day, :month, :year].each do |depth|
48
+ db.ttl(@key.to_s(depth)).should == -1
49
+ end
50
+ end
51
+
52
+ it "should update all summaries properly" do
53
+ Redisrank::Summary.update_all(@key, @stats, :sec)
54
+ [:year, :month, :day, :hour, :min, :sec, :usec].each do |depth|
55
+ summary = db.zrevrange(@key.to_s(depth), 0, -1, :with_scores => true)
56
+ if depth != :usec
57
+ summary.count.should eq(2)
58
+ views = summary.first
59
+ expect(views.first).to eq('views')
60
+ expect(views.last).to be 3.0
61
+ visitors = summary.last
62
+ expect(visitors.first).to eq('visitors')
63
+ expect(visitors.last).to be 2.0
64
+ else
65
+ summary.count.should eq(0)
66
+ end
67
+ end
68
+ end
69
+
70
+ it "should update summaries even if no label is set" do
71
+ key = Redisrank::Key.new(@scope, nil, @date, {:depth => :day})
72
+ Redisrank::Summary.send(:update_fields, key, @stats, :hour)
73
+ summary = db.zrevrange(key.to_s(:hour), 0, -1, :with_scores => true)
74
+ views = summary.first
75
+ expect(views.first).to eq('views')
76
+ expect(views.last).to be 3.0
77
+ visitors = summary.last
78
+ expect(visitors.first).to eq('visitors')
79
+ expect(visitors.last).to be 2.0
80
+ end
81
+
82
+ it "should inject stats key grouping summaries" do
83
+ hash = { "count/hello" => 3, "count/world" => 7,
84
+ "death/bomb" => 4, "death/unicorn" => 3,
85
+ :"od/sugar" => 7, :"od/meth" => 8 }
86
+ res = Redisrank::Summary.send(:inject_group_summaries, hash)
87
+ res.should == { "count" => 10, "count/hello" => 3, "count/world" => 7,
88
+ "death" => 7, "death/bomb" => 4, "death/unicorn" => 3,
89
+ "od" => 15, :"od/sugar" => 7, :"od/meth" => 8 }
90
+ end
91
+
92
+ it "should properly store key group summaries" do
93
+ stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
94
+ Redisrank::Summary.update_all(@key, stats, :hour)
95
+ summary = db.zrevrange(@key.to_s(:hour), 0, -1, :with_scores => true)
96
+ summary.count.should eq(4)
97
+
98
+ views = summary.select{|s| s.first == 'views'}.first
99
+ visitors = summary.select{|s| s.first == 'visitors'}.first
100
+ eu = summary.select{|s| s.first == 'visitors/eu'}.first
101
+ us = summary.select{|s| s.first == 'visitors/us'}.first
102
+
103
+ views.last.should == 3.0
104
+ visitors.last.should == 6.0
105
+ eu.last.should == 2.0
106
+ us.last.should == 4.0
107
+ end
108
+
109
+ it "should not store key group summaries when option is disabled" do
110
+ stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
111
+ Redisrank::Summary.update_all(@key, stats, :hour, {:enable_grouping => false})
112
+ summary = db.zrevrange(@key.to_s(:hour), 0, -1, :with_scores => true)
113
+ summary.count.should eq(3)
114
+
115
+ views = summary.select{|s| s.first == 'views'}.first
116
+ eu = summary.select{|s| s.first == 'visitors/eu'}.first
117
+ us = summary.select{|s| s.first == 'visitors/us'}.first
118
+
119
+ views.last.should == 3.0
120
+ eu.last.should == 2.0
121
+ us.last.should == 4.0
122
+ end
123
+
124
+ it "should store label-based grouping enabled stats" do
125
+ stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
126
+ label = "views/about_us"
127
+ key = Redisrank::Key.new(@scope, label, @date)
128
+ Redisrank::Summary.update_all(key, stats, :hour)
129
+
130
+ key.groups[0].label.to_s.should == "views/about_us"
131
+ key.groups[1].label.to_s.should == "views"
132
+ child1 = key.groups[0]
133
+ parent = key.groups[1]
134
+
135
+ label = "views/contact"
136
+ key = Redisrank::Key.new(@scope, label, @date)
137
+ Redisrank::Summary.update_all(key, stats, :hour)
138
+
139
+ key.groups[0].label.to_s.should == "views/contact"
140
+ key.groups[1].label.to_s.should == "views"
141
+ child2 = key.groups[0]
142
+
143
+ summary = db.zrevrange(child1.to_s(:hour), 0, -1, :with_scores => true)
144
+ summary.count.should eq(4)
145
+
146
+ views = summary.select{|s| s.first == 'views'}.first
147
+ eu = summary.select{|s| s.first == 'visitors/eu'}.first
148
+ us = summary.select{|s| s.first == 'visitors/us'}.first
149
+
150
+ views.last.should == 3.0
151
+ eu.last.should == 2.0
152
+ us.last.should == 4.0
153
+
154
+ summary = db.zrevrange(child2.to_s(:hour), 0, -1, :with_scores => true)
155
+ summary.count.should eq(4)
156
+
157
+ views = summary.select{|s| s.first == 'views'}.first
158
+ eu = summary.select{|s| s.first == 'visitors/eu'}.first
159
+ us = summary.select{|s| s.first == 'visitors/us'}.first
160
+
161
+ views.last.should == 3.0
162
+ eu.last.should == 2.0
163
+ us.last.should == 4.0
164
+
165
+ summary = db.zrevrange(parent.to_s(:hour), 0, -1, :with_scores => true)
166
+ summary.count.should eq(4)
167
+
168
+ views = summary.select{|s| s.first == 'views'}.first
169
+ eu = summary.select{|s| s.first == 'visitors/eu'}.first
170
+ us = summary.select{|s| s.first == 'visitors/us'}.first
171
+
172
+ views.last.should == 3.0
173
+ eu.last.should == 2.0
174
+ us.last.should == 4.0
175
+ end
176
+
177
+ end