trackoid 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+
data/README.rdoc CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  Trackoid is an analytics tracking system made specifically for MongoDB using Mongoid as ORM.
4
4
 
5
+ = *** IMPORTANT ***
6
+
7
+ Trackoid Version 0.3.0 changes the internal representation of tracking data. So <b>YOU WILL NOT SEE PREVIOUS DATA</b> when you update.
8
+
9
+ Hopefully, due to the magic of MongoDB, data is <b>NOT LOST</b>. In fact it's never lost unless you delete it. :-) Just it's not visible right away.
10
+
11
+ See <b>Changes for TZ support</b> below for an explanation of changes. If you absolutely, desperately, dead or alive, need a migration, leave me a message and we can arrange a migration script.
12
+
13
+
5
14
  = Requirements
6
15
 
7
16
  Trackoid requires Mongoid, which obviously in turn requires MongoDB. Although you can only use Trackoid in Rails projects using Mongoid, it can easily be ported to MongoMapper or other ORM. You can also port it to work directly using MongoDB.
@@ -186,8 +195,129 @@ A year full of statistical data takes only 2.8Kb, if you store integers. If your
186
195
  For comparison, this README is already 8.5Kb in size...
187
196
 
188
197
 
198
+ = Changes for TZ support
199
+
200
+ Well, this is the time (no pun intended) to add TZ support to Trackoid.
201
+
202
+ The problem is that "today" is not the same "today" for everyone, so unless you live in UTC or don't care about time zones, you probably should stay in 0.2.0 version and live long and prosper...
203
+
204
+ But... Okay, given the fact that "today" is not the same "today" for everyone, this is the brand new Trackoid, with TZ support.
205
+
206
+ == What has changed?
207
+
208
+ In the surface, almost nothing, but internally there has been a major rewrite of the tracking code (the 'inc', 'set' methods) and the readers ('today', 'yesterday', etc). This is due to the changes I've made to the MongoDB structure of the tracking data.
209
+
210
+ <b>YOU WILL NEED TO MIGRATE THE EXISTING DATA IF YOU WANT TO KEEP IT</b>
211
+
212
+ This is very important, so I will repeat:
213
+
214
+ <b>YOU WILL NEED TO MIGRATE THE EXISTING DATA IF YOU WANT TO KEEP IT</b>
215
+
216
+ The internal JSON structure of a tracking field was like that.
217
+
218
+ {
219
+ ... some other fields in the model...,
220
+ "tracking_data" : {
221
+ "2011" : {
222
+ "01" : {
223
+ "01" : 10,
224
+ "02" : 20,
225
+ "03" : 30,
226
+ ...
227
+ },
228
+ "02" : {
229
+ "01" : 10,
230
+ "02" : 20,
231
+ "03" : 30,
232
+ ...
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ That is, years, months and days numbers created a nested hash in which the final data (leaves) was the amount tracked. You see? There was no trace of hours... That's the problem.
239
+
240
+ This is the new, TZ aware version of the internal JSON structure:
241
+
242
+ {
243
+ ... some other fields in the model...,
244
+ "tracking_data" : {
245
+ "14975" : {
246
+ "00" : 10,
247
+ "01" : 20,
248
+ "02" : 30,
249
+ ...
250
+ "22" : 88,
251
+ "23" : 99
252
+ },
253
+ "14976" : {
254
+ "00" : 10,
255
+ "01" : 20,
256
+ "02" : 30,
257
+ ...
258
+ "22" : 88,
259
+ "23" : 99
260
+ }
261
+ }
262
+ }
263
+
264
+ So, instead of a nested array with keys like year/month/day, I now use the timestamp of the date. Well, a cooked timestamp. "14975" is the numbers of days since the epoch, which is the number of seconds elapsed since midnight Coordinated Universal Time (UTC) of January 1, 1970, and blah, blah, blah... You know what's this all about (http://en.wikipedia.org/wiki/Unix_time)
265
+
266
+ The exact formula is like this (Ruby):
267
+
268
+ date_index = Time.now.utc.to_i / 60 / 60 / 24
269
+
270
+ The contents of every "day record" is another hash with 24 keys, one for each hour. This MUST be a hash, not an array (which might be more natural) sice Trackoid uses "upserts" operations to be atomic. Reading the array, modifying it and saving it back is not an option. The exact MongoDB operation to support this is as follows:
271
+
272
+ db.update(
273
+ { search_criteria },
274
+ { "$inc" => {"track_data.14976.10" => 1} },
275
+ { :upsert => true, :safe => false }
276
+ )
277
+
278
+ == What "today" is it?
279
+
280
+ All dates are saved in UTC. That means Trackoid returns a whole 24 hour block for "today" only where the TZ is exactly UTC/GMT (no offset). If you live in a country where there is an offset into UTC, Trackoid must read a whole block and some hours from the block before or after to build "your today".
281
+
282
+ Example: I live in GMT+0200 (Daylight saving in effect, or summer time), then if I request data for "today" as of 2011-04-14, Trackoid must first read the block for 15078 (UTC index for 2011-04-14), shift up 2 hours and then fill the missing 2 hours from the day before (15078). The entire block will be like this:
283
+
284
+ "tracking_data" : {
285
+ "15078" : {
286
+ "22" : 88, # Last two hours from 2011-04-13 UTC
287
+ "23" : 99
288
+ },
289
+ "15079" : {
290
+ "00" : 10,
291
+ "01" : 20,
292
+ "02" : 30,
293
+ ""
294
+ }
295
+
296
+ This is a more graphical representation:
297
+
298
+ Hours 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
299
+ ------ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
300
+ GMT+2: 00 00 00 XX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
301
+ UTC: ---> 00 XX 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
302
+ Shift up ---> 2 hours.
303
+
304
+
305
+ For timezones with a negative offset from UTC (Like PDT/PST) the process is reversed: UTC values are shifted down and holes filled with the following day.
306
+
307
+
308
+ == How should I tell Trackoid how TZ to use?
309
+
310
+ Piece of cake: Use the reader methods "today", "yesterday", "last_days(N)" and Trackoid will use the effective Time Zone of your Rails/Ruby application.
311
+
312
+ Trackoid will correctly translate dates for you (hopefully) if you pass a date to any of those methods.
313
+
314
+
315
+
189
316
  = Revision History
190
317
 
318
+ 0.3.0 - Biggest change ever. Read <b>Changes for TZ support</b> above.
319
+ <b>YOU WILL NEED A MIGRATION FOR EXISTING DATA</b>
320
+
191
321
  0.2.0 - Added 'reset' and 'erase' methods to tracker fields:
192
322
  * Reset does the same as "set" but also sets aggregate fields.
193
323
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -2,6 +2,9 @@
2
2
  module Mongoid #:nodoc:
3
3
  module Tracking #:nodoc:
4
4
  module Aggregates
5
+
6
+ DEPRECATED_TOKENS = ['hour', 'hours']
7
+
5
8
  # This module includes aggregate data extensions to Trackoid instances
6
9
  def self.included(base)
7
10
  base.class_eval do
@@ -55,8 +58,8 @@ module Mongoid #:nodoc:
55
58
  # But you are encouraged to use Trackoid methods whenever possible.
56
59
  #
57
60
  def aggregate(name, &block)
58
- raise Errors::AggregationAlreadyDefined.new(self.name, name) if
59
- aggregate_fields.has_key? name
61
+ raise Errors::AggregationAlreadyDefined.new(self.name, name) if aggregate_fields.has_key? name
62
+ raise Errors::AggregationNameDeprecated.new(name) if DEPRECATED_TOKENS.include? name.to_s
60
63
 
61
64
  define_aggregate_model if aggregate_klass.nil?
62
65
  has_many_related internal_accessor_name(name), :class_name => aggregate_klass.to_s
@@ -83,6 +86,7 @@ module Mongoid #:nodoc:
83
86
  # Defines the aggregation model. It checks for class name conflicts
84
87
  def define_aggregate_model
85
88
  raise Errors::ClassAlreadyDefined.new(internal_aggregates_name) if foreign_class_defined?
89
+
86
90
  parent = self
87
91
  define_klass do
88
92
  include Mongoid::Document
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ class Range
3
+ # Adds some enumerable capabilities to Time ranges
4
+ # (Normally they raise a "Can't iterate time ranges")
5
+ #
6
+ # It works by assuming days while iterating the time range, but you can
7
+ # pass an optional parameter
8
+
9
+ HOURS = 3600
10
+ DAYS = 24*HOURS
11
+ DEFAULT_TIME_GRANULARITY = DAYS
12
+
13
+ # Map / Collect over a Time range.
14
+ # A better implementation would be redefining 'succ' on Time. However,
15
+ # the ruby source code (At least 1.9.2-p0) hardcodes a check for Type,
16
+ # so it would not work even if we provide our own 'succ' for Time.
17
+ def collect(step = DEFAULT_TIME_GRANULARITY)
18
+ return super() unless first.is_a?(Time)
19
+
20
+ return collect(step) {|c| c} unless block_given?
21
+
22
+ # Pretty much a standard implementation of Map/Collect here
23
+ ary, current, op = [], first, (exclude_end? ? :< : :<=)
24
+ while current.send(op, last)
25
+ ary << yield(current)
26
+ current = current + step
27
+ end
28
+ ary
29
+ end
30
+ alias :map :collect
31
+
32
+ # Diff returns the number of elements in the Range, much like 'count'.
33
+ # Again, redefining 'succ' would be a better idea (see above).
34
+ # However, I think redefining 'succ' makes this O(n) while this is O(1)
35
+ def diff(granularity = DEFAULT_TIME_GRANULARITY)
36
+ if first.is_a?(Time)
37
+ @diff ||= (last - first) / granularity + (exclude_end? ? 0 : 1)
38
+ @diff.to_i
39
+ else
40
+ @diff ||= count
41
+ end
42
+ end
43
+
44
+ # Helper methods for non default parameters
45
+ def hour_diff
46
+ diff(HOURS)
47
+ end
48
+
49
+ def hour_collect(&block)
50
+ collect(HOURS, &block)
51
+ end
52
+ alias :hour_map :hour_collect
53
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ class Time
3
+ # Functions to construct the MongoDB field key for trackers
4
+ #
5
+ # to_i_timestamp returns the computed UTC timestamp regardless of the
6
+ # timezone.
7
+ #
8
+ # Examples:
9
+ # 2011-01-01 00:00:00 UTC ===> 14975
10
+ # 2011-01-01 23:59:59 UTC ===> 14975
11
+ # 2011-01-02 00:00:00 UTC ===> 14976
12
+ #
13
+ # to_i_hour returns the hour for the date, again regardless of TZ
14
+ #
15
+ # 2011-01-01 00:00:00 UTC ===> 0
16
+ # 2011-01-01 23:59:59 UTC ===> 23
17
+ #
18
+ ONEHOUR = 60 * 60
19
+ ONEDAY = 24 * ONEHOUR
20
+
21
+ def to_i_timestamp
22
+ self.dup.utc.to_i / ONEDAY
23
+ end
24
+
25
+ def to_i_hour
26
+ self.dup.utc.hour
27
+ end
28
+
29
+ # Returns an integer to use as MongoDB key
30
+ def to_key
31
+ "#{to_i_timestamp}.#{to_i_hour}"
32
+ end
33
+
34
+ def self.from_key(ts, h)
35
+ Time.at(ts.to_i * ONEDAY + h.to_i * ONEHOUR)
36
+ end
37
+
38
+ # Returns a range to be enumerated using hours for the whole day
39
+ def whole_day
40
+ # We could have used 'beginning_of_day' from ActiveSupport, but don't
41
+ # want to introduce a dependency (I've tried to avoid ActiveSupport
42
+ # although you will be using it since it's introduced by Mongoid)
43
+ midnight = Time.send(utc? ? :utc : :local, year, month, day)
44
+ midnight...(midnight + ::Range::DAYS)
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+ require 'trackoid/core_ext/time'
3
+ require 'trackoid/core_ext/range'
@@ -13,10 +13,20 @@ module Mongoid #:nodoc
13
13
 
14
14
  class AggregationAlreadyDefined < RuntimeError
15
15
  def initialize(klass, token)
16
+ @klass = klass
17
+ @token = token
18
+ end
19
+ def message
20
+ "Aggregation '#{@token}' already defined for model #{@klass}"
21
+ end
22
+ end
23
+
24
+ class AggregationNameDeprecated < RuntimeError
25
+ def initialize(token)
16
26
  @token = token
17
27
  end
18
28
  def message
19
- "Aggregation '#{@token}' already defined for model #{klass}"
29
+ "Ussing aggregation name '#{@klass}' is deprecated. Please select another name."
20
30
  end
21
31
  end
22
32
 
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Tracking
4
+
5
+ # ReaderExtender is used in cases where we need to return an integer
6
+ # (class Numeric) while extending their contents. It would allow to
7
+ # perform advanced calculations in some situations:
8
+ #
9
+ # Example:
10
+ #
11
+ # a = visits.today # Would return a number, but "extended" so that
12
+ # # we can make a.hourly to get a detailed, hourly
13
+ # # array of the visits.
14
+ #
15
+ # b = visits.yesterday
16
+ # c = a + b # Here, in c, normally we would have a FixNum with
17
+ # # the sum of a plus b, but if we extend the sum
18
+ # # operation, we can additionaly sum the hourly
19
+ # # array and return a new ReaderExtender c.
20
+ #
21
+ class ReaderExtender
22
+ def initialize(number, hours)
23
+ @number = number
24
+ @hours = hours
25
+ end
26
+
27
+ def hourly
28
+ @hours
29
+ end
30
+
31
+ def to_s
32
+ @number.to_s
33
+ end
34
+
35
+ def ==(other)
36
+ @number == other
37
+ end
38
+
39
+ def <=>(other)
40
+ @number <=> other
41
+ end
42
+
43
+ def +(other)
44
+ return @number + other unless other.is_a?(ReaderExtender)
45
+
46
+ @number = @number + other
47
+ @hours = @hours.zip(other.hourly).map!(&:sum)
48
+ self
49
+ end
50
+
51
+ # Solution proposed by Yehuda Katz in the following Stack Overflow:
52
+ # http://stackoverflow.com/questions/1095789/sub-classing-fixnum-in-ruby
53
+ #
54
+ # Basically we override our methods while proxying all missing methods
55
+ # to the underliying FixNum
56
+ #
57
+ def method_missing(name, *args, &blk)
58
+ ret = @number.send(name, *args, &blk)
59
+ ret.is_a?(Numeric) ? ReaderExtender.new(ret, @hours) : ret
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -6,11 +6,11 @@ module Mongoid #:nodoc:
6
6
 
7
7
  # Access methods
8
8
  def today
9
- data_for(Date.today)
9
+ whole_data_for(Time.now)
10
10
  end
11
11
 
12
12
  def yesterday
13
- data_for(Date.today - 1)
13
+ whole_data_for(Time.now - 1.day)
14
14
  end
15
15
 
16
16
  def first_value
@@ -20,17 +20,16 @@ module Mongoid #:nodoc:
20
20
  def last_value
21
21
  data_for(last_date)
22
22
  end
23
-
23
+
24
24
  def last_days(how_much = 7)
25
25
  return [today] unless how_much > 0
26
- date, values = Date.today, []
27
- (date - how_much.abs + 1).step(date) {|d| values << data_for(d) }
28
- values
26
+ now, hmd = Time.now, (how_much - 1)
27
+ on( now.ago(hmd.days)..now )
29
28
  end
30
29
 
31
30
  def on(date)
32
- return date.collect {|d| data_for(d)} if date.is_a?(Range)
33
- data_for(date)
31
+ return date.collect {|d| whole_data_for(d)} if date.is_a?(Range)
32
+ whole_data_for(date)
34
33
  end
35
34
 
36
35
  def all_values
@@ -39,25 +38,38 @@ module Mongoid #:nodoc:
39
38
 
40
39
  # Utility methods
41
40
  def first_date
42
- # We are guaranteed _m and _d to exists unless @data is a malformed
43
- # hash, so we need to do this nasty "return nil", sorry...
44
- # TODO: I'm open to change this to a cleaner algorithm :-)
45
- return nil unless _y = @data.keys.min
46
- return nil unless _m = @data[_y].keys.min
47
- return nil unless _d = @data[_y][_m].keys.min
48
- Date.new(_y.to_i, _m.to_i, _d.to_i)
49
- end
50
-
41
+ date_cleanup
42
+ return nil unless _ts = @data.keys.min
43
+ return nil unless _h = @data[_ts].keys.min
44
+ Time.from_key(_ts, _h)
45
+ end
46
+
51
47
  def last_date
52
- # We are guaranteed _m and _d to exists unless @data is a malformed
53
- # hash, so we need to do this nasty "return nil", sorry...
54
- # TODO: I'm open to change this to a cleaner algorithm :-)
55
- return nil unless _y = @data.keys.max
56
- return nil unless _m = @data[_y].keys.max
57
- return nil unless _d = @data[_y][_m].keys.max
58
- Date.new(_y.to_i, _m.to_i, _d.to_i)
48
+ date_cleanup
49
+ return nil unless _ts = @data.keys.max
50
+ return nil unless _h = @data[_ts].keys.max
51
+ Time.from_key(_ts, _h)
59
52
  end
60
53
 
54
+ # We need the cleanup method only for methods who rely on date indexes
55
+ # to be valid (well formed) like first/last_date. This is because
56
+ # Mongo update operations cleans up the last key, which in our case
57
+ # left the array in an inconsistent state.
58
+ #
59
+ # Example:
60
+ # Before update:
61
+ #
62
+ # { :visits_data => {"14803" => {"22" => 1} } }
63
+ #
64
+ # After updating with: {"$unset"=>{"visits_data.14803.22"=>1}
65
+ #
66
+ # { :visits_data => {"14803" => {} } }
67
+ #
68
+ # We can NOT retrieve the first date with visits_data.keys.min
69
+ #
70
+ def date_cleanup
71
+ @data.reject! {|k,v| v.count == 0}
72
+ end
61
73
  end
62
74
  end
63
75
  end
@@ -34,7 +34,7 @@ module Mongoid #:nodoc:
34
34
  end
35
35
 
36
36
  # Update methods
37
- def add(how_much = 1, date = Date.today)
37
+ def add(how_much = 1, date = Time.now)
38
38
  raise Errors::ModelNotSaved, "Can't update a new record. Save first!" if @owner.new_record?
39
39
  return if how_much == 0
40
40
 
@@ -64,15 +64,15 @@ module Mongoid #:nodoc:
64
64
  end
65
65
  end
66
66
 
67
- def inc(date = Date.today)
67
+ def inc(date = Time.now)
68
68
  add(1, date)
69
69
  end
70
70
 
71
- def dec(date = Date.today)
71
+ def dec(date = Time.now)
72
72
  add(-1, date)
73
73
  end
74
74
 
75
- def set(how_much, date = Date.today)
75
+ def set(how_much, date = Time.now)
76
76
  raise Errors::ModelNotSaved, "Can't update a new record" if @owner.new_record?
77
77
  update_data(how_much, date)
78
78
  @owner.collection.update(
@@ -92,7 +92,7 @@ module Mongoid #:nodoc:
92
92
  end
93
93
  end
94
94
 
95
- def reset(how_much, date = Date.today)
95
+ def reset(how_much, date = Time.now)
96
96
  return erase(date) if how_much.nil?
97
97
 
98
98
  # First, we use the default "set" for the tracking field
@@ -111,11 +111,10 @@ module Mongoid #:nodoc:
111
111
  end
112
112
  end
113
113
 
114
- def erase(date = Date.today)
114
+ def erase(date = Time.now)
115
115
  raise Errors::ModelNotSaved, "Can't update a new record" if @owner.new_record?
116
116
 
117
- # For the in memory data, we just need to set it to nil
118
- update_data(nil, date)
117
+ remove_data(date)
119
118
  @owner.collection.update(
120
119
  @owner._selector,
121
120
  { "$unset" => update_hash(1, date) },
@@ -137,35 +136,58 @@ module Mongoid #:nodoc:
137
136
 
138
137
  private
139
138
  def data_for(date)
140
- return nil if date.nil?
141
- date = normalize_date(date)
142
- @data.try(:[], date.year.to_s).try(:[], date.month.to_s).try(:[], date.day.to_s) || 0
139
+ unless date.nil?
140
+ date = normalize_date(date)
141
+ @data.try(:[], date.to_i_timestamp.to_s).try(:[], date.to_i_hour.to_s) || 0
142
+ end
143
+ end
144
+
145
+ def whole_data_for(date)
146
+ unless date.nil?
147
+ date = normalize_date(date)
148
+ h = date.whole_day.hour_collect {|d| data_for(d)}
149
+ ReaderExtender.new(h.sum, h)
150
+ end
143
151
  end
144
152
 
145
153
  def update_data(value, date)
146
- return nil if date.nil?
147
- date = normalize_date(date)
148
- [:year, :month].inject(@data) { |data, period|
149
- data[date.send(period).to_s] ||= {}
150
- }
151
- @data[date.year.to_s][date.month.to_s][date.day.to_s] = value
154
+ unless date.nil?
155
+ return remove_data(date) unless value
156
+ date = normalize_date(date)
157
+ dk, hk = date.to_i_timestamp.to_s, date.to_i_hour.to_s
158
+ unless ts = @data[dk]
159
+ ts = (@data[dk] = {})
160
+ end
161
+ ts[hk] = value
162
+ end
152
163
  end
153
164
 
154
- def year_literal(d); "#{d.year}"; end
155
- def month_literal(d); "#{d.year}.#{d.month}"; end
156
- def date_literal(d); "#{d.year}.#{d.month}.#{d.day}"; end
165
+ def remove_data(date)
166
+ unless date.nil?
167
+ date = normalize_date(date)
168
+ dk, hk = date.to_i_timestamp.to_s, date.to_i_hour.to_s
169
+ if ts = @data[dk]
170
+ ts.delete(hk)
171
+ unless ts.count > 0
172
+ @data.delete(dk)
173
+ end
174
+ end
175
+ end
176
+ end
157
177
 
158
178
  def update_hash(num, date)
159
179
  date = normalize_date(date)
160
180
  {
161
- "#{@for_data}.#{date_literal(date)}" => num
181
+ "#{@for_data}.#{date.to_key}" => num
162
182
  }
163
183
  end
164
184
 
165
185
  def normalize_date(date)
166
186
  case date
167
187
  when String
168
- Date.parse(date)
188
+ Time.parse(date)
189
+ when Date
190
+ date.to_time
169
191
  else
170
192
  date
171
193
  end
@@ -12,6 +12,7 @@ module Mongoid #:nodoc:
12
12
  @accessor = @owner.class.send(:internal_accessor_name, @token)
13
13
  @selector = {:ns => @token}
14
14
  @selector.merge!(:key => @key) if @key
15
+
15
16
  @criteria = @owner.send(@accessor).where(@selector)
16
17
  end
17
18
 
@@ -40,8 +40,11 @@ module Mongoid #:nodoc:
40
40
  # an index for the internal tracking field.
41
41
  def set_tracking_field(name)
42
42
  field internal_track_name(name), :type => Hash # , :default => {}
43
- # Should we make an index for this field?
44
- index internal_track_name(name)
43
+
44
+ # DONT make an index for this field. MongoDB indexes have limited
45
+ # size and seems that this is not a good target for indexing.
46
+ # index internal_track_name(name)
47
+
45
48
  tracked_fields << name
46
49
  end
47
50
 
data/lib/trackoid.rb CHANGED
@@ -1,8 +1,11 @@
1
+ # encoding: utf-8
1
2
  require 'rubygems'
2
3
 
3
4
  gem "mongoid", ">= 1.9.0"
4
5
 
5
6
  require 'trackoid/errors'
7
+ require 'trackoid/core_ext'
8
+ require 'trackoid/reader_extender'
6
9
  require 'trackoid/readers'
7
10
  require 'trackoid/tracker'
8
11
  require 'trackoid/aggregates'
@@ -84,6 +84,16 @@ describe Mongoid::Tracking::Aggregates do
84
84
  }.should raise_error Mongoid::Errors::AggregationAlreadyDefined
85
85
  end
86
86
 
87
+ it "should raise error if we try to use 'hours' as aggregate" do
88
+ lambda {
89
+ class TestModel
90
+ aggregate :hours do
91
+ "(none)"
92
+ end
93
+ end
94
+ }.should raise_error Mongoid::Errors::AggregationNameDeprecated
95
+ end
96
+
87
97
  it "should have Mongoid accessors defined" do
88
98
  tm = TestModel.create(:name => "Dummy")
89
99
  tm.send(tm.class.send(:internal_accessor_name, "browsers")).class.should == Mongoid::Criteria
@@ -311,7 +321,6 @@ describe Mongoid::Tracking::Aggregates do
311
321
 
312
322
  it "should delete the values when using nil" do
313
323
  @mock.visits.reset(nil, "2010-07-14")
314
-
315
324
  @mock.visits.on("2010-07-14").should == 0
316
325
  @mock.visits.browsers.all_values.should =~ [
317
326
  ["mozilla", [1]],
@@ -401,17 +410,17 @@ describe Mongoid::Tracking::Aggregates do
401
410
 
402
411
  it "should return the correct first_date for every aggregate" do
403
412
  @mock.visits.browsers.first_date.should =~ [
404
- ["mozilla", Date.parse("2010-07-11")],
405
- ["google", Date.parse("2010-07-12")],
406
- ["internet", Date.parse("2010-07-13")]
413
+ ["mozilla", Time.parse("2010-07-11")],
414
+ ["google", Time.parse("2010-07-12")],
415
+ ["internet", Time.parse("2010-07-13")]
407
416
  ]
408
417
  end
409
418
 
410
419
  it "should return the correct last_date for every aggregate" do
411
420
  @mock.visits.browsers.last_date.should =~ [
412
- ["mozilla", Date.parse("2010-07-14")],
413
- ["google", Date.parse("2010-07-15")],
414
- ["internet", Date.parse("2010-07-16")]
421
+ ["mozilla", Time.parse("2010-07-14")],
422
+ ["google", Time.parse("2010-07-15")],
423
+ ["internet", Time.parse("2010-07-16")]
415
424
  ]
416
425
  end
417
426