redisrank 0.1.0

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.
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,205 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Finder 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 = {"user_3" => 3, "user_2" => 2}
13
+ @stats2 = {"user_3" => 6, "user_2" => 2}
14
+ @two_hours_ago = 2.hours.ago
15
+ @one_hour_ago = 1.hour.ago
16
+ end
17
+
18
+ it "should initialize properly" do
19
+ options = {:scope => "PageViews", :label => "Label", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
20
+
21
+ finder = Redisrank::Finder.new
22
+ finder.send(:set_options, options)
23
+ finder.options[:scope].should be_a(Redisrank::Scope)
24
+ finder.options[:scope].to_s.should == options[:scope]
25
+ finder.options[:label].should be_a(Redisrank::Label)
26
+ finder.options[:label].to_s.should == options[:label]
27
+ finder.options.should == options.merge(:scope => finder.options[:scope], :label => finder.options[:label])
28
+
29
+ finder = Redisrank::Finder.scope("hello")
30
+ finder.options[:scope].to_s.should == "hello"
31
+ finder.scope.to_s.should == "hello"
32
+
33
+ finder = Redisrank::Finder.label("hello")
34
+ finder.options[:label].to_s.should == "hello"
35
+ finder.label.to_s.should == "hello"
36
+
37
+ finder = Redisrank::Finder.dates(@two_hours_ago, @one_hour_ago)
38
+ finder.options[:from].should == @two_hours_ago
39
+ finder.options[:till].should == @one_hour_ago
40
+
41
+ finder = Redisrank::Finder.from(@two_hours_ago)
42
+ finder.options[:from].should == @two_hours_ago
43
+ finder.from.should == @two_hours_ago
44
+
45
+ finder = Redisrank::Finder.till(@one_hour_ago)
46
+ finder.options[:till].should == @one_hour_ago
47
+ finder.till.should == @one_hour_ago
48
+
49
+ finder = Redisrank::Finder.depth(:hour)
50
+ finder.options[:depth].should == :hour
51
+ finder.depth.should == :hour
52
+
53
+ finder = Redisrank::Finder.interval(true)
54
+ expect(finder.options[:interval]).to be true
55
+ expect(finder.interval).to be true
56
+ finder = Redisrank::Finder.interval(false)
57
+ expect(finder.options[:interval]).to be false
58
+ expect(finder.interval).to be false
59
+ end
60
+
61
+ it "should fetch stats properly" do
62
+ first_stat, last_stat = create_example_stats
63
+
64
+ stats = Redisrank::Finder.find({:from => first_stat, :till => last_stat, :scope => @scope, :label => @label, :depth => :hour})
65
+ stats.from.should == first_stat
66
+ stats.till.should == last_stat
67
+ stats.depth.should == :hour
68
+
69
+ stats.rank.should == @stats2
70
+ end
71
+
72
+ it "should fetch data per unit when interval option is specified" do
73
+ first_stat, last_stat = create_example_stats
74
+
75
+ stats = Redisrank::Finder.find(:from => first_stat, :till => last_stat, :scope => @scope, :label => @label, :depth => :hour, :interval => :hour)
76
+ stats.from.should == first_stat
77
+ stats.till.should == last_stat
78
+ stats.rank.should == @stats2
79
+ stats[0].should == {}
80
+ stats[0].date.should == Time.parse("2010-05-14 12:00")
81
+ stats[1].should == @stats
82
+ stats[1].date.should == Time.parse("2010-05-14 13:00")
83
+ stats[2].should == @stats
84
+ stats[2].date.should == Time.parse("2010-05-14 14:00")
85
+ stats[3].should == @stats2
86
+ stats[3].date.should == Time.parse("2010-05-14 15:00")
87
+ stats[4].should == {}
88
+ stats[4].date.should == Time.parse("2010-05-14 16:00")
89
+ end
90
+
91
+ it "should return empty hash when attempting to fetch non-existent results" do
92
+ stats = Redisrank::Finder.find({:from => 3.hours.ago, :till => 2.hours.from_now, :scope => @scope, :label => @label, :depth => :hour})
93
+ stats.rank.should == {}
94
+ end
95
+
96
+ it "should throw error on invalid options" do
97
+ lambda { Redisrank::Finder.find(:from => 3.hours.ago) }.should raise_error(Redisrank::InvalidOptions)
98
+ end
99
+
100
+ describe "Grouping" do
101
+ before(:each) do
102
+ @options = {:scope => "PageViews", :label => "message/public", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
103
+ @finder = Redisrank::Finder.new(@options)
104
+ end
105
+
106
+ it "should return parent finder" do
107
+ @finder.instance_variable_get("@parent").should be_nil
108
+ @finder.parent.should be_a(Redisrank::Finder)
109
+ @finder.instance_variable_get("@parent").should_not be_nil
110
+ @finder.parent.options[:label].to_s.should == 'message'
111
+ @finder.label('message')
112
+ @finder.instance_variable_get("@parent").should be_nil
113
+ @finder.parent.should_not be_nil
114
+ @finder.parent.options[:label].should be_nil
115
+ @finder.parent.parent.should be_nil
116
+ end
117
+
118
+ it "should find children" do
119
+ Redisrank::Key.new("PageViews", "message/public/die").update_index
120
+ Redisrank::Key.new("PageViews", "message/public/live").update_index
121
+ Redisrank::Key.new("PageViews", "message/public/fester").update_index
122
+ members = db.smembers("#{@scope}#{Redisrank::LABEL_INDEX}message/public") # checking 'message/public'
123
+ @finder.children.first.should be_a(Redisrank::Finder)
124
+ subs = @finder.children.map { |f| f.options[:label].me }
125
+ expect(subs.count).to eq(3)
126
+ expect(subs).to include('die')
127
+ expect(subs).to include('live')
128
+ expect(subs).to include('fester')
129
+ end
130
+ end
131
+
132
+ describe "Lazy-Loading" do
133
+
134
+ before(:each) do
135
+ @first_stat, @last_stat = create_example_stats
136
+
137
+ @finder = Redisrank::Finder.new
138
+ @finder.from(@first_stat).till(@last_stat).scope(@scope).label(@label).depth(:hour)
139
+
140
+ @match = [{},
141
+ {"user_3"=>3.0, "user_2"=>2.0},
142
+ {"user_3"=>3.0, "user_2"=>2.0},
143
+ {"user_3"=>6.0, "user_2"=>2.0}, {}]
144
+ end
145
+
146
+ it "should lazy-load" do
147
+ @finder.instance_variable_get("@result").should be_nil
148
+ stats = @finder.all
149
+ @finder.instance_variable_get("@result").should_not be_nil
150
+
151
+ stats.should == @finder.find # find method directly fetches results
152
+ stats.rank.should == @finder.rank
153
+ stats.rank.should == @stats2
154
+ stats.rank.from.should == @first_stat
155
+ stats.rank.till.should == @last_stat
156
+
157
+ @finder.all.object_id.should == stats.object_id
158
+ @finder.from(@first_stat + 2.hours)
159
+ @finder.instance_variable_get("@result").should be_nil
160
+ @finder.all.object_id.should_not == stats.object_id
161
+ stats = @finder.all
162
+ stats.rank.should == @stats2
163
+ end
164
+
165
+ it "should handle #map" do
166
+ @finder.interval(:hour)
167
+ @finder.map { |r| r }.should == @match
168
+ end
169
+
170
+ it "should handle #each" do
171
+ @finder.interval(:hour)
172
+
173
+ res = []
174
+ @finder.each { |r| res << r }
175
+ res.should == @match
176
+ end
177
+
178
+ it "should handle #each_with_index" do
179
+ @finder.interval(:hour)
180
+
181
+ res = {}
182
+ match = {}
183
+ @finder.each_with_index { |r, i| res[i] = r }
184
+ @match.each_with_index { |r, i| match[i] = r }
185
+ res.should == match
186
+ end
187
+
188
+ end # "Lazy-Loading"
189
+
190
+
191
+ # helper methods
192
+
193
+ def create_example_stats
194
+ key = Redisrank::Key.new(@scope, @label, (first = Time.parse("2010-05-14 13:43")))
195
+ Redisrank::Summary.send(:update_fields, key, @stats, :hour)
196
+ key = Redisrank::Key.new(@scope, @label, Time.parse("2010-05-14 13:53"))
197
+ Redisrank::Summary.send(:update_fields, key, @stats, :hour)
198
+ key = Redisrank::Key.new(@scope, @label, Time.parse("2010-05-14 14:52"))
199
+ Redisrank::Summary.send(:update_fields, key, @stats, :hour)
200
+ key = Redisrank::Key.new(@scope, @label, (last = Time.parse("2010-05-14 15:02")))
201
+ Redisrank::Summary.send(:update_fields, key, @stats2, :hour)
202
+ [first - 1.hour, last + 1.hour]
203
+ end
204
+
205
+ end
@@ -0,0 +1,129 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Key do
4
+ include Redisrank::Database
5
+
6
+ before(:each) do
7
+ db.flushdb
8
+ @scope = "PageViews"
9
+ @label = "about_us"
10
+ @label_hash = Digest::SHA1.hexdigest(@label)
11
+ @date = Time.now
12
+ @key = Redisrank::Key.new(@scope, @label, @date, {:depth => :hour})
13
+ end
14
+
15
+ it "should initialize properly" do
16
+ @key.scope.to_s.should == @scope
17
+ @key.label.to_s.should == @label
18
+ @key.label_hash.should == @label_hash
19
+ @key.groups.map { |k| k.instance_variable_get("@label") }.should == @key.instance_variable_get("@label").groups
20
+ @key.date.should be_instance_of(Redisrank::Date)
21
+ @key.date.to_time.to_s.should == @date.to_s
22
+ end
23
+
24
+ it "should convert to string properly" do
25
+ @key.to_s.should == "#{@scope}/#{@label}:#{@key.date.to_s(:hour)}"
26
+ props = [:year, :month, :day, :hour, :min, :sec]
27
+ props.each do
28
+ @key.to_s(props.last).should == "#{@scope}/#{@label}:#{@key.date.to_s(props.last)}"
29
+ props.pop
30
+ end
31
+ key = Redisrank::Key.new(@scope, nil, @date, {:depth => :hour})
32
+ key.to_s.should == "#{@scope}:#{key.date.to_s(:hour)}"
33
+ end
34
+
35
+ it "should abide to hashed_label option" do
36
+ @key = Redisrank::Key.new(@scope, @label, @date, {:depth => :hour, :hashed_label => true})
37
+ @key.to_s.should == "#{@scope}/#{@label_hash}:#{@key.date.to_s(:hour)}"
38
+ @key = Redisrank::Key.new(@scope, @label, @date, {:depth => :hour, :hashed_label => false})
39
+ @key.to_s.should == "#{@scope}/#{@label}:#{@key.date.to_s(:hour)}"
40
+ end
41
+
42
+ it "should have default depth option" do
43
+ @key = Redisrank::Key.new(@scope, @label, @date)
44
+ @key.depth.should == :hour
45
+ end
46
+
47
+ it "should allow changing attributes" do
48
+ # scope
49
+ @key.scope.to_s.should == @scope
50
+ @scope = "VisitorCount"
51
+ @key.scope = @scope
52
+ @key.scope.to_s.should == @scope
53
+ # date
54
+ @key.date.to_time.to_s.should == @date.to_s
55
+ @date = Time.now
56
+ @key.date = @date
57
+ @key.date.to_time.to_s.should == @date.to_s
58
+ # label
59
+ @key.label.to_s.should == @label
60
+ @key.label_hash == @label_hash
61
+ @label = "contact_us"
62
+ @label_hash = Digest::SHA1.hexdigest(@label)
63
+ @key.label = @label
64
+ @key.label.to_s.should == @label
65
+ @key.label_hash == @label_hash
66
+ end
67
+
68
+ describe "Grouping" do
69
+ before(:each) do
70
+ @label = "message/public/offensive"
71
+ @key = Redisrank::Key.new(@scope, @label, @date, {:depth => :hour})
72
+ end
73
+
74
+ it "should create a group of keys from label group" do
75
+ label = 'message/public/offensive'
76
+ result = [ "message/public/offensive",
77
+ "message/public",
78
+ "message" ]
79
+
80
+ key = Redisrank::Key.new(@scope, label, @date, {:depth => :hour})
81
+
82
+ key.groups.map { |k| k.label.to_s }.should == result
83
+ end
84
+
85
+ it "should know it's parent" do
86
+ @key.parent.should be_a(Redisrank::Key)
87
+ @key.parent.label.to_s.should == 'message/public'
88
+ Redisrank::Key.new(@scope, 'hello', @date).parent.should be_nil
89
+ end
90
+
91
+ it "should update label index and return children" do
92
+ db.smembers("#{@scope}#{Redisrank::LABEL_INDEX}#{@key.label.parent}").should == []
93
+ @key.children.count.should be(0)
94
+
95
+ @key.update_index # indexing 'message/publish/offensive'
96
+ Redisrank::Key.new("PageViews", "message/public/die").update_index # indexing 'message/publish/die'
97
+ Redisrank::Key.new("PageViews", "message/public/live").update_index # indexing 'message/publish/live'
98
+
99
+ members = db.smembers("#{@scope}#{Redisrank::LABEL_INDEX}#{@key.label.parent}") # checking 'message/public'
100
+ members.count.should be(3)
101
+ members.should include('offensive')
102
+ members.should include('live')
103
+ members.should include('die')
104
+
105
+ key = @key.parent
106
+ key.children.first.should be_a(Redisrank::Key)
107
+ key.children.count.should be(3)
108
+ key.children.map { |k| k.label.me }.should == members
109
+
110
+ members = db.smembers("#{@scope}#{Redisrank::LABEL_INDEX}#{key.label.parent}") # checking 'message'
111
+ members.count.should be(1)
112
+ members.should include('public')
113
+
114
+ key = key.parent
115
+ key.children.count.should be(1)
116
+ key.children.map { |k| k.label.me }.should == members
117
+
118
+ members = db.smembers("#{@scope}#{Redisrank::LABEL_INDEX}") # checking ''
119
+ members.count.should be(1)
120
+ members.should include('message')
121
+
122
+ key.parent.should be_nil
123
+ key = Redisrank::Key.new("PageViews")
124
+ key.children.count.should be(1)
125
+ key.children.map { |k| k.label.me }.should include('message')
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,86 @@
1
+ require "spec_helper"
2
+
3
+ describe Redisrank::Label do
4
+ include Redisrank::Database
5
+
6
+ before(:each) do
7
+ db.flushdb
8
+ @name = "about_us"
9
+ @label = Redisrank::Label.new(@name)
10
+ end
11
+
12
+ it "should initialize properly and SHA1 hash the label name" do
13
+ @label.name.should == @name
14
+ @label.hash.should == Digest::SHA1.hexdigest(@name)
15
+ end
16
+
17
+ it "should store a label hash lookup key" do
18
+ label = Redisrank::Label.new(@name, {:hashed_label => true}).save
19
+ label.saved?.should be(true)
20
+ db.hget(Redisrank::KEY_LABELS, label.hash).should == @name
21
+
22
+ name = "contact_us"
23
+ label = Redisrank::Label.create(name, {:hashed_label => true})
24
+ label.saved?.should be(true)
25
+ db.hget(Redisrank::KEY_LABELS, label.hash).should == name
26
+ end
27
+
28
+ it "should join labels" do
29
+ include Redisrank
30
+ label = Redisrank::Label.join('email', 'message', 'public')
31
+ label.should be_a(Redisrank::Label)
32
+ label.to_s.should == 'email/message/public'
33
+ label = Redisrank::Label.join(Redisrank::Label.new('email'), Redisrank::Label.new('message'), Redisrank::Label.new('public'))
34
+ label.should be_a(Redisrank::Label)
35
+ label.to_s.should == 'email/message/public'
36
+ label = Redisrank::Label.join('email', '', 'message', nil, 'public')
37
+ label.should be_a(Redisrank::Label)
38
+ label.to_s.should == 'email/message/public'
39
+ end
40
+
41
+ it "should allow you to use a different group separator" do
42
+ include Redisrank
43
+ Redisrank.group_separator = '|'
44
+ label = Redisrank::Label.join('email', 'message', 'public')
45
+ label.should be_a(Redisrank::Label)
46
+ label.to_s.should == 'email|message|public'
47
+ label = Redisrank::Label.join(Redisrank::Label.new('email'), Redisrank::Label.new('message'), Redisrank::Label.new('public'))
48
+ label.should be_a(Redisrank::Label)
49
+ label.to_s.should == 'email|message|public'
50
+ label = Redisrank::Label.join('email', '', 'message', nil, 'public')
51
+ label.should be_a(Redisrank::Label)
52
+ label.to_s.should == 'email|message|public'
53
+ Redisrank.group_separator = Redisrank::GROUP_SEPARATOR
54
+ end
55
+
56
+ describe "Grouping" do
57
+ before(:each) do
58
+ @name = "message/public/offensive"
59
+ @label = Redisrank::Label.new(@name)
60
+ end
61
+
62
+ it "should know it's parent label group" do
63
+ @label.parent.to_s.should == 'message/public'
64
+ Redisrank::Label.new('hello').parent.should be_nil
65
+ end
66
+
67
+ it "should separate label names into groups" do
68
+ @label.name.should == @name
69
+ @label.groups.map { |l| l.to_s }.should == [ "message/public/offensive",
70
+ "message/public",
71
+ "message" ]
72
+
73
+ @name = "/message/public/"
74
+ @label = Redisrank::Label.new(@name)
75
+ @label.name.should == @name
76
+ @label.groups.map { |l| l.to_s }.should == [ "message/public",
77
+ "message" ]
78
+
79
+ @name = "message"
80
+ @label = Redisrank::Label.new(@name)
81
+ @label.name.should == @name
82
+ @label.groups.map { |l| l.to_s }.should == [ "message" ]
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,31 @@
1
+ require "redisrank"
2
+
3
+ class ModelHelper1
4
+ include Redisrank::Model
5
+
6
+
7
+ end
8
+
9
+ class ModelHelper2
10
+ include Redisrank::Model
11
+
12
+ depth :day
13
+ store_event true
14
+ hashed_label true
15
+
16
+ end
17
+
18
+ class ModelHelper3
19
+ include Redisrank::Model
20
+
21
+ connect_to :port => 8379, :db => 14
22
+
23
+ end
24
+
25
+ class ModelHelper4
26
+ include Redisrank::Model
27
+
28
+ scope "FancyHelper"
29
+ expire :hour => 24*3600
30
+
31
+ end