trackoid 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
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
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jose Miguel Perez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,185 @@
1
+ = trackoid
2
+
3
+ Trackoid is an analytics tracking system made specifically for MongoDB using Mongoid as ORM.
4
+
5
+ = Requirements
6
+
7
+ 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.
8
+
9
+ 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).
10
+
11
+ = Using Trackoid to track analytics information for models
12
+
13
+ Given the most obvious use for Trackoid, consider this example:
14
+
15
+ Class WebPage
16
+ include Mongoid::Document
17
+ include Mongoid::Tracking
18
+
19
+ ...
20
+
21
+ track :visits
22
+ end
23
+
24
+ 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:
25
+
26
+ def view
27
+ @page = WebPage.find(params[:webpage_id])
28
+
29
+ @page.visits.inc # Increment a visit to this page
30
+ end
31
+
32
+ That is, dead simple. Later in our views we can use the `visits` field to show the visit information to the users:
33
+
34
+ <h1><%= @page.visits.today %> visits to this page today</h1>
35
+ <p>The page had <%= @page.visits.yesterday %> visits yesterday</p>
36
+
37
+ Of course, you can also show visits in a time range:
38
+
39
+ <h1>Visits on last 7 days</h1>
40
+ <ul>
41
+ <% @page.visits.last_days(7).reverse.each_with_index do |i,d| %>
42
+ <li><%= (DateTime.now - i).to_s %> : <%= d %></li>
43
+ <% end %>
44
+ </ul>
45
+
46
+ == Not only visits...
47
+
48
+ Of course, you can use Trackoid to track all actions who require numeric analytics in a date frame.
49
+
50
+ === Prevent login to a control panel with a maximum login attemps
51
+
52
+ You can track invalid logins so you can prevent login for a user when certain invalid login had been made. Imagine your login controller:
53
+
54
+ # User model
55
+ class User
56
+ include Mongoid::Document
57
+ include Mongoid::Tracking
58
+
59
+ track :failed_logins
60
+ end
61
+
62
+ # User controller
63
+ def login
64
+ user = User.find(params[:email])
65
+
66
+ # Stop login if failed attemps > 3
67
+ redirect(root_path) if user.failed_logins.today > 3
68
+
69
+ # Continue with the normal login steps
70
+ if user.authenticate(params[:password])
71
+ redirect_back_or_default(root_path)
72
+ else
73
+ user.failed_logins.inc
74
+ end
75
+ end
76
+
77
+ Note that additionally you have the full failed login history for free. :-)
78
+
79
+ # All failed login attemps, ever.
80
+ @user.failed_logins.sum
81
+
82
+ # Failed logins this month.
83
+ @user.failed_logins.this_month
84
+
85
+
86
+ === Automatically saving a history of document changes
87
+
88
+ 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.
89
+
90
+ class User
91
+ include Mongoid::Document
92
+ include Mongoid::Tracking
93
+
94
+ field :name
95
+ track :changes
96
+
97
+ after_update :track_changes
98
+
99
+ protected
100
+ def track_changes
101
+ self.changes.inc
102
+ end
103
+ end
104
+
105
+
106
+ === Track temperature history for a nuclear plant
107
+
108
+ 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:
109
+
110
+ # Room temperature
111
+ class Room
112
+ include Mongoid::Document
113
+ include Mongoid::Tracking
114
+
115
+ track :temperature
116
+
117
+ end
118
+
119
+
120
+ # Temperature controller
121
+ def set_temperature_for_room
122
+ @room = Room.find(params[:room_number])
123
+
124
+ @room.temperature.set(current_temperature)
125
+ end
126
+
127
+ So, you don't need only to increment or decrement a value, yuo can also set an specific value. Now it's easy to know the maximum temperature of the last 30 days for a room:
128
+
129
+ @room.temperature.last_days(30).max
130
+
131
+
132
+ = How does it works?
133
+
134
+ Trakoid works by embedding date tracking information into models. The date tracking information is limited by a granularity of days for now. As the project evolves and we test performance, my idea is to add finer granularity of hours and perhaps, minutes.
135
+
136
+ == Scalability and performance
137
+
138
+ 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 loses. This is perfect for a cloud application.
139
+
140
+ The problem with a distributed system for tracking analytical information is the atomicity of operations. Imagine you must increment visits information from several servers at the same time and how you would do it. With an SQL model, this is somewhat easy because the tradittional approaches for doing this only require INSERT or UPDATE operations that are atomic by nature. But for a Document Oriented Database like MongoDB you need some kind of special operations. MongoDB uses "upsert" commands, which stands for "update or insert". That is, modify this and create if not exists.
141
+
142
+ The problem with Mongoid, and with all other ORM for that matter, is that they are not made with those operations in mind. If you store an Array or Hash into a Mongoid document, you read or save it as a whole, you can not increment or store only a value without reading/writting the full Array.
143
+
144
+ Trackoid issues "upsert" commands directly to the MongoDB driver, with the following structure:
145
+
146
+
147
+ collection.update( {_id:ObjectID}, {$inc: {visits.2010.05.30: 1} }, true )
148
+
149
+ This way, the collection can receive multiple incremental operations without requiring additional logic for locking or something. The only drawback is that you will not have realtime data in your model. For example:
150
+
151
+ v = @page.visits.today # v is now "5" if there was 5 visits today
152
+ @page.visits.inc # Increment visits today
153
+ @page.visits.today == v+1 # Visits is now incremented in our local copy
154
+ # of the object, but we need to reload for it
155
+ # to reflect the realtime visits to the page
156
+ # since there could be another processes
157
+ # updating visits
158
+
159
+ In practice, we don't need visits information so fine grained, but it's good to take this into account.
160
+
161
+ == Embedding tracking information into models
162
+
163
+ 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.
164
+
165
+ Table "site_visits"
166
+
167
+ SiteID Date Visits
168
+ ------ ---------- ------
169
+ 1234 2010-05-01 34
170
+ 1234 2010-05-02 25
171
+ 1234 2010-05-03 45
172
+
173
+ With this schema, it's easy to get visits for a website using single SQL statements. However, for complex queries this can be easily become cumbersome. Also this doesn't work so well for systems using a generic SQL DSL like ActiveRecord since for really taking advantage of some queries you need to use SQL language directly, one option that isn't neither really interesting nor available.
174
+
175
+ 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...
176
+
177
+ == Memory implications
178
+
179
+ 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...
180
+
181
+ MongoDB stores information in BSON format as a binary representation of a JSON structure. So, BSON stores integers like integers, not like string representations of ASCII characters. This is important to calculate the space used for analytic information.
182
+
183
+ A year full of statistical data takes only 2.8Kb, if you store integers. If your statistical data includes floats, a year full of information takes 4.3Kb. I said "a year full of data" because Trackoid does not store information for days without data.
184
+
185
+ For comparison, this README is already 8.5Kb in size.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
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.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "trackoid #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Tracking
4
+ # This internal class handles all interaction for a track field.
5
+ class Tracker
6
+ def initialize(owner, field)
7
+ @owner, @for = owner, field
8
+ @data = @owner.read_attribute(@for)
9
+ end
10
+
11
+ # Update methods
12
+ def add(how_much = 1, date = DateTime.now)
13
+ raise "Can not update a recently created object" if @owner.new_record?
14
+
15
+ update_data(data_for(date) + how_much, date)
16
+ @owner.collection.update( @owner._selector,
17
+ { (how_much > 0 ? "$inc" : "$dec") => update_hash(how_much.abs, date) },
18
+ :upsert => true)
19
+ end
20
+
21
+ def inc(date = DateTime.now)
22
+ add(1, date)
23
+ end
24
+
25
+ def dec(date = DateTime.now)
26
+ add(-1, date)
27
+ end
28
+
29
+ def set(how_much, date = DateTime.now)
30
+ raise "Can not update a recently created object" if @owner.new_record?
31
+
32
+ update_data(how_much, date)
33
+ @owner.collection.update( @owner._selector,
34
+ { "$set" => update_hash(how_much, date) },
35
+ :upsert => true)
36
+ end
37
+
38
+ # Access methods
39
+ def today
40
+ data_for(Date.today)
41
+ end
42
+
43
+ def yesterday
44
+ data_for(Date.today - 1)
45
+ end
46
+
47
+ def last_days(how_much = 7)
48
+ return [today] unless how_much > 0
49
+
50
+ date, values = DateTime.now, []
51
+ (date - how_much.abs + 1).step(date) {|d| values << data_for(d) }
52
+ values
53
+ end
54
+
55
+ def on(date)
56
+ date = DateTime.parse(date) if date.is_a?(String)
57
+ return date.collect {|d| data_for(d)} if date.is_a?(Range)
58
+ data_for(date)
59
+ end
60
+
61
+ # Private methods
62
+ private
63
+ def data_for(date)
64
+ return 0 if @data[date.year.to_s].nil?
65
+ return 0 if @data[date.year.to_s][date.month.to_s].nil?
66
+ @data[date.year.to_s][date.month.to_s][date.day.to_s] || 0
67
+ end
68
+
69
+ def update_data(value, date)
70
+ if @data[date.year.to_s]
71
+ if @data[date.year.to_s][date.month.to_s]
72
+ @data[date.year.to_s][date.month.to_s][date.day.to_s] = value
73
+ else
74
+ @data[date.year.to_s][date.month.to_s] = {
75
+ date.day.to_s => value
76
+ }
77
+ end
78
+ else
79
+ @data[date.year.to_s] = {
80
+ date.month.to_s => {
81
+ date.day.to_s => value
82
+ }
83
+ }
84
+ end
85
+ end
86
+
87
+ def year_literal(d); "#{d.year}"; end
88
+ def month_literal(d); "#{d.year}.#{d.month}"; end
89
+ def date_literal(d); "#{d.year}.#{d.month}.#{d.day}"; end
90
+
91
+ def update_hash(num, date)
92
+ {
93
+ "#{@for}.#{date_literal(date)}" => num
94
+ }
95
+ end
96
+
97
+
98
+ # WARNING: This is +only+ for debugging pourposes (rspec y tal)
99
+ def _original_hash
100
+ @data
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require 'trackoid/tracker'
3
+
4
+ module Mongoid #:nodoc:
5
+ module Tracking
6
+ # Include this module to add analytics tracking into a +root level+ document.
7
+ # Use "track :field" to add a field named :field and an associated mongoid
8
+ # field named after :field
9
+ def self.included(base)
10
+ base.class_eval do
11
+ raise "Must be included in a Mongoid::Document" unless self.ancestors.include? Mongoid::Document
12
+ extend ClassMethods
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ # Adds analytics tracking for +name+. Adds a +'name'_data+ mongoid
18
+ # field as a Hash for tracking this information. Additionaly, makes
19
+ # this field hidden, so that the user can not mangle with the original
20
+ # field. This is necessary so that Mongoid does not "dirty" the field
21
+ # potentially overwriting the original data.
22
+ def track(name)
23
+ name_sym = "#{name}_data".to_sym
24
+ field name_sym, :type => Hash, :default => {}
25
+
26
+ # Shoul we make an index for this field?
27
+ # index name_sym
28
+
29
+ define_method("#{name}") do
30
+ Tracker.new(self, name_sym)
31
+ end
32
+
33
+ # Should we just "undef" this methods?
34
+ # They override the just defined ones from Mongoid
35
+ define_method("#{name}_data") do
36
+ raise NoMethodError
37
+ end
38
+
39
+ define_method("#{name}_data=") do
40
+ raise NoMethodError
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ end
47
+ end
data/lib/trackoid.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+
3
+ gem "mongoid", ">= 1.9.0"
4
+
5
+ require 'trackoid/tracking'
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+
5
+ gem 'mocha', '>= 0.9.8'
6
+
7
+ require 'mocha'
8
+ require 'mongoid'
9
+ require 'trackoid'
10
+ require 'spec'
11
+ require 'spec/autorun'
12
+
13
+ Mongoid.configure do |config|
14
+ name = "trackoid_test"
15
+ host = "localhost"
16
+ port = "27017"
17
+ # config.master = Mongo::Connection.new(host, port, :logger => Logger.new(STDOUT)).db(name)
18
+ config.master = Mongo::Connection.new.db(name)
19
+ end
20
+
21
+ Spec::Runner.configure do |config|
22
+ config.mock_with :mocha
23
+ config.before :suite do
24
+ Mongoid.master.collections.each(&:drop)
25
+ end
26
+ end
@@ -0,0 +1,171 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class Test
4
+ include Mongoid::Document
5
+ include Mongoid::Tracking
6
+
7
+ field :name # Dummy field
8
+ track :visits
9
+ end
10
+
11
+ describe Mongoid::Tracking do
12
+
13
+ it "should raise error when used in a class not of class Mongoid::Document" do
14
+ lambda {
15
+ class NotMongoidClass
16
+ include Mongoid::Tracking
17
+ end
18
+ }.should raise_error
19
+ end
20
+
21
+ it "should not not raise when used in a class of class Mongoid::Document" do
22
+ lambda {
23
+ class MongoidedDocument
24
+ include Mongoid::Document
25
+ include Mongoid::Tracking
26
+ end
27
+ }.should_not raise_error
28
+ end
29
+
30
+ describe "when creating a new field with stats" do
31
+
32
+ before(:all) do
33
+ @mock = Test.new
34
+ end
35
+
36
+ it "should deny access to the underlying mongoid field" do
37
+ lambda { @mock.visits_data }.should raise_error NoMethodError
38
+ lambda { @mock.visits_data = {} }.should raise_error NoMethodError
39
+ end
40
+
41
+ it "should create a method for accesing the stats" do
42
+ @mock.respond_to?(:visits).should == true
43
+ end
44
+
45
+ it "should create a method for accesing the stats of the proper class" do
46
+ @mock.visits.class.should == Mongoid::Tracking::Tracker
47
+ end
48
+
49
+ it "should not update stats when new record" do
50
+ lambda { @mock.inc }.should raise_error
51
+ end
52
+
53
+ it "shold create an empty hash as the internal representation" do
54
+ @mock.visits.send(:_original_hash).should == {}
55
+ end
56
+
57
+ it "should give 0 for today stats" do
58
+ @mock.visits.today.should == 0
59
+ end
60
+
61
+ it "should give 0 for last 7 days stats" do
62
+ @mock.visits.last_days.should == [0, 0, 0, 0, 0, 0, 0]
63
+ end
64
+
65
+ it "should give today stats for last 0 days stats" do
66
+ @mock.visits.last_days(0).should == [@mock.visits.today]
67
+ end
68
+
69
+ end
70
+
71
+ describe "when using a model in the database" do
72
+
73
+ before(:all) do
74
+ Test.delete_all
75
+ Test.create(:name => "test")
76
+ @object_id = Test.first.id
77
+ end
78
+
79
+ before do
80
+ @mock = Test.find(@object_id)
81
+ end
82
+
83
+ it "should increment visits stats for today" do
84
+ @mock.visits.inc
85
+ @mock.visits.today.should == 1
86
+ end
87
+
88
+ it "should increment another visits stats for today for a total of 2" do
89
+ @mock.visits.inc
90
+ @mock.visits.today.should == 2
91
+ end
92
+
93
+ it "should also work for yesterday" do
94
+ @mock.visits.inc(DateTime.now - 1)
95
+ @mock.visits.yesterday.should == 1
96
+ end
97
+
98
+ it "should also work for yesterday if adding another visit (for a total of 2)" do
99
+ @mock.visits.inc(DateTime.now - 1)
100
+ @mock.visits.yesterday.should == 2
101
+ end
102
+
103
+ it "then, the visits of today + yesterday must be the same" do
104
+ @mock.visits.last_days(2).should == [2, 2]
105
+ end
106
+
107
+ it "should have 4 visits for this test" do
108
+ @mock.visits.last_days(2).sum.should == 4
109
+ end
110
+
111
+ it "should correctly handle the last 7 days" do
112
+ @mock.visits.last_days.should == [0, 0, 0, 0, 0, 2, 2]
113
+ end
114
+
115
+ end
116
+
117
+ context "testing accessor operations without reloading models" do
118
+
119
+ before(:all) do
120
+ Test.delete_all
121
+ Test.create(:name => "test")
122
+ @object_id = Test.first.id
123
+ end
124
+
125
+ before do
126
+ @mock = Test.find(@object_id)
127
+ end
128
+
129
+ it "'set' operator must work" do
130
+ @mock.visits.set(5)
131
+ @mock.visits.today.should == 5
132
+ Test.find(@object_id).visits.today.should == 5
133
+ end
134
+
135
+ it "'set' operator must work on arbitrary days" do
136
+ @mock.visits.set(5, Date.parse("2010-05-01"))
137
+ @mock.visits.on(Date.parse("2010-05-01")).should == 5
138
+ Test.find(@object_id).visits.on(Date.parse("2010-05-01")).should == 5
139
+ end
140
+
141
+ it "'add' operator must work" do
142
+ @mock.visits.add(5)
143
+ @mock.visits.today.should == 10 # Remember 5 set on previous test
144
+ Test.find(@object_id).visits.today.should == 10
145
+ end
146
+
147
+ it "'add' operator must work on arbitrary days" do
148
+ @mock.visits.add(5, Date.parse("2010-05-01"))
149
+ @mock.visits.on(Date.parse("2010-05-01")).should == 10
150
+ Test.find(@object_id).visits.on(Date.parse("2010-05-01")).should == 10
151
+ end
152
+
153
+ it "on() accessor must work on dates as String" do
154
+ # We have data for today as previous tests populated the visits field
155
+ @mock.visits.on("2010-05-01").should == 10
156
+ end
157
+
158
+ it "on() accessor must work on dates as Date ancestors" do
159
+ # We have data for today as previous tests populated the visits field
160
+ @mock.visits.on(Date.parse("2010-05-01")).should == 10
161
+ end
162
+
163
+ it "on() accessor must work on dates as Ranges" do
164
+ # We have data for today as previous tests populated the visits field
165
+ @mock.visits.on(Date.parse("2010-04-30")..Date.parse("2010-05-02")).should == [0, 10, 0]
166
+ end
167
+
168
+
169
+ end
170
+
171
+ end
data/trackoid.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{trackoid}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jose Miguel Perez"]
12
+ s.date = %q{2010-05-30}
13
+ s.description = %q{Trackoid uses an embeddable approach to track analytics data using the poweful features of MongoDB for scalability}
14
+ s.email = %q{josemiguel@perezruiz.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/trackoid.rb",
27
+ "lib/trackoid/tracker.rb",
28
+ "lib/trackoid/tracking.rb",
29
+ "spec/spec.opts",
30
+ "spec/spec_helper.rb",
31
+ "spec/trackoid_spec.rb",
32
+ "trackoid.gemspec"
33
+ ]
34
+ s.homepage = %q{http://github.com/twoixter/trackoid}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.7}
38
+ s.summary = %q{Trackoid is an easy scalable analytics tracker using MongoDB and Mongoid}
39
+ s.test_files = [
40
+ "spec/spec_helper.rb",
41
+ "spec/trackoid_spec.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
50
+ else
51
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
52
+ end
53
+ else
54
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
55
+ end
56
+ end
57
+
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trackoid
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jose Miguel Perez
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-05-30 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: Trackoid uses an embeddable approach to track analytics data using the poweful features of MongoDB for scalability
38
+ email: josemiguel@perezruiz.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - LICENSE
45
+ - README.rdoc
46
+ files:
47
+ - .document
48
+ - .gitignore
49
+ - LICENSE
50
+ - README.rdoc
51
+ - Rakefile
52
+ - VERSION
53
+ - lib/trackoid.rb
54
+ - lib/trackoid/tracker.rb
55
+ - lib/trackoid/tracking.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ - spec/trackoid_spec.rb
59
+ - trackoid.gemspec
60
+ has_rdoc: true
61
+ homepage: http://github.com/twoixter/trackoid
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.3.7
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Trackoid is an easy scalable analytics tracker using MongoDB and Mongoid
94
+ test_files:
95
+ - spec/spec_helper.rb
96
+ - spec/trackoid_spec.rb