redistat 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -66,6 +66,11 @@ When retrieving statistics for a given date range, Redistat figures out how to d
66
66
  * Anything else that becomes apparent after real-world use.
67
67
 
68
68
 
69
+ ## Credits
70
+
71
+ [Global Personals](http://globalpersonals.co.uk/) deserves a thank you. Currently the primary user of Redistat, they've allowed me to spend some company time to further develop the project.
72
+
73
+
69
74
  ## Note on Patches/Pull Requests
70
75
 
71
76
  * Fork the project.
@@ -9,9 +9,10 @@ require 'time_ext'
9
9
  require 'json'
10
10
  require 'digest/sha1'
11
11
 
12
- require 'redistat/collection'
12
+ require 'redistat/options'
13
13
  require 'redistat/connection'
14
14
  require 'redistat/database'
15
+ require 'redistat/collection'
15
16
  require 'redistat/date'
16
17
  require 'redistat/date_helper'
17
18
  require 'redistat/event'
@@ -34,8 +35,9 @@ module Redistat
34
35
 
35
36
  KEY_NEXT_ID = ".next_id"
36
37
  KEY_EVENT = ".event:"
37
- KEY_LEBELS = "Redistat.lables:"
38
+ KEY_LEBELS = "Redistat.labels:" # used for reverse label hash lookup
38
39
  KEY_EVENT_IDS = ".event_ids"
40
+ LABEL_INDEX = ".label_index:"
39
41
  GROUP_SEPARATOR = "/"
40
42
 
41
43
  class InvalidOptions < ArgumentError; end
@@ -4,6 +4,7 @@ module Redistat
4
4
  base.extend(Database)
5
5
  end
6
6
  def db(ref = nil)
7
+ ref ||= @options[:connection_ref] if !@options.nil?
7
8
  Redistat.connection(ref)
8
9
  end
9
10
  end
@@ -1,40 +1,28 @@
1
1
  module Redistat
2
2
  class Event
3
3
  include Database
4
+ include Options
4
5
 
5
6
  attr_reader :id
6
7
  attr_reader :key
7
- attr_reader :connection_ref
8
8
 
9
9
  attr_accessor :stats
10
10
  attr_accessor :meta
11
- attr_accessor :options
12
11
 
13
- def initialize(scope, label = nil, date = nil, stats = {}, options = {}, meta = {}, is_new = true)
14
- @options = parse_options(options)
15
- @connection_ref = @options[:connection_ref]
16
- @key = Key.new(scope, label, date, @options)
17
- @stats = stats ||= {}
18
- @meta = meta ||= {}
19
- @new = is_new
20
- end
21
-
22
- def db
23
- super(@connection_ref)
24
- end
25
-
26
- def parse_options(options)
27
- default_options.each do |opt, val|
28
- options[opt] = val if options[opt].nil?
29
- end
30
- options
31
- end
32
-
33
12
  def default_options
34
13
  { :depth => :hour,
35
14
  :store_event => false,
36
15
  :connection_ref => nil,
37
- :enable_grouping => true }
16
+ :enable_grouping => true,
17
+ :label_indexing => true }
18
+ end
19
+
20
+ def initialize(scope, label = nil, date = nil, stats = {}, opts = {}, meta = {}, is_new = true)
21
+ parse_options(opts)
22
+ @key = Key.new(scope, label, date, @options)
23
+ @stats = stats ||= {}
24
+ @meta = meta ||= {}
25
+ @new = is_new
38
26
  end
39
27
 
40
28
  def new?
@@ -75,7 +63,7 @@ module Redistat
75
63
 
76
64
  def save
77
65
  return false if !self.new?
78
- Summary.update_all(@key, @stats, depth_limit, @connection_ref, @options[:enable_grouping])
66
+ Summary.update_all(@key, @stats, depth_limit, @options)
79
67
  if @options[:store_event]
80
68
  @id = self.next_id
