trackoid 0.3.8 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ Gemfile.lock
21
+
22
+ ## PROJECT::SPECIFIC
data/Gemfile CHANGED
@@ -1,10 +1,6 @@
1
- source 'http://rubygems.org'
1
+ source "http://rubygems.org"
2
2
 
3
- gem 'mongoid', '>= 2.1.0'
4
-
5
- group :development do
6
- gem 'rake'
7
- gem 'jeweler'
8
- gem 'rspec', '>= 2.2.0'
9
- gem 'mocha', '0.11.0'
10
- end
3
+ # Declare your gem's dependencies in trackoid.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
@@ -1,23 +1,31 @@
1
- = trackoid
1
+ # Trackoid
2
2
 
3
3
  Trackoid is an analytics tracking system made specifically for MongoDB using Mongoid as ORM.
4
4
 
5
- = *** IMPORTANT ***
5
+ ## IMPORTANT upgrade information
6
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.
7
+ **Trackoid Version 0.4.0** is updated to work with Mongoid 3. It's NOT backwards compatible with any previous version of Mongoid. A dependency on Ruby 1.9.x has also been added.
8
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.
9
+ **Trackoid Version 0.3.0** changes the internal representation of tracking data. So **YOU WILL NOT SEE PREVIOUS DATA** when you update.
10
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.
11
+ Hopefully, due to the magic of MongoDB, data is **NOT LOST**. In fact it's never lost unless you delete it. :-) _Just it's not visible right away_.
12
12
 
13
+ See **Changes for TZ support** below for an explanation of changes in the internal representation of tracked data.
13
14
 
14
- = Requirements
15
+ ### Should I 'lock' the Trackoid version with Bundler?
16
+
17
+ If you are new to Trackoid and don't know what I'm talking about **you're safe** upgrading from 0.4.x onwards.
18
+
19
+ If you are having problems with Ruby 1.9.3 or not using Mongoid 3.x, probably you'll want to lock on 0.3.x, since 0.4.x *requires Mongoid 3.x** and hence, requires Ruby 1.9.3+.
20
+
21
+
22
+ # Requirements
15
23
 
16
24
  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.
17
25
 
18
26
  Please feel free to fork and port to other libraries. However, Trackoid really requires MongoDB since it is build from scratch to take advantage of several MongoDB features (please let me know if you dare enough to port Trackoid into CouchDB or similar, I will be glad to know).
19
27
 
20
- = Using Trackoid to track analytics information for models
28
+ # Using Trackoid to track analytics information for models
21
29
 
22
30
  Given the most obvious use for Trackoid, consider this example:
23
31
 
@@ -29,12 +37,12 @@ Given the most obvious use for Trackoid, consider this example:
29
37
 
30
38
  track :visits
31
39
  end
32
-
40
+
33
41
  This class models a web page, and by using `track :visits` we add a `visits` field to track... well... visits. :-) Later, in out controller we can do:
34
42
 
35
43
  def view
36
44
  @page = WebPage.find(params[:webpage_id])
37
-
45
+
38
46
  @page.visits.inc # Increment a visit to this page
39
47
  end
40
48
 
@@ -52,11 +60,11 @@ Of course, you can also show visits in a time range:
52
60
  <% end %>
53
61
  </ul>
54
62
 
55
- == Not only visits...
63
+ ## Not only visits...
56
64
 
57
65
  Of course, you can use Trackoid to track all actions who require numeric analytics in a date frame.
58
66
 
59
- === Prevent login to a control panel with a maximum login attemps
67
+ ### Prevent login to a control panel with a maximum login attemps
60
68
 
61
69
  You can track invalid logins so you can prevent login for a user when certain invalid login had been made. Imagine your login controller:
62
70
 
@@ -64,14 +72,14 @@ You can track invalid logins so you can prevent login for a user when certain in
64
72
  class User
65
73
  include Mongoid::Document
66
74
  include Mongoid::Tracking
67
-
75
+
68
76
  track :failed_logins
69
77
  end
70
78
 
71
79
  # User controller
72
80
  def login
73
81
  user = User.find(params[:email])
74
-
82
+
75
83
  # Stop login if failed attemps > 3
76
84
  redirect(root_path) if user.failed_logins.today > 3
77
85
 
@@ -92,7 +100,7 @@ Note that additionally you have the full failed login history for free. :-)
92
100
  @user.failed_logins.this_month
93
101
 
94
102
 
95
- === Automatically saving a history of document changes
103
+ ### Automatically saving a history of document changes
96
104
 
97
105
  You can combine Trackoid with the power of callbacks to automatically track certain operations, for example modification of a document. This way you have a history of document changes.
