redistat 0.1.1 → 0.2.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.
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