81
69
  db.hmset("#{self.scope}#{KEY_EVENT}#{@id}",
@@ -40,8 +40,12 @@ module Redistat
40
40
 
41
41
  attr_reader :options
42
42
 
43
- def initialize(options = {})
44
- @options = options
43
+ def initialize(opts = {})
44
+ set_options(opts)
45
+ end
46
+
47
+ def options
48
+ @options ||= {}
45
49
  end
46
50
 
47
51
  def all(reload = false)
@@ -65,21 +69,27 @@ module Redistat
65
69
  all.each_with_index(&block)
66
70
  end
67
71
 
72
+ def children
73
+ build_key.children.map { |key|
74
+ self.class.new(options.merge(:label => key.label.to_s))
75
+ }
76
+ end
77
+
68
78
  def connection_ref(ref)
69
- reset! if @options[:connection_ref] != ref
70
- @options[:connection_ref] = ref
79
+ reset! if options[:connection_ref] != ref
80
+ options[:connection_ref] = ref
71
81
  self
72
82
  end
73
83
 
74
84
  def scope(scope)
75
- reset! if @options[:scope] != scope
76
- @options[:scope] = scope
85
+ reset! if !options[:scope].nil? && options[:scope].to_s != scope
86
+ options[:scope] = Scope.new(scope)
77
87
  self
78
88
  end
79
89
 
80
90
  def label(label)
81
- reset! if @options[:label] != label
82
- @options[:label] = label
91
+ reset! if !options[:label].nil? && options[:label].to_s != label
92
+ options[:label] = Label.new(label)
83
93
  self
84
94
  end
85
95
 
@@ -89,34 +99,34 @@ module Redistat
89
99
  alias :date :dates
90
100
 
91
101
  def from(date)
92
- reset! if @options[:from] != date
93
- @options[:from] = date
102
+ reset! if options[:from] != date
103
+ options[:from] = date
94
104
  self
95
105
  end
96
106
 
97
107
  def till(date)
98
- reset! if @options[:till] != date
99
- @options[:till] = date
108
+ reset! if options[:till] != date
109
+ options[:till] = date
100
110
  self
101
111
  end
102
112
  alias :until :till
103
113
 
104
114
  def depth(unit)
105
- reset! if @options[:depth] != unit
106
- @options[:depth] = unit
115
+ reset! if options[:depth] != unit
116
+ options[:depth] = unit
107
117
  self
108
118
  end
109
119
 
110
120
  def interval(unit)
111
- reset! if @options[:interval] != unit
112
- @options[:interval] = unit
121
+ reset! if options[:interval] != unit
122
+ options[:interval] = unit
113
123
  self
114
124
  end
115
125
 
116
- def find(options = {})
117
- set_options(options)
126
+ def find(opts = {})
127
+ set_options(opts)
118
128
  raise InvalidOptions.new if !valid_options?
119
- if @options[:interval].nil? || !@options[:interval]
129
+ if options[:interval].nil? || !options[:interval]
120
130
  find_by_magic
121
131
  else
122
132
  find_by_interval
@@ -130,14 +140,14 @@ module Redistat
130
140
  opts.each do |key, value|
131
141
  self.send(key, opts.delete(key)) if self.respond_to?(key)
132
142
  end
133
- @options.merge!(opts)
143
+ self.options.merge!(opts)
134
144
  end
135
145
 
136
- def find_by_interval(options = {})
146
+ def find_by_interval
137
147
  raise InvalidOptions.new if !valid_options?
138
148
  key = build_key
139
- col = Collection.new(@options)
140
- col.total = Result.new(@options)
149
+ col = Collection.new(options)
150
+ col.total = Result.new(options)
141
151
  build_date_sets.each do |set|
142
152
  set[:add].each do |date|
143
153
  result = Result.new
@@ -152,11 +162,11 @@ module Redistat
152
162
  col
153
163
  end
154
164
 
155
- def find_by_magic(options = {})
165
+ def find_by_magic
156
166
  raise InvalidOptions.new if !valid_options?
157
- key = Key.new(@options[:scope], @options[:label])
158
- col = Collection.new(@options)
159
- col.total = Result.new(@options)
167
+ key = build_key
168
+ col = Collection.new(options)
169
+ col.total = Result.new(options)
160
170
  col << col.total
161
171
  build_date_sets.each do |set|
162
172
  sum = Result.new
@@ -174,16 +184,16 @@ module Redistat
174
184
  end
175
185
 
176
186
  def valid_options?
177
- return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
187
+ return true if !options[:scope].blank? && !options[:label].blank? && !options[:from].blank? && !options[:till].blank?
178
188
  false
179
189
  end
180
190
 
181
191
  def build_date_sets
182
- Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
192
+ Finder::DateSet.new(options[:from], options[:till], options[:depth], options[:interval])
183
193
  end
184
194
 
185
195
  def build_key
186
- Key.new(@options[:scope], @options[:label])
196
+ Key.new(options[:scope], options[:label])
187
197
  end
188
198
 
189
199
  def summarize_add_keys(sets, key, sum)
@@ -205,7 +215,7 @@ module Redistat
205
215
  end
206
216
 
207
217
  def db
208
- super(@options[:connection_ref])
218
+ super(options[:connection_ref])
209
219
  end
210
220
 
211
221
  end
@@ -1,24 +1,22 @@
1
1
  module Redistat
2
2
  class Key
3
+ include Database
4
+ include Options
3
5
 
4
- attr_accessor :scope
5
- attr_accessor :date
6
- attr_accessor :options
6
+ def default_options
7
+ { :depth => :hour }
8
+ end
7
9
 
8
- def initialize(scope, label_name = nil, time_stamp = nil, options = {})
9
- @options = default_options.merge(options || {})
10
- @scope = scope
10
+ def initialize(scope, label_name = nil, time_stamp = nil, opts = {})
11
+ parse_options(opts)
12
+ self.scope = scope
11
13
  self.label = label_name if !label_name.nil?
12
14
  self.date = time_stamp ||= Time.now
13
15
  end
14
16
 
15
- def default_options
16
- { :depth => :hour, :hashed_label => false }
17
- end
18
-
19
17
  def prefix
20
18
  key = "#{@scope}"
21
- key << "/#{label}" if !label.nil?
19
+ key << "/#{label.name}" if !label.nil?
22
20
  key << ":"
23
21
  key
24
22
  end
@@ -26,30 +24,51 @@ module Redistat
26
24
  def date=(input)
27
25
  @date = (input.instance_of?(Redistat::Date)) ? input : Date.new(input) # Redistat::Date, not ::Date
28
26
  end
27
+ attr_reader :date
29
28
 
30
29
  def depth
31
- @options[:depth]
30
+ options[:depth]
31
+ end
32
+
33
+ def scope
34
+ @scope.to_s
35
+ end
36
+
37
+ def scope=(input)
38
+ @scope = (input.instance_of?(Redistat::Scope)) ? input : Scope.new(input)
32
39
  end
33
40
 
34
- def label
35
- @label.name
41
+ def label=(input)
42
+ @label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
36
43
  end
44
+ attr_reader :label
37
45
 
38
46
  def label_hash
39
47
  @label.hash
40
48
  end
41
49
 
42
- def label_groups
43
- @label.groups
50
+ def parent
51
+ @parent ||= self.class.new(self.scope, @label.parent, self.date, @options) unless @label.parent.nil?
44
52
  end
45
53
 
46
- def label=(input)
47
- @label = (input.instance_of?(Redistat::Label)) ? input : Label.create(input, @options)
54
+ def children
55
+ db.smembers("#{scope}#{LABEL_INDEX}#{@label}").map { |member|
56
+ child_label = [@label, member].reject { |i| i.nil? }
57
+ self.class.new(self.scope, child_label.join(GROUP_SEPARATOR), self.date, @options)
58
+ }
59
+ end
60
+
61
+ def update_index
62
+ @label.groups.each do |label|
63
+ # break if label.parent.nil?
64
+ parent = (label.parent || "")
65
+ db.sadd("#{scope}#{LABEL_INDEX}#{parent}", label.me)
66
+ end
48
67
  end
49
68
 
50
- def groups
51
- @groups ||= label_groups.map do |label_name|
52
- self.class.new(@scope, label_name, self.date, @options)
69
+ def groups # TODO: Is this useless?
70
+ @groups ||= @label.groups.map do |label|
71
+ self.class.new(@scope, label, self.date, @options)
53
72
  end
54
73
  end
55
74
 
@@ -1,48 +1,59 @@
1
1
  module Redistat
2
2
  class Label
3
3
  include Database
4
+ include Options
4
5
 
5
- attr_reader :raw
6
- attr_reader :connection_ref
6
+ def default_options
7
+ { :hashed_label => false }
8
+ end
7
9
 
8
- def self.create(name, options = {})
9
- self.new(name, options).save
10
+ def self.create(name, opts = {})
11
+ self.new(name, opts).save
10
12
  end
11
13
 
12
- def initialize(str, options = {})
13
- @options = options
14
+ def initialize(str, opts = {})
15
+ parse_options(opts)
14
16
  @raw = str.to_s
15
17
  end
16
-
17
- def db
18
- super(@options[:connection_ref])
18
+
19
+ def to_s
20
+ @raw
19
21
  end
20
22
 
21
23
  def name
22
- @options[:hashed_label] ? hash : @raw
24
+ @options[:hashed_label] ? hash : self.to_s
23
25
  end
24
26
 
25
27
  def hash
26
- @hash ||= Digest::SHA1.hexdigest(@raw)
28
+ @hash ||= Digest::SHA1.hexdigest(self.to_s)
27
29
  end
28
30
 
29
31
  def save
30
- @saved = (db.set("#{KEY_LEBELS}#{hash}", @raw) == "OK") if @options[:hashed_label]
32
+ @saved = db.hset(KEY_LEBELS, hash, self.to_s) if @options[:hashed_label]
31
33
  self
32
34
  end
33
35
 
34
36
  def saved?
37
+ return true unless @options[:hashed_label]
35
38
  @saved ||= false
36
39
  end
37
40
 
41
+ def parent
42
+ @parent ||= groups[1] if groups.size > 1
43
+ end
44
+
45
+ def me
46
+ self.to_s.split(GROUP_SEPARATOR).last
47
+ end
48
+
38
49
  def groups
39
- return @groups if @groups
50
+ return @groups unless @groups.nil?
40
51
  @groups = []
41
52
  parent = ""
42
- @raw.split(GROUP_SEPARATOR).each do |part|
53
+ self.to_s.split(GROUP_SEPARATOR).each do |part|
43
54
  if !part.blank?
44
- group = ((parent.blank?) ? "" : "#{parent}/") + part
45
- @groups << group
55
+ group = ((parent.blank?) ? "" : "#{parent}#{GROUP_SEPARATOR}") + part
56
+ @groups << Label.new(group)
46
57
  parent = group
47
58
  end
48
59
  end
@@ -1,16 +1,18 @@
1
1
  module Redistat
2
2
  module Model
3
- include Redistat::Database
3
+ include Database
4
+ include Options
4
5
 
5
6
  def self.included(base)
6
7
  base.extend(self)
7
8
  end
8
9
 
10
+
9
11
  #
10
12
  # statistics store/fetch methods
11
13
  #
12
14
 
13
- def store(label, stats = {}, date = nil, meta = {}, opts = {})
15
+ def store(label, stats = {}, date = nil, opts = {}, meta = {})
14
16
  Event.new(name, label, date, stats, options.merge(opts), meta).save
15
17
  end
16
18
  alias :event :store
@@ -21,53 +23,30 @@ module Redistat
21
23
  alias :lookup :fetch
22
24
 
23
25
  def find(label, from, till, opts = {})
24
- Finder.new( { :scope => name,
26
+ Finder.new( { :scope => self.name,
25
27
  :label => label,
26
28
  :from => from,
27
29
  :till => till }.merge(options.merge(opts)) )
28
30
  end
29
31
 
32
+
30
33
  #
31
34
  # options methods
32
35
  #
33
36
 
34
- def connect_to(opts = {})
35
- Connection.create(opts.merge(:ref => name))
36
- options[:connection_ref] = name
37
- end
38
-
39
- def hashed_label(boolean = nil)
40
- if !boolean.nil?
41
- options[:hashed_label] = boolean
42
- else
43
- options[:hashed_label] || nil
44
- end
45
- end
37
+ option_accessor :depth
38
+ option_accessor :class_name
39
+ option_accessor :store_event
40
+ option_accessor :hashed_label
41
+ option_accessor :label_indexing
46
42
 
47
- def class_name(class_name = nil)
48
- if !class_name.nil?
49
- options[:class_name] = class_name
50
- else
51
- options[:class_name] || nil
52
- end
53
- end
54
43
  alias :scope :class_name
55
44
 
56
- def depth(depth = nil)
57
- if !depth.nil?
58
- options[:depth] = depth
59
- else
60
- options[:depth] || nil
61
- end
45
+ def connect_to(opts = {})
46
+ Connection.create(opts.merge(:ref => name))
47
+ options[:connection_ref] = name
62
48
  end
63
49
 
64
- def store_event(boolean = nil)
65
- if !boolean.nil?
66
- options[:store_event] = boolean
67
- else
68
- options[:store_event] || nil
69
- end
70
- end
71
50
 
72
51
  #
73
52
  # resource access methods
@@ -78,10 +57,6 @@ module Redistat
78
57
  end
79
58
  alias :redis :connection
80
59
 
81
- def options
82
- @options ||= {}
83
- end
84
-
85
60
  def name
86
61
  options[:class_name] || (@name ||= self.to_s)
87
62
  end
@@ -0,0 +1,43 @@
1
+ module Redistat
2
+ module Options
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ class InvalidDefaultOptions < ArgumentError; end
9
+
10
+ module ClassMethods
11
+ def option_accessor(*opts)
12
+ opts.each do |option|
13
+ define_method(option) do |*args|
14
+ if !args.first.nil?
15
+ options[option.to_sym] = args.first
16
+ else
17
+ options[option.to_sym] || nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def parse_options(opts)
25
+ opts ||= {}
26
+ @raw_options = opts
27
+ @options = default_options.merge(opts.reject { |k,v| v.nil? })
28
+ end
29
+
30
+ def default_options
31
+ {}
32
+ end
33
+
34
+ def options
35
+ @options ||= {}
36
+ end
37
+
38
+ def raw_options
39
+ @raw_options ||= {}
40
+ end
41
+
42
+ end
43
+ end
@@ -2,20 +2,28 @@ module Redistat
2
2
  class Summary
3
3
  include Database
4
4
 
5
- def self.update_all(key, stats = {}, depth_limit = nil, connection_ref = nil, enable_grouping = nil)
5
+ def self.default_options
6
+ { :enable_grouping => true,
7
+ :label_indexing => true,
8
+ :connection_ref => nil }
9
+ end
10
+
11
+ def self.update_all(key, stats = {}, depth_limit = nil, opts = {})
6
12
  stats ||= {}
7
13
  return nil if stats.size == 0
8
14
 
15
+ options = default_options.merge((opts || {}).reject { |k,v| v.nil? })
16
+
9
17
  depth_limit ||= key.depth
10
- enable_grouping = true if enable_grouping.nil?
11
18
 
12
- if enable_grouping
19
+ if options[:enable_grouping]
13
20
  stats = inject_group_summaries(stats)
14
21
  key.groups.each { |k|
15
- update_key(k, stats, depth_limit, connection_ref)
22
+ update_key(k, stats, depth_limit, options[:connection_ref])
23
+ k.update_index if options[:label_indexing]
16
24
  }
17
25
  else
18
- update_key(key, stats, depth_limit, connection_ref)
26
+ update_key(key, stats, depth_limit, options[:connection_ref])
19
27
  end
20
28
  end
21
29
 
@@ -1,3 +1,3 @@
1
1
  module Redistat
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -27,4 +27,5 @@ Gem::Specification.new do |s|
27
27
  s.add_development_dependency 'rspec', '>= 2.1.0'
28
28
  s.add_development_dependency 'rcov', '>= 0.9.9'
29
29
  s.add_development_dependency 'yard', '>= 0.6.3'
30
+ s.add_development_dependency 'ruby-debug'
30
31
  end
@@ -18,7 +18,7 @@ describe Redistat::Event do
18
18
  it "should initialize properly" do
19
19
  @event.id.should be_nil
20
20
  @event.scope.should == @scope
21
- @event.label.should == @label
21
+ @event.label.to_s.should == @label
22
22
  @event.label_hash.should == @label_hash
23
23
  @event.date.to_time.to_s.should == @date.to_s
24
24
  @event.stats.should == @stats
@@ -33,12 +33,12 @@ describe Redistat::Event do
33
33
  @event.date = @date
34
34
  @event.date.to_time.to_s.should == @date.to_s
35
35
  # label
36
- @event.label.should == @label
36
+ @event.label.to_s.should == @label
37
37
  @event.label_hash.should == @label_hash
38
38
  @label = "contact_us"
39
39
  @label_hash = Digest::SHA1.hexdigest(@label)
40
40
  @event.label = @label
41
- @event.label.should == @label
41
+ @event.label.to_s.should == @label
42
42
  @event.label_hash.should == @label_hash
43
43
  end
44
44
 
@@ -64,7 +64,7 @@ describe Redistat::Event do
64
64
  @event = Redistat::Event.new(@scope, @label, @date, @stats, @options.merge({:store_event => true}), @meta).save
65
65
  fetched = Redistat::Event.find(@scope, @event.id)
66
66
  @event.scope.should == fetched.scope
67
- @event.label.should == fetched.label
67
+ @event.label.to_s.should == fetched.label.to_s
68
68
  @event.date.to_s.should == fetched.date.to_s
69
69
  end
70
70
 
@@ -9,41 +9,43 @@ describe Redistat::Finder do
9
9
  @label = "about_us"
10
10
  @date = Time.now
11
11
  @key = Redistat::Key.new(@scope, @label, @date, {:depth => :day})
12
- @stats = {"views" => 3, "visitors" => 2}
12
+ @stats = {"views" => 3, "visitors" => 2}
13
+ @two_hours_ago = 2.hours.ago
14
+ @one_hour_ago = 1.hour.ago
13
15
  end
14
16
 
15
17
  it "should initialize properly" do
16
- two_hours_ago = 2.hours.ago
17
- one_hour_ago = 1.hour.ago
18
- options = {:scope => "PageViews", :label => "Label", :from => two_hours_ago, :till => one_hour_ago, :depth => :hour, :interval => :hour}
19
-
20
- finder = Redistat::Finder.new(options)
21
- finder.options.should == options
18
+ options = {:scope => "PageViews", :label => "Label", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
22
19
 
23
20
  finder = Redistat::Finder.new
24
21
  finder.send(:set_options, options)
25
- finder.options.should == options
26
-
27
- finder = Redistat::Finder.dates(two_hours_ago, one_hour_ago).scope("PageViews").label("Label").depth(:hour).interval(:hour)
28
- finder.options.should == options
29
-
30
- finder = Redistat::Finder.scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour)
31
- finder.options.should == options
22
+ finder.options[:scope].should be_a(Redistat::Scope)
23
+ finder.options[:scope].to_s.should == options[:scope]
24
+ finder.options[:label].should be_a(Redistat::Label)
25
+ finder.options[:label].to_s.should == options[:label]
26
+ finder.options.should == options.merge(:scope => finder.options[:scope], :label => finder.options[:label])
27
+
28
+ finder = Redistat::Finder.dates(@two_hours_ago, @one_hour_ago)
29
+ finder.options[:from].should == @two_hours_ago
30
+ finder.options[:till].should == @one_hour_ago
31
+
32
+ finder = Redistat::Finder.scope("hello")
33
+ finder.options[:scope].to_s.should == "hello"
32
34
 
33
- finder = Redistat::Finder.label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews")
34
- finder.options.should == options
35
+ finder = Redistat::Finder.label("hello")
36
+ finder.options[:label].to_s.should == "hello"
35
37
 
36
- finder = Redistat::Finder.from(two_hours_ago).till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews").label("Label")
37
- finder.options.should == options
38
+ finder = Redistat::Finder.from(@two_hours_ago)
39
+ finder.options[:from].should == @two_hours_ago
38
40
 
39
- finder = Redistat::Finder.till(one_hour_ago).depth(:hour).interval(:hour).scope("PageViews").label("Label").from(two_hours_ago)
40
- finder.options.should == options
41
+ finder = Redistat::Finder.till(@one_hour_ago)
42
+ finder.options[:till].should == @one_hour_ago
41
43
 
42
- finder = Redistat::Finder.depth(:hour).interval(:hour).scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago)
43
- finder.options.should == options
44
+ finder = Redistat::Finder.depth(:hour)
45
+ finder.options[:depth].should == :hour
44
46
 
45
- finder = Redistat::Finder.interval(:hour).scope("PageViews").label("Label").from(two_hours_ago).till(one_hour_ago).depth(:hour)
46
- finder.options.should == options
47
+ finder = Redistat::Finder.interval(:hour)
48
+ finder.options[:interval].should == :hour
47
49
 
48
50
  end
49
51
 
@@ -89,6 +91,21 @@ describe Redistat::Finder do
89
91
  lambda { Redistat::Finder.find(:from => 3.hours.ago) }.should raise_error(Redistat::InvalidOptions)
90
92
  end
91
93
 
94
+ it "should find children" do
95
+ Redistat::Key.new("PageViews", "message/public/die").update_index
96
+ Redistat::Key.new("PageViews", "message/public/live").update_index
97
+ Redistat::Key.new("PageViews", "message/public/fester").update_index
98
+ members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}message/public") # checking 'message/public'
99
+ options = {:scope => "PageViews", :label => "message/public", :from => @two_hours_ago, :till => @one_hour_ago, :depth => :hour, :interval => :hour}
100
+ finder = Redistat::Finder.new(options)
101
+ finder.children.first.should be_a(Redistat::Finder)
102
+ subs = finder.children.map { |f| f.options[:label].me }
103
+ subs.should have(3).items
104
+ subs.should include('die')
105
+ subs.should include('live')
106
+ subs.should include('fester')
107
+ end
108
+
92
109
  describe "Lazy-Loading" do
93
110
 
94
111
  before(:each) do
@@ -103,7 +120,6 @@ describe Redistat::Finder do
103
120
  end
104
121
 
105
122
  it "should lazy-load" do
106
-
107
123
  @finder.instance_variable_get("@result").should be_nil
108
124
  stats = @finder.all
109
125
  @finder.instance_variable_get("@result").should_not be_nil
@@ -146,7 +162,7 @@ describe Redistat::Finder do
146
162
  res.should == match
147
163
  end
148
164
 
149
- end
165
+ end # "Lazy-Loading"
150
166
 
151
167
 
152
168
  # helper methods
@@ -1,8 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Redistat::Key do
4
+ include Redistat::Database
4
5
 
5
6
  before(:each) do
7
+ db.flushdb
6
8
  @scope = "PageViews"
7
9
  @label = "about_us"
8
10
  @label_hash = Digest::SHA1.hexdigest(@label)
@@ -12,9 +14,9 @@ describe Redistat::Key do
12
14
 
13
15
  it "should initialize properly" do
14
16
  @key.scope.should == @scope
15
- @key.label.should == @label
17
+ @key.label.to_s.should == @label
16
18
  @key.label_hash.should == @label_hash
17
- @key.label_groups.should == @key.instance_variable_get("@label").groups
19
+ @key.groups.map { |k| k.instance_variable_get("@label") }.should == @key.instance_variable_get("@label").groups
18
20
  @key.date.should be_instance_of(Redistat::Date)
19
21
  @key.date.to_time.to_s.should == @date.to_s
20
22
  end
@@ -52,25 +54,74 @@ describe Redistat::Key do
52
54
  @key.date = @date
53
55
  @key.date.to_time.to_s.should == @date.to_s
54
56
  # label
55
- @key.label.should == @label
57
+ @key.label.to_s.should == @label
56
58
  @key.label_hash == @label_hash
57
59
  @label = "contact_us"
58
60
  @label_hash = Digest::SHA1.hexdigest(@label)
59
61
  @key.label = @label
60
- @key.label.should == @label
62
+ @key.label.to_s.should == @label
61
63
  @key.label_hash == @label_hash
62
64
  end
63
65
 
64
- it "should create a group of keys from label group" do
65
- label = 'message/public/offensive'
66
- result = [ "message/public/offensive",
67
- "message/public",
68
- "message" ]
66
+ describe "Grouping" do
67
+ before(:each) do
68
+ @label = "message/public/offensive"
69
+ @key = Redistat::Key.new(@scope, @label, @date, {:depth => :hour})
70
+ end
69
71
 
70
- key = Redistat::Key.new(@scope, label, @date, {:depth => :hour})
72
+ it "should create a group of keys from label group" do
73
+ label = 'message/public/offensive'
74
+ result = [ "message/public/offensive",
75
+ "message/public",
76
+ "message" ]
77
+
78
+ key = Redistat::Key.new(@scope, label, @date, {:depth => :hour})
79
+
80
+ key.groups.map { |k| k.label.to_s }.should == result
81
+ end
71
82
 
72
- key.label_groups.should == result
73
- key.groups.map { |k| k.label }.should == result
83
+ it "should know it's parent" do
84
+ @key.parent.should be_a(Redistat::Key)
85
+ @key.parent.label.to_s.should == 'message/public'
86
+ Redistat::Key.new(@scope, 'hello', @date).parent.should be_nil
87
+ end
88
+
89
+ it "should update label index and return children" do
90
+ db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{@key.label.parent}").should == []
91
+ @key.children.should have(0).items
92
+
93
+ @key.update_index # indexing 'message/publish/offensive'
94
+ Redistat::Key.new("PageViews", "message/public/die").update_index # indexing 'message/publish/die'
95
+ Redistat::Key.new("PageViews", "message/public/live").update_index # indexing 'message/publish/live'
96
+
97
+ members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{@key.label.parent}") # checking 'message/public'
98
+ members.should have(3).item
99
+ members.should include('offensive')
100
+ members.should include('live')
101
+ members.should include('die')
102
+
103
+ key = @key.parent
104
+ key.children.first.should be_a(Redistat::Key)
105
+ key.children.should have(3).item
106
+ key.children.map { |k| k.label.me }.should == members
107
+
108
+ members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}#{key.label.parent}") # checking 'message'
109
+ members.should have(1).item
110
+ members.should include('public')
111
+
112
+ key = key.parent
113
+ key.children.should have(1).item
114
+ key.children.map { |k| k.label.me }.should == members
115
+
116
+ members = db.smembers("#{@scope}#{Redistat::LABEL_INDEX}") # checking ''
117
+ members.should have(1).item
118
+ members.should include('message')
119
+
120
+ key.parent.should be_nil
121
+ key = Redistat::Key.new("PageViews")
122
+ key.children.should have(1).item
123
+ key.children.map { |k| k.label.me }.should include('message')
124
+ end
74
125
  end