98
106
 
@@ -112,7 +120,7 @@ You can combine Trackoid with the power of callbacks to automatically track cert
112
120
  end
113
121
 
114
122
 
115
- === Track temperature history for a nuclear plant
123
+ ### Track temperature history for a nuclear plant
116
124
 
117
125
  Imagine you need a web service to track the temperature of all rooms of a nuclear plant. Now you have a simple method to do this:
118
126
 
@@ -120,7 +128,7 @@ Imagine you need a web service to track the temperature of all rooms of a nuclea
120
128
  class Room
121
129
  include Mongoid::Document
122
130
  include Mongoid::Tracking
123
-
131
+
124
132
  track :temperature
125
133
  end
126
134
 
@@ -128,7 +136,7 @@ Imagine you need a web service to track the temperature of all rooms of a nuclea
128
136
  # Temperature controller
129
137
  def set_temperature_for_room
130
138
  @room = Room.find(params[:room_number])
131
-
139
+
132
140
  @room.temperature.set(current_temperature)
133
141
  end
134
142
 
@@ -137,12 +145,12 @@ So, you are not restricted into incrementing or decrementing a value, you can al
137
145
  @room.temperature.last_days(30).max
138
146
 
139
147
 
140
- = How does it works?
148
+ # How does it works?
141
149
 
142
150
  Trakoid works by embedding date tracking information into the models. The date tracking information is limited by a granularity of days, but you can use aggregates if you absolutely need hour or minutes granularity.
143
151
 
144
152
 
145
- == Scalability and performance
153
+ ## Scalability and performance
146
154
 
147
155
  Trackoid is made from the ground up to take advantage of the great scalability features of MongoDB. Trackoid uses "upsert" operations, bypassing Mongoid controllers so that it can be used in a distributed system without data loss. This is perfect for a cloud hosted SaaS application!
148
156
 
@@ -168,12 +176,12 @@ This way, the collection can receive multiple incremental operations without req
168
176
 
169
177
  In practice, we don't need visits information so fine grained, but it's good to take this into account.
170
178
 
171
- == Embedding tracking information into models
179
+ ## Embedding tracking information into models
172
180
 
173
181
  Tracking analytics data in SQL databases was historicaly saved into her own table, perhaps called `site_visits` with a relation to the sites table and each row saving an integer for each day.
174
182
 
175
183
  Table "site_visits"
176
-
184
+
177
185
  SiteID Date Visits
178
186
  ------ ---------- ------
179
187
  1234 2010-05-01 34
@@ -184,7 +192,7 @@ With this schema, it's easy to get visits for a website using single SQL stateme
184
192
 
185
193
  Trackoid uses an embedding approach to tackle this. For the above examples, Trackoid would embedd a ruby Hash into the Site model. This means the tracking information is already saved "inside" the Site, and we don't have to reach the database for any date querying! Moreover, since the data retrieved with the accessor methods like "last_days", "this_month" and the like, are already arrays, we could use Array methods like sum, count, max, min, etc...
186
194
 
187
- == Memory implications
195
+ ## Memory implications
188
196
 
189
197
  Since storing all tracking information with the model implies we add additional information that can grow, and grow, and grow... You can be wondering yourself if this is a good idea. Yes, it's is, or at least I think so. Let me convice you...
190
198
 
@@ -195,7 +203,7 @@ A year full of statistical data takes only 2.8Kb, if you store integers. If your
195
203
  For comparison, this README is already 8.5Kb in size...
196
204
 
197
205
 
198
- = Changes for TZ support
206
+ # Changes for TZ support
199
207
 
200
208
  Well, this is the time (no pun intended) to add TZ support to Trackoid.
201
209
 
@@ -203,7 +211,7 @@ The problem is that "today" is not the same "today" for everyone, so unless you
203
211
 
204
212
  But... Okay, given the fact that "today" is not the same "today" for everyone, this is the brand new Trackoid, with TZ support.
205
213
 
206
- == What has changed?
214
+ ## What has changed?
207
215
 
208
216
  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
217
 
@@ -275,7 +283,7 @@ The contents of every "day record" is another hash with 24 keys, one for each ho
275
283
  { :upsert => true, :safe => false }
276
284
  )
277
285
 
278
- == What "today" is it?
286
+ ## What "today" is it?
279
287
 
280
288
  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
289
 
@@ -292,11 +300,11 @@ Example: I live in GMT+0200 (Daylight saving in effect, or summer time), then if
292
300
  "02" : 30,
293
301
  ""
294
302
  }
295
-
303
+
296
304
  This is a more graphical representation:
297
305
 
298
306
  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
- ------ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
307
+ ------ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
300
308
  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
