redistat 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/lib/redistat/finder.rb +123 -80
- data/lib/redistat/model.rb +30 -18
- data/lib/redistat/version.rb +1 -1
- data/spec/finder_spec.rb +64 -1
- data/spec/model_spec.rb +26 -14
- metadata +4 -4
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -79,7 +79,7 @@ When retrieving statistics for a given date range, Redistat figures out how to d
|
|
79
79
|
|
80
80
|
## License and Copyright
|
81
81
|
|
82
|
-
Copyright (c)
|
82
|
+
Copyright (c) 2011 Jim Myhrberg.
|
83
83
|
|
84
84
|
Permission is hereby granted, free of charge, to any person obtaining
|
85
85
|
a copy of this software and associated documentation files (the
|
data/lib/redistat/finder.rb
CHANGED
@@ -2,23 +2,119 @@ module Redistat
|
|
2
2
|
class Finder
|
3
3
|
include Database
|
4
4
|
|
5
|
+
class << self
|
6
|
+
def find(*args)
|
7
|
+
new.find(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def scope(scope)
|
11
|
+
new.scope(scope)
|
12
|
+
end
|
13
|
+
|
14
|
+
def label(label)
|
15
|
+
new.label(label)
|
16
|
+
end
|
17
|
+
|
18
|
+
def dates(from, till)
|
19
|
+
new.dates(from, till)
|
20
|
+
end
|
21
|
+
alias :date :dates
|
22
|
+
|
23
|
+
def from(date)
|
24
|
+
new.from(date)
|
25
|
+
end
|
26
|
+
|
27
|
+
def till(date)
|
28
|
+
new.till(date)
|
29
|
+
end
|
30
|
+
alias :untill :till
|
31
|
+
|
32
|
+
def depth(unit)
|
33
|
+
new.depth(unit)
|
34
|
+
end
|
35
|
+
|
36
|
+
def interval(unit)
|
37
|
+
new.interval(unit)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
5
41
|
attr_reader :options
|
6
42
|
|
7
43
|
def initialize(options = {})
|
8
44
|
@options = options
|
9
45
|
end
|
10
46
|
|
11
|
-
def
|
12
|
-
|
47
|
+
def all(reload = false)
|
48
|
+
@result = nil if reload
|
49
|
+
@result ||= find
|
13
50
|
end
|
14
51
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
52
|
+
def total
|
53
|
+
all.total
|
54
|
+
end
|
55
|
+
|
56
|
+
def each(&block)
|
57
|
+
all.each(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def map(&block)
|
61
|
+
all.map(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def each_with_index(&block)
|
65
|
+
all.each_with_index(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def connection_ref(ref)
|
69
|
+
reset! if @options[:connection_ref] != ref
|
70
|
+
@options[:connection_ref] = ref
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def scope(scope)
|
75
|
+
reset! if @options[:scope] != scope
|
76
|
+
@options[:scope] = scope
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def label(label)
|
81
|
+
reset! if @options[:label] != label
|
82
|
+
@options[:label] = label
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def dates(from, till)
|
87
|
+
from(from).till(till)
|
88
|
+
end
|
89
|
+
alias :date :dates
|
90
|
+
|
91
|
+
def from(date)
|
92
|
+
reset! if @options[:from] != date
|
93
|
+
@options[:from] = date
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def till(date)
|
98
|
+
reset! if @options[:till] != date
|
99
|
+
@options[:till] = date
|
100
|
+
self
|
101
|
+
end
|
102
|
+
alias :until :till
|
103
|
+
|
104
|
+
def depth(unit)
|
105
|
+
reset! if @options[:depth] != unit
|
106
|
+
@options[:depth] = unit
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def interval(unit)
|
111
|
+
reset! if @options[:interval] != unit
|
112
|
+
@options[:interval] = unit
|
113
|
+
self
|
18
114
|
end
|
19
115
|
|
20
116
|
def find(options = {})
|
21
|
-
|
117
|
+
set_options(options)
|
22
118
|
raise InvalidOptions.new if !valid_options?
|
23
119
|
if @options[:interval].nil? || !@options[:interval]
|
24
120
|
find_by_magic
|
@@ -27,8 +123,17 @@ module Redistat
|
|
27
123
|
end
|
28
124
|
end
|
29
125
|
|
126
|
+
private
|
127
|
+
|
128
|
+
def set_options(opts = {})
|
129
|
+
opts = opts.clone
|
130
|
+
opts.each do |key, value|
|
131
|
+
self.send(key, opts.delete(key)) if self.respond_to?(key)
|
132
|
+
end
|
133
|
+
@options.merge!(opts)
|
134
|
+
end
|
135
|
+
|
30
136
|
def find_by_interval(options = {})
|
31
|
-
@options.merge!(options)
|
32
137
|
raise InvalidOptions.new if !valid_options?
|
33
138
|
key = build_key
|
34
139
|
col = Collection.new(@options)
|
@@ -48,7 +153,6 @@ module Redistat
|
|
48
153
|
end
|
49
154
|
|
50
155
|
def find_by_magic(options = {})
|
51
|
-
@options.merge!(options)
|
52
156
|
raise InvalidOptions.new if !valid_options?
|
53
157
|
key = Key.new(@options[:scope], @options[:label])
|
54
158
|
col = Collection.new(@options)
|
@@ -64,6 +168,15 @@ module Redistat
|
|
64
168
|
end
|
65
169
|
col
|
66
170
|
end
|
171
|
+
|
172
|
+
def reset!
|
173
|
+
@result = nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def valid_options?
|
177
|
+
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
|
178
|
+
false
|
179
|
+
end
|
67
180
|
|
68
181
|
def build_date_sets
|
69
182
|
Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
|
@@ -91,78 +204,8 @@ module Redistat
|
|
91
204
|
sum
|
92
205
|
end
|
93
206
|
|
94
|
-
|
95
|
-
|
96
|
-
def find(*args)
|
97
|
-
new.find(*args)
|
98
|
-
end
|
99
|
-
|
100
|
-
def scope(scope)
|
101
|
-
new.scope(scope)
|
102
|
-
end
|
103
|
-
|
104
|
-
def label(label)
|
105
|
-
new.label(label)
|
106
|
-
end
|
107
|
-
|
108
|
-
def dates(from, till)
|
109
|
-
new.dates(from, till)
|
110
|
-
end
|
111
|
-
alias :date :dates
|
112
|
-
|
113
|
-
def from(date)
|
114
|
-
new.from(date)
|
115
|
-
end
|
116
|
-
|
117
|
-
def till(date)
|
118
|
-
new.till(date)
|
119
|
-
end
|
120
|
-
alias :untill :till
|
121
|
-
|
122
|
-
def depth(unit)
|
123
|
-
new.depth(unit)
|
124
|
-
end
|
125
|
-
|
126
|
-
def interval(unit)
|
127
|
-
new.interval(unit)
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
def scope(scope)
|
133
|
-
@options[:scope] = scope
|
134
|
-
self
|
135
|
-
end
|
136
|
-
|
137
|
-
def label(label)
|
138
|
-
@options[:label] = label
|
139
|
-
self
|
140
|
-
end
|
141
|
-
|
142
|
-
def dates(from, till)
|
143
|
-
from(from).till(till)
|
144
|
-
end
|
145
|
-
alias :date :dates
|
146
|
-
|
147
|
-
def from(date)
|
148
|
-
@options[:from] = date
|
149
|
-
self
|
150
|
-
end
|
151
|
-
|
152
|
-
def till(date)
|
153
|
-
@options[:till] = date
|
154
|
-
self
|
155
|
-
end
|
156
|
-
alias :until :till
|
157
|
-
|
158
|
-
def depth(unit)
|
159
|
-
@options[:depth] = unit
|
160
|
-
self
|
161
|
-
end
|
162
|
-
|
163
|
-
def interval(unit)
|
164
|
-
@options[:interval] = unit
|
165
|
-
self
|
207
|
+
def db
|
208
|
+
super(@options[:connection_ref])
|
166
209
|
end
|
167
210
|
|
168
211
|
end
|
data/lib/redistat/model.rb
CHANGED
@@ -6,31 +6,36 @@ module Redistat
|
|
6
6
|
base.extend(self)
|
7
7
|
end
|
8
8
|
|
9
|
+
#
|
10
|
+
# statistics store/fetch methods
|
11
|
+
#
|
12
|
+
|
9
13
|
def store(label, stats = {}, date = nil, meta = {}, opts = {})
|
10
14
|
Event.new(name, label, date, stats, options.merge(opts), meta).save
|
11
15
|
end
|
12
16
|
alias :event :store
|
17
|
+
|
18
|
+
def fetch(label, from, till, opts = {})
|
19
|
+
find(label, from, till, opts).all
|
20
|
+
end
|
21
|
+
alias :lookup :fetch
|
22
|
+
|
23
|
+
def find(label, from, till, opts = {})
|
24
|
+
Finder.new( { :scope => name,
|
25
|
+
:label => label,
|
26
|
+
:from => from,
|
27
|
+
:till => till }.merge(options.merge(opts)) )
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# options methods
|
32
|
+
#
|
13
33
|
|
14
34
|
def connect_to(opts = {})
|
15
35
|
Connection.create(opts.merge(:ref => name))
|
16
36
|
options[:connection_ref] = name
|
17
37
|
end
|
18
38
|
|
19
|
-
def connection
|
20
|
-
db(options[:connection_ref])
|
21
|
-
end
|
22
|
-
alias :redis :connection
|
23
|
-
|
24
|
-
def fetch(label, from, till, opts = {})
|
25
|
-
Finder.find({
|
26
|
-
:scope => name,
|
27
|
-
:label => label,
|
28
|
-
:from => from,
|
29
|
-
:till => till
|
30
|
-
}.merge(options.merge(opts)))
|
31
|
-
end
|
32
|
-
alias :lookup :fetch
|
33
|
-
|
34
39
|
def hashed_label(boolean = nil)
|
35
40
|
if !boolean.nil?
|
36
41
|
options[:hashed_label] = boolean
|
@@ -64,15 +69,22 @@ module Redistat
|
|
64
69
|
end
|
65
70
|
end
|
66
71
|
|
72
|
+
#
|
73
|
+
# resource access methods
|
74
|
+
#
|
75
|
+
|
76
|
+
def connection
|
77
|
+
db(options[:connection_ref])
|
78
|
+
end
|
79
|
+
alias :redis :connection
|
80
|
+
|
67
81
|
def options
|
68
82
|
@options ||= {}
|
69
83
|
end
|
70
84
|
|
71
|
-
private
|
72
|
-
|
73
85
|
def name
|
74
86
|
options[:class_name] || (@name ||= self.to_s)
|
75
87
|
end
|
76
|
-
|
88
|
+
|
77
89
|
end
|
78
90
|
end
|
data/lib/redistat/version.rb
CHANGED
data/spec/finder_spec.rb
CHANGED
@@ -19,6 +19,10 @@ describe Redistat::Finder do
|
|
19
19
|
|
20
20
|
finder = Redistat::Finder.new(options)
|
21
21
|
finder.options.should == options
|
22
|
+
|
23
|
+
finder = Redistat::Finder.new
|
24
|
+
finder.send(:set_options, options)
|
25
|
+
finder.options.should == options
|
22
26
|
|
23
27
|
finder = Redistat::Finder.dates(two_hours_ago, one_hour_ago).scope("PageViews").label("Label").depth(:hour).interval(:hour)
|
24
28
|
finder.options.should == options
|
@@ -85,6 +89,65 @@ describe Redistat::Finder do
|
|
85
89
|
lambda { Redistat::Finder.find(:from => 3.hours.ago) }.should raise_error(Redistat::InvalidOptions)
|
86
90
|
end
|
87
91
|
|
92
|
+
describe "Lazy-Loading" do
|
93
|
+
|
94
|
+
before(:each) do
|
95
|
+
@first_stat, @last_stat = create_example_stats
|
96
|
+
|
97
|
+
@finder = Redistat::Finder.new
|
98
|
+
@finder.from(@first_stat).till(@last_stat).scope(@scope).label(@label).depth(:hour)
|
99
|
+
|
100
|
+
@match = [{}, {"visitors"=>"4", "views"=>"6"},
|
101
|
+
{"visitors"=>"2", "views"=>"3"},
|
102
|
+
{"visitors"=>"2", "views"=>"3"}, {}]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should lazy-load" do
|
106
|
+
|
107
|
+
@finder.instance_variable_get("@result").should be_nil
|
108
|
+
stats = @finder.all
|
109
|
+
@finder.instance_variable_get("@result").should_not be_nil
|
110
|
+
|
111
|
+
stats.should == @finder.find # find method directly fetches results
|
112
|
+
stats.total.should == @finder.total
|
113
|
+
stats.total.should == { "views" => 12, "visitors" => 8 }
|
114
|
+
stats.total.from.should == @first_stat
|
115
|
+
stats.total.till.should == @last_stat
|
116
|
+
stats.first.should == stats.total
|
117
|
+
|
118
|
+
@finder.all.object_id.should == stats.object_id
|
119
|
+
@finder.from(@first_stat + 2.hours)
|
120
|
+
@finder.instance_variable_get("@result").should be_nil
|
121
|
+
@finder.all.object_id.should_not == stats.object_id
|
122
|
+
stats = @finder.all
|
123
|
+
stats.total.should == { "views" => 6, "visitors" => 4 }
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should handle #map" do
|
127
|
+
@finder.interval(:hour)
|
128
|
+
@finder.map { |r| r }.should == @match
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should handle #each" do
|
132
|
+
@finder.interval(:hour)
|
133
|
+
|
134
|
+
res = []
|
135
|
+
@finder.each { |r| res << r }
|
136
|
+
res.should == @match
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should handle #each_with_index" do
|
140
|
+
@finder.interval(:hour)
|
141
|
+
|
142
|
+
res = {}
|
143
|
+
match = {}
|
144
|
+
@finder.each_with_index { |r, i| res[i] = r }
|
145
|
+
@match.each_with_index { |r, i| match[i] = r }
|
146
|
+
res.should == match
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
88
151
|
|
89
152
|
# helper methods
|
90
153
|
|
@@ -93,7 +156,7 @@ describe Redistat::Finder do
|
|
93
156
|
Redistat::Summary.update(key, @stats, :hour)
|
94
157
|
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 13:53"))
|
95
158
|
Redistat::Summary.update(key, @stats, :hour)
|
96
|
-
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 14:
|
159
|
+
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 14:52"))
|
97
160
|
Redistat::Summary.update(key, @stats, :hour)
|
98
161
|
key = Redistat::Key.new(@scope, @label, (last = Time.parse("2010-05-14 15:02")))
|
99
162
|
Redistat::Summary.update(key, @stats, :hour)
|
data/spec/model_spec.rb
CHANGED
@@ -5,6 +5,7 @@ describe Redistat::Model do
|
|
5
5
|
include Redistat::Database
|
6
6
|
|
7
7
|
before(:each) do
|
8
|
+
@time = Time.utc(2010, 8, 28, 12, 0, 0)
|
8
9
|
ModelHelper1.redis.flushdb
|
9
10
|
ModelHelper2.redis.flushdb
|
10
11
|
ModelHelper3.redis.flushdb
|
@@ -16,6 +17,17 @@ describe Redistat::Model do
|
|
16
17
|
ModelHelper2.send(:name).should == "ModelHelper2"
|
17
18
|
end
|
18
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(Redistat::Finder)
|
25
|
+
finder.options[:scope].should == 'ModelHelper1'
|
26
|
+
finder.options[:label].should == 'label'
|
27
|
+
finder.options[:from].should == two_hours_ago
|
28
|
+
finder.options[:till].should == one_hour_ago
|
29
|
+
end
|
30
|
+
|
19
31
|
it "should listen to model-defined options" do
|
20
32
|
ModelHelper2.depth.should == :day
|
21
33
|
ModelHelper2.store_event.should == true
|
@@ -43,28 +55,28 @@ describe Redistat::Model do
|
|
43
55
|
end
|
44
56
|
|
45
57
|
it "should store and fetch stats" do
|
46
|
-
ModelHelper1.store("sheep.black", {:count => 6, :weight => 461}, 4
|
47
|
-
ModelHelper1.store("sheep.black", {:count => 2, :weight => 156})
|
58
|
+
ModelHelper1.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4))
|
59
|
+
ModelHelper1.store("sheep.black", {:count => 2, :weight => 156}, @time)
|
48
60
|
|
49
|
-
stats = ModelHelper1.fetch("sheep.black", 2
|
61
|
+
stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1))
|
50
62
|
stats.total["count"].should == 2
|
51
63
|
stats.total["weight"].should == 156
|
52
64
|
stats.first.should == stats.total
|
53
65
|
|
54
|
-
stats = ModelHelper1.fetch("sheep.black", 5
|
66
|
+
stats = ModelHelper1.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
|
55
67
|
stats.total[:count].should == 8
|
56
68
|
stats.total[:weight].should == 617
|
57
69
|
stats.first.should == stats.total
|
58
70
|
|
59
|
-
ModelHelper1.store("sheep.white", {:count => 5, :weight => 393}, 4
|
60
|
-
ModelHelper1.store("sheep.white", {:count => 4, :weight => 316})
|
71
|
+
ModelHelper1.store("sheep.white", {:count => 5, :weight => 393}, @time.hours_ago(4))
|
72
|
+
ModelHelper1.store("sheep.white", {:count => 4, :weight => 316}, @time)
|
61
73
|
|
62
|
-
stats = ModelHelper1.fetch("sheep.white", 2
|
74
|
+
stats = ModelHelper1.fetch("sheep.white", @time.hours_ago(2), @time.hours_since(1))
|
63
75
|
stats.total[:count].should == 4
|
64
76
|
stats.total[:weight].should == 316
|
65
77
|
stats.first.should == stats.total
|
66
78
|
|
67
|
-
stats = ModelHelper1.fetch("sheep.white", 5
|
79
|
+
stats = ModelHelper1.fetch("sheep.white", @time.hours_ago(5), @time.hours_since(1))
|
68
80
|
stats.total[:count].should == 9
|
69
81
|
stats.total[:weight].should == 709
|
70
82
|
stats.first.should == stats.total
|
@@ -73,31 +85,31 @@ describe Redistat::Model do
|
|
73
85
|
it "should connect to different Redis servers on a per-model basis" do
|
74
86
|
ModelHelper3.redis.client.db.should == 14
|
75
87
|
|
76
|
-
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, 4
|
77
|
-
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156})
|
88
|
+
ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4))
|
89
|
+
ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time)
|
78
90
|
|
79
91
|
db.keys("*").should be_empty
|
80
92
|
ModelHelper1.redis.keys("*").should be_empty
|
81
93
|
db("ModelHelper3").keys("*").should have(5).items
|
82
94
|
ModelHelper3.redis.keys("*").should have(5).items
|
83
95
|
|
84
|
-
stats = ModelHelper3.fetch("sheep.black", 2
|
96
|
+
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1))
|
85
97
|
stats.total["count"].should == 2
|
86
98
|
stats.total["weight"].should == 156
|
87
|
-
stats = ModelHelper3.fetch("sheep.black", 5
|
99
|
+
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
|
88
100
|
stats.total[:count].should == 8
|
89
101
|
stats.total[:weight].should == 617
|
90
102
|
|
91
103
|
ModelHelper3.connect_to(:port => 8379, :db => 13)
|
92
104
|
ModelHelper3.redis.client.db.should == 13
|
93
105
|
|
94
|
-
stats = ModelHelper3.fetch("sheep.black", 5
|
106
|
+
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
|
95
107
|
stats.total.should == {}
|
96
108
|
|
97
109
|
ModelHelper3.connect_to(:port => 8379, :db => 14)
|
98
110
|
ModelHelper3.redis.client.db.should == 14
|
99
111
|
|
100
|
-
stats = ModelHelper3.fetch("sheep.black", 5
|
112
|
+
stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
|
101
113
|
stats.total[:count].should == 8
|
102
114
|
stats.total[:weight].should == 617
|
103
115
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redistat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jim Myhrberg
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-09 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|