75
126
 
76
127
  end
@@ -17,32 +17,42 @@ describe Redistat::Label do
17
17
  it "should store a label hash lookup key" do
18
18
  label = Redistat::Label.new(@name, {:hashed_label => true}).save
19
19
  label.saved?.should be_true
20
- db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == @name
20
+ db.hget(Redistat::KEY_LEBELS, label.hash).should == @name
21
21
 
22
22
  name = "contact_us"
23
23
  label = Redistat::Label.create(name, {:hashed_label => true})
24
24
  label.saved?.should be_true
25
- db.get("#{Redistat::KEY_LEBELS}#{label.hash}").should == name
25
+ db.hget(Redistat::KEY_LEBELS, label.hash).should == name
26
26
  end
27
27
 
28
- it "should separate label names into groups" do
29
- name = "message/public/offensive"
30
- label = Redistat::Label.new(name)
31
- label.name.should == name
32
- label.groups.should == [ "message/public/offensive",
33
- "message/public",
34
- "message" ]
28
+ describe "Grouping" do
29
+ before(:each) do
30
+ @name = "message/public/offensive"
31
+ @label = Redistat::Label.new(@name)
32
+ end
33
+
34
+ it "should know it's parent label group" do
35
+ @label.parent.to_s.should == 'message/public'
36
+ Redistat::Label.new('hello').parent.should be_nil
37
+ end
38
+
39
+ it "should separate label names into groups" do
40
+ @label.name.should == @name
41
+ @label.groups.map { |l| l.to_s }.should == [ "message/public/offensive",
42
+ "message/public",
43
+ "message" ]
35
44
 
36
- name = "/message/public/"
37
- label = Redistat::Label.new(name)
38
- label.name.should == name
39
- label.groups.should == [ "message/public",
40
- "message" ]
45
+ @name = "/message/public/"
46
+ @label = Redistat::Label.new(@name)
47
+ @label.name.should == @name
48
+ @label.groups.map { |l| l.to_s }.should == [ "message/public",
49
+ "message" ]
41
50
 
42
- name = "message"
43
- label = Redistat::Label.new(name)
44
- label.name.should == name
45
- label.groups.should == [ "message" ]
51
+ @name = "message"
52
+ @label = Redistat::Label.new(@name)
53
+ @label.name.should == @name
54
+ @label.groups.map { |l| l.to_s }.should == [ "message" ]
55
+ end
46
56
  end
47
57
 
48
58
  end
@@ -22,8 +22,8 @@ describe Redistat::Model do
22
22
  one_hour_ago = 1.hour.ago
23
23
  finder = ModelHelper1.find('label', two_hours_ago, one_hour_ago)
24
24
  finder.should be_a(Redistat::Finder)
25
- finder.options[:scope].should == 'ModelHelper1'
26
- finder.options[:label].should == 'label'
25
+ finder.options[:scope].to_s.should == 'ModelHelper1'
26
+ finder.options[:label].to_s.should == 'label'
27
27
  finder.options[:from].should == two_hours_ago