309
  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
310
  Shift up ---> 2 hours.
@@ -305,7 +313,7 @@ This is a more graphical representation:
305
313
  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
314
 
307
315
 
308
- == How should I tell Trackoid how TZ to use?
316
+ ## How should I tell Trackoid how TZ to use?
309
317
 
310
318
  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
319
 
@@ -313,7 +321,8 @@ Trackoid will correctly translate dates for you (hopefully) if you pass a date t
313
321
 
314
322
 
315
323
 
316
- = Revision History
324
+ # Revision History
325
+ 0.3.8 - Fixed support for Ruby 1.9.3
317
326
 
318
327
  0.3.7 - Fixed support for Rails 3.1 and Mongoid 2.2
319
328
 
@@ -341,7 +350,7 @@ Trackoid will correctly translate dates for you (hopefully) if you pass a date t
341
350
 
342
351
  - Renamed the internal field of ReaderExtender to "total" so that
343
352
  converting to json automatically gives you:
344
-
353
+
345
354
  {
346
355
  "total": <total value>
347
356
  "hours": [<hours array>]
@@ -356,13 +365,13 @@ Trackoid will correctly translate dates for you (hopefully) if you pass a date t
356
365
  * Reset does the same as "set" but also sets aggregate fields.
357
366
 
358
367
  Example:
359
-
368
+
360
369
  A) model.value(aggregate_data).set(5)
361
370
  B) model.value(aggregate_data).reset(5)
362
-
371
+
363
372
  A will set "5" to the 'value' and to the aggregate.
364
373
  B will set "5" to the 'value' and all aggregates.
365
-
374
+
366
375
  * Erase resets the values in the mongo database. Note that this
367
376
  is completely different of doing 'reset(0)'. (With erase you
368
377
  can actually recall space from the database).
@@ -416,4 +425,3 @@ Trackoid will correctly translate dates for you (hopefully) if you pass a date t
416
425
 
417
426
  0.1.5 - Added support for namespaced models and aggregations
418
427
  - Enabled "set" operations on aggregates
419
-
data/Rakefile CHANGED
@@ -1,40 +1,2 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "trackoid"
8
- gem.summary = %Q{Trackoid is an easy scalable analytics tracker using MongoDB and Mongoid}
9
- gem.description = %Q{Trackoid uses an embeddable approach to track analytics data using the poweful features of MongoDB for scalability}
10
- gem.email = "josemiguel@perezruiz.com"
11
- gem.homepage = "http://github.com/twoixter/trackoid"
12
- gem.authors = ["Jose Miguel Perez"]
13
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
- end
15
- Jeweler::GemcutterTasks.new
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
- end
19
-
20
- require 'rspec/core/rake_task'
21
- RSpec::Core::RakeTask.new(:spec) do |spec|
22
- spec.pattern = 'spec/**/*_spec.rb'
23
- end
24
-
25
- RSpec::Core::RakeTask.new(:rcov) do |spec|
26
- spec.pattern = 'spec/**/*_spec.rb'
27
- spec.rcov = true
28
- end
29
-
30
- task :default => :spec
31
-
32
- require 'rake/rdoctask'
33
- Rake::RDocTask.new do |rdoc|
34
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
35
-
36
- rdoc.rdoc_dir = 'rdoc'
37
- rdoc.title = "trackoid #{version}"
38
- rdoc.rdoc_files.include('README*')
39
- rdoc.rdoc_files.include('lib/**/*.rb')
40
- end
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,8 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: trackoid_test
5
+ hosts:
6
+ - 127.0.0.1:27017
7
+ options:
8
+ allow_dynamic_fields: false
@@ -10,13 +10,13 @@ module Mongoid #:nodoc:
10
10
  unless self.ancestors.include? Mongoid::Document
11
11
  raise Errors::NotMongoid, "Must be included in a Mongoid::Document"
12
12
  end
13
-
13
+
14
14
  include Aggregates
15
15
  extend ClassMethods
16
-
16
+
17
17
  class_attribute :tracked_fields
18
18
  self.tracked_fields = []
19
- delegate :tracked_fields, :internal_track_name, :to => "self.class"
19
+ delegate :tracked_fields, :internal_track_name, to: "self.class"
20
20
  end
21
21
  end
22
22
 
@@ -41,15 +41,12 @@ module Mongoid #:nodoc:
41
41
  # Configures the internal fields for tracking. Additionally also creates
42
42
  # an index for the internal tracking field.
43
43
  def set_tracking_field(name)
44
- field internal_track_name(name), :type => Hash # , :default => {}
45
-
46
44
  # DONT make an index for this field. MongoDB indexes have limited