28
28
  finder.options[:till].should == one_hour_ago
29
29
  end
@@ -82,34 +82,71 @@ describe Redistat::Model do
82
82
  stats.first.should == stats.total
83
83
  end
84
84
 
85
+ it "should store and fetch grouping enabled stats" do
86
+ ModelHelper1.store("sheep/black", {:count => 6, :weight => 461}, @time.hours_ago(4))
87
+ ModelHelper1.store("sheep/black", {:count => 2, :weight => 156}, @time)
88
+ ModelHelper1.store("sheep/white", {:count => 5, :weight => 393}, @time.hours_ago(4))
89
+ ModelHelper1.store("sheep/white", {:count => 4, :weight => 316}, @time)
90
+
91
+ stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(2), @time.hours_since(1))
92
+ stats.total["count"].should == 2
93
+ stats.total["weight"].should == 156
94
+ stats.first.should == stats.total
95
+
96
+ stats = ModelHelper1.fetch("sheep/black", @time.hours_ago(5), @time.hours_since(1))
97
+ stats.total[:count].should == 8
98
+ stats.total[:weight].should == 617
99
+ stats.first.should == stats.total
100
+
101
+ stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(2), @time.hours_since(1))
102
+ stats.total[:count].should == 4
103
+ stats.total[:weight].should == 316
104
+ stats.first.should == stats.total
105
+
106
+ stats = ModelHelper1.fetch("sheep/white", @time.hours_ago(5), @time.hours_since(1))
107
+ stats.total[:count].should == 9
108
+ stats.total[:weight].should == 709
109
+ stats.first.should == stats.total
110
+
111
+ stats = ModelHelper1.fetch("sheep", @time.hours_ago(2), @time.hours_since(1))
112
+ stats.total[:count].should == 6
113
+ stats.total[:weight].should == 472
114
+ stats.first.should == stats.total
115
+
116
+ stats = ModelHelper1.fetch("sheep", @time.hours_ago(5), @time.hours_since(1))
117
+ stats.total[:count].should == 17
118
+ stats.total[:weight].should == 1326
119
+ stats.first.should == stats.total
120
+ end
121
+
85
122
  it "should connect to different Redis servers on a per-model basis" do
86
123
  ModelHelper3.redis.client.db.should == 14
87
124
 
88
- ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4))
89
- ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time)
125
+ ModelHelper3.store("sheep.black", {:count => 6, :weight => 461}, @time.hours_ago(4), :label_indexing => false)
126
+ ModelHelper3.store("sheep.black", {:count => 2, :weight => 156}, @time, :label_indexing => false)
90
127
 
91
128
  db.keys("*").should be_empty
92
129
  ModelHelper1.redis.keys("*").should be_empty
93
130
  db("ModelHelper3").keys("*").should have(5).items
94
131
  ModelHelper3.redis.keys("*").should have(5).items
95
132
 
96
- stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1))
133
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(2), @time.hours_since(1), :label_indexing => false)
97
134
  stats.total["count"].should == 2
98
135
  stats.total["weight"].should == 156
99
- stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
136
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
100
137
  stats.total[:count].should == 8
101
138
  stats.total[:weight].should == 617
102
139
 
103
140
  ModelHelper3.connect_to(:port => 8379, :db => 13)
104
141
  ModelHelper3.redis.client.db.should == 13
105
142
 
106
- stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
143
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
107
144
  stats.total.should == {}
108
145
 
109
146
  ModelHelper3.connect_to(:port => 8379, :db => 14)