47
45
  # size and seems that this is not a good target for indexing.
48
46
  # index internal_track_name(name)
49
-
50
47
  tracked_fields << name
51
48
  end
52
-
49
+
53
50
  # Creates the tracking field accessor and also disables the original
54
51
  # ones from Mongoid. Hidding here the original accessors for the
55
52
  # Mongoid fields ensures they doesn't get dirty, so Mongoid does not
@@ -58,29 +55,13 @@ module Mongoid #:nodoc:
58
55
  define_method(name) do |*aggr|
59
56
  Tracker.new(self, name, aggr)
60
57
  end
61
-
62
- # Should we just "undef" this methods?
63
- # They override the ones defined from Mongoid
64
- define_method("#{name}_data") do
65
- raise NoMethodError
66
- end
67
-
68
- define_method("#{name}_data=") do |m|
69
- raise NoMethodError
70
- end
71
-
72
- # I think it's important to override also the #{name}_changed? so
73
- # as to be sure Mongoid never mark this field as dirty.
74
- define_method("#{name}_changed?") do
75
- false
76
- end
77
58
  end
78
-
59
+
79
60
  # Updates the aggregated class for it to include a new tracking field
80
61
  def update_aggregates(name)
81
62
  aggregate_klass.track name
82
63
  end
83
-
64
+
84
65
  end
85
66
 
86
67
  end
@@ -14,7 +14,7 @@ module Mongoid #:nodoc:
14
14
  self.aggregate_fields = {}
15
15
  self.aggregate_klass = nil
16
16
  delegate :aggregate_fields, :aggregate_klass, :aggregated?,
17
- :to => "self.class"
17
+ to: "self.class"
18
18
  end
19
19
  end
20
20
 
@@ -62,7 +62,7 @@ module Mongoid #:nodoc:
62
62
  raise Errors::AggregationNameDeprecated.new(name) if DEPRECATED_TOKENS.include? name.to_s
63
63
 
64
64
  define_aggregate_model if aggregate_klass.nil?
65
- has_many_related internal_accessor_name(name), :class_name => aggregate_klass.to_s
65
+ has_many internal_accessor_name(name), class_name: aggregate_klass.to_s
66
66
  add_aggregate_field(name, block)
67
67
  create_aggregation_accessors(name)
68
68
  end
@@ -90,32 +90,34 @@ module Mongoid #:nodoc:
90
90
  end
91
91
 
92
92
  parent = self
93
+
93
94
  define_klass do
94
95
  include Mongoid::Document
95
96
  include Mongoid::Tracking
96
97
 
97
98
  # Make the relation to the original class
98
- belongs_to_related parent.name.demodulize.underscore.to_sym, :class_name => parent.name
99
+ belongs_to parent.name.demodulize.underscore.to_sym, class_name: parent.name
99
100
 
100
101
  # Internal fields to track aggregation token and keys
101
- field :ns, :type => String
102
- field :key, :type => String
103
- index [[parent.name.foreign_key.to_sym, Mongo::ASCENDING],
104
- [:ns, Mongo::ASCENDING],
105
- [:key, Mongo::ASCENDING]],
106
- :unique => true, :background => true
102
+ field :ns, type: String
103
+ field :key, type: String
104
+
105
+ index({
106
+ parent.name.foreign_key.to_sym => 1,
107
+ ns: 1,
108
+ key: 1
109
+ }, { unique: true, background: true })
107
110
 
108
111
  # Include parent tracking data.
109
- parent.tracked_fields.each {|track_field| track track_field }
112
+ parent.tracked_fields.each { |track_field| track track_field }
110
113
  end
114
+
111
115
  self.aggregate_klass = internal_aggregates_name.constantize
112
116
  end
113
117
 
114
118
  # Returns true if there is a class defined with the same name as our
115
119
  # aggregate class.
116
120
  def foreign_class_defined?
117
- # The following construct doesn't work with namespaced constants.
118
- # Object.const_defined?(internal_aggregates_name.to_sym)
119
121
  internal_aggregates_name.constantize && true
120
122
  rescue NameError
121
123
  false
@@ -132,7 +134,9 @@ module Mongoid #:nodoc:
132
134
  def define_klass(&block)
133
135
  scope = internal_aggregates_name.split('::')
134
136
  klass = scope.pop
135
- scope = scope.inject(Object) {|scope, const_name| scope.const_get(const_name)}
137
+ scope = scope.inject(Object) do |scope, const_name|
138
+ scope.const_get(const_name)
139
+ end
136
140
  klass = scope.const_set(klass, Class.new)
137
141
  klass.class_eval(&block)
138
142
  end