110
147
  ModelHelper3.redis.client.db.should == 14
111
148
 
112
- stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1))
149
+ stats = ModelHelper3.fetch("sheep.black", @time.hours_ago(5), @time.hours_since(1), :label_indexing => false)
113
150
  stats.total[:count].should == 8
114
151
  stats.total[:weight].should == 617
115
152
  end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe Redistat::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 Redistat::Options
27
+
28
+ option_accessor :hello
29
+
30
+ def default_options
31
+ { :hello => 'world',
32
+ :foo => 'bar' }
33
+ end
34
+
35
+
36
+ end
@@ -69,7 +69,7 @@ describe Redistat::Summary do
69
69
 
70
70
  it "should not store key group summaries when option is disabled" do
71
71
  stats = {"views" => 3, "visitors/eu" => 2, "visitors/us" => 4}
72
- Redistat::Summary.update_all(@key, stats, :hour, nil, false)
72
+ Redistat::Summary.update_all(@key, stats, :hour, {:enable_grouping => false})
73
73
  summary = db.hgetall(@key.to_s(:hour))
74
74
  summary.should have(3).items
75
75
  summary["views"].should == "3"
@@ -83,8 +83,8 @@ describe Redistat::Summary do
83
83
  key = Redistat::Key.new(@scope, label, @date)
84
84
  Redistat::Summary.update_all(key, stats, :hour)
85
85
 
86
- key.groups[0].label.should == "views/about_us"
87
- key.groups[1].label.should == "views"
86
+ key.groups[0].label.to_s.should == "views/about_us"
87
+ key.groups[1].label.to_s.should == "views"
88
88
  child1 = key.groups[0]
89
89
  parent = key.groups[1]
90
90
 
@@ -92,8 +92,8 @@ describe Redistat::Summary do
92
92
  key = Redistat::Key.new(@scope, label, @date)
93
93
  Redistat::Summary.update_all(key, stats, :hour)
94
94
 
95
- key.groups[0].label.should == "views/contact"
96
- key.groups[1].label.should == "views"
95
+ key.groups[0].label.to_s.should == "views/contact"
96
+ key.groups[1].label.to_s.should == "views"
97
97
  child2 = key.groups[0]
98
98
 
99
99
  summary = db.hgetall(child1.to_s(:hour))
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: 25
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
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-09 00:00:00 +00:00
18
+ date: 2011-03-10 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -130,6 +130,20 @@ dependencies:
130
130
  version: 0.6.3
131
131
  type: :development
132
132
  version_requirements: *id007
133
+ - !ruby/object:Gem::Dependency
134
+ name: ruby-debug
135
+ prerelease: false
136
+ requirement: &id008 !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ type: :development
146
+ version_requirements: *id008
133
147
  description: A Redis-backed statistics storage and querying library written in Ruby.
134
148
  email:
135
149
  - contact@jimeh.me
@@ -164,6 +178,7 @@ files:
164
178
  - lib/redistat/key.rb
165
179
  - lib/redistat/label.rb
166
180
  - lib/redistat/model.rb
181
+ - lib/redistat/options.rb
167
182
  - lib/redistat/result.rb
168
183
  - lib/redistat/scope.rb
169
184
  - lib/redistat/summary.rb
@@ -181,6 +196,7 @@ files:
181
196
  - spec/label_spec.rb
182
197
  - spec/model_helper.rb
183
198
  - spec/model_spec.rb
199
+ - spec/options_spec.rb
184
200
  - spec/redis-test.conf
185
201
  - spec/result_spec.rb
186
202
  - spec/scope_spec.rb
@@ -234,6 +250,7 @@ test_files:
234
250
  - spec/label_spec.rb
235
251
  - spec/model_helper.rb
236
252
  - spec/model_spec.rb
253
+ - spec/options_spec.rb
237
254
  - spec/redis-test.conf
238
255
  - spec/result_spec.rb
239
256
  - spec/scope_spec.rb