gatoroid 0.1.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/.rspec +2 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +59 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/gatoroid.gemspec +70 -0
- data/lib/gator/errors.rb +38 -0
- data/lib/gator/gator.rb +39 -0
- data/lib/gator/gatorer.rb +204 -0
- data/lib/gator/javascript/functions.yml +17 -0
- data/lib/gator/javascript.rb +22 -0
- data/lib/gator/readers.rb +33 -0
- data/lib/gatoroid.rb +27 -0
- data/spec/gatoroid_spec.rb +198 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +26 -0
- metadata +130 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Kevin Haight (kevinjhaight@gmail.com)
|
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,59 @@
|
|
1
|
+
= Gatoroid
|
2
|
+
|
3
|
+
Gatoroid is a way to store analytics using the powerful features of MongoDB for scalability
|
4
|
+
|
5
|
+
Leveraging mongoid, Gatoroid is a way to increment, decrement, add to, and reset counters in MongoDB. The data is stored using an hourly grain in a utc allowing for easy analytics for on just about anything.
|
6
|
+
|
7
|
+
== Using Gatoroid to store analytics information
|
8
|
+
Simply create a class that includes Mongoid::Gator
|
9
|
+
|
10
|
+
class SiteStats
|
11
|
+
include Mongoid::Gator
|
12
|
+
field :siteid
|
13
|
+
gator_field :visits
|
14
|
+
end
|
15
|
+
|
16
|
+
Use the class to increment the counter for visits to the site.
|
17
|
+
|
18
|
+
@site_stats = SiteStats.new()
|
19
|
+
@site_stats.visits.inc(:siteid=>100)
|
20
|
+
|
21
|
+
You can also add to the count using the add method. The following adds 10 visits to tomorrow.
|
22
|
+
|
23
|
+
@site_stats.visits.add(10, :date=>Today.now + 1.day)
|
24
|
+
|
25
|
+
The data will be stored in MongoDB using seconds since the epoch in hours
|
26
|
+
|
27
|
+
{
|
28
|
+
"_id" : ObjectId("4f85b40f2a2c4e4d9709eaf9"),
|
29
|
+
"date" : 1334160000,
|
30
|
+
"siteid" : 100,
|
31
|
+
"visits" : 1
|
32
|
+
}
|
33
|
+
|
34
|
+
You can view the total visits to a site by using some built in functions. For example:
|
35
|
+
|
36
|
+
@site_stats = SiteStats.new()
|
37
|
+
@site_stats.visits.today(:siteid=>100) # returns total for today as integer
|
38
|
+
@site_stats.visits.yesterday(:siteid=100) # returns total for yesterday as integer
|
39
|
+
|
40
|
+
|
41
|
+
To get a list of visits to for a range, use the range method.
|
42
|
+
|
43
|
+
@site_stats = SiteStats.new()
|
44
|
+
@site_stats.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::DAY, :siteid=>100)
|
45
|
+
|
46
|
+
You can change the grain on which you would like to return the data using
|
47
|
+
|
48
|
+
Mongoid::Gator::Readers::HOUR
|
49
|
+
Mongoid::Gator::Readers::DAY
|
50
|
+
Mongoid::Gator::Readers::MONTH
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "gatoroid"
|
8
|
+
gem.summary = %Q{Gatoroid is an easy scalable analytics plugin using MongoDB and Mongoid}
|
9
|
+
gem.description = %Q{Gatoroid is a way to store analytics using the poweful features of MongoDB for scalability}
|
10
|
+
gem.email = "kevinjhaight@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/alayho/gatoroid"
|
12
|
+
gem.authors = ["Kevin Haight"]
|
13
|
+
end
|
14
|
+
Jeweler::GemcutterTasks.new
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
21
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
25
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
26
|
+
spec.rcov = true
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => :spec
|
30
|
+
|
31
|
+
require 'rake/rdoctask'
|
32
|
+
Rake::RDocTask.new do |rdoc|
|
33
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
34
|
+
rdoc.rdoc_dir = 'rdoc'
|
35
|
+
rdoc.title = "gatoroid #{version}"
|
36
|
+
rdoc.rdoc_files.include('README*')
|
37
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
38
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/gatoroid.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "gatoroid"
|
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 = ["Kevin Haight"]
|
12
|
+
s.date = "2012-04-11"
|
13
|
+
s.description = "Gatoroid is a way to store analytics using the poweful features of MongoDB for scalability"
|
14
|
+
s.email = "kevinjhaight@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".rspec",
|
21
|
+
"Gemfile",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"gatoroid.gemspec",
|
27
|
+
"lib/gator/errors.rb",
|
28
|
+
"lib/gator/gator.rb",
|
29
|
+
"lib/gator/gatorer.rb",
|
30
|
+
"lib/gator/javascript.rb",
|
31
|
+
"lib/gator/javascript/functions.yml",
|
32
|
+
"lib/gator/readers.rb",
|
33
|
+
"lib/gatoroid.rb",
|
34
|
+
"spec/gatoroid_spec.rb",
|
35
|
+
"spec/spec.opts",
|
36
|
+
"spec/spec_helper.rb"
|
37
|
+
]
|
38
|
+
s.homepage = "http://github.com/alayho/gatoroid"
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = "1.8.17"
|
41
|
+
s.summary = "Gatoroid is an easy scalable analytics plugin using MongoDB and Mongoid"
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<mongoid>, [">= 2.1.0"])
|
48
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
50
|
+
s.add_development_dependency(%q<rspec>, [">= 2.2.0"])
|
51
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<bson_ext>, [">= 0"])
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<mongoid>, [">= 2.1.0"])
|
55
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
56
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
57
|
+
s.add_dependency(%q<rspec>, [">= 2.2.0"])
|
58
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
59
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
60
|
+
end
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<mongoid>, [">= 2.1.0"])
|
63
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
64
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
65
|
+
s.add_dependency(%q<rspec>, [">= 2.2.0"])
|
66
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
67
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/gator/errors.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc
|
3
|
+
module Errors #:nodoc
|
4
|
+
|
5
|
+
class ClassAlreadyDefined < RuntimeError
|
6
|
+
def initialize(klass)
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
def message
|
10
|
+
"#{@klass} already defined, can't aggregate!"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AggregationAlreadyDefined < RuntimeError
|
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)
|
26
|
+
@token = token
|
27
|
+
end
|
28
|
+
def message
|
29
|
+
"Ussing aggregation name '#{@klass}' is deprecated. Please select another name."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ModelNotSaved < RuntimeError; end
|
34
|
+
|
35
|
+
class NotMongoid < RuntimeError; end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/gator/gator.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'mongoid'
|
3
|
+
module Mongoid #:nodoc:
|
4
|
+
module Gator #:nodoc:
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
include Mongoid::Document
|
8
|
+
extend ClassMethods
|
9
|
+
class_attribute :gator_keys, :gator_fields
|
10
|
+
self.gator_keys = []
|
11
|
+
self.gator_fields = []
|
12
|
+
delegate :gator_keys, :to => "self.class"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def field(name=nil)
|
18
|
+
gator_keys << name
|
19
|
+
end
|
20
|
+
|
21
|
+
def gator_field(name=nil)
|
22
|
+
gator_fields << name
|
23
|
+
create_accessors(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def create_accessors(name)
|
28
|
+
define_method(name) do
|
29
|
+
Gatorer.new(self, name)
|
30
|
+
end
|
31
|
+
|
32
|
+
define_singleton_method(name) do
|
33
|
+
Gatorer.new(self, name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Gator
|
4
|
+
class Gatorer
|
5
|
+
|
6
|
+
include Readers
|
7
|
+
|
8
|
+
# Initialize object
|
9
|
+
def initialize(object, field)
|
10
|
+
@object, @for = object, field
|
11
|
+
create_accessors()
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Get total for
|
17
|
+
def total_for(date,grain,opts={})
|
18
|
+
unless date.nil?
|
19
|
+
begin
|
20
|
+
return @object.class.where(create_query_hash(date,grain,opts)).sum(@for)
|
21
|
+
rescue
|
22
|
+
return @object.where(create_query_hash(date,grain,opts)).sum(@for)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get collections for
|
28
|
+
def collection_for(date,grain, opts={})
|
29
|
+
unless date.nil?
|
30
|
+
return @object.collection.group(:keyf => create_fkey(grain),
|
31
|
+
:reduce => "function(obj,prev){for (var key in obj.#{@for}) {prev.#{@for} += obj.#{@for}}}",
|
32
|
+
:cond=>create_query_hash(date,grain,opts),
|
33
|
+
:initial => {@for => 0})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert date levels
|
38
|
+
def convert_date_by_level(date,level)
|
39
|
+
if date.is_a?(Range)
|
40
|
+
sdate = date.first
|
41
|
+
edate = date.last
|
42
|
+
else
|
43
|
+
sdate = date
|
44
|
+
edate = date
|
45
|
+
end
|
46
|
+
case level
|
47
|
+
when HOUR
|
48
|
+
return sdate.change(:sec=>0), edate.change(:sec=>0) + 1.hour
|
49
|
+
when DAY
|
50
|
+
return sdate.change(:hour=>0).change(:sec=>0), edate.change(:hour=>0).change(:sec=>0) + 1.day
|
51
|
+
when MONTH
|
52
|
+
return sdate.change(:day=>1).change(:hour=>0).change(:sec=>0), edate.change(:day=>1).change(:hour=>0).change(:sec=>0) + 1.month
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create fkey
|
57
|
+
def create_fkey(grain)
|
58
|
+
case grain
|
59
|
+
when HOUR
|
60
|
+
fkey = Javascript.aggregate_hour
|
61
|
+
when MONTH
|
62
|
+
fkey = Javascript.aggregate_month
|
63
|
+
else # DEFAULT TO DAY
|
64
|
+
fkey = Javascript.aggregate_day
|
65
|
+
end
|
66
|
+
return fkey
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def create_accessors
|
71
|
+
self.class.class_eval do
|
72
|
+
define_method :inc do | *args |
|
73
|
+
keys, date = gen_params(Hash[*args])
|
74
|
+
inc_counter(keys,date)
|
75
|
+
end
|
76
|
+
|
77
|
+
define_method :dec do | *args |
|
78
|
+
keys, date = gen_params(Hash[*args])
|
79
|
+
dec_counter(keys,date)
|
80
|
+
end
|
81
|
+
|
82
|
+
define_method :add do | how_many, *args |
|
83
|
+
keys, date = gen_params(Hash[*args])
|
84
|
+
add_to_counter(how_many,keys,date)
|
85
|
+
end
|
86
|
+
|
87
|
+
define_method :reset do | *args |
|
88
|
+
keys, date = gen_params(Hash[*args])
|
89
|
+
reset_counter(keys,date)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add
|
95
|
+
def add_to_counter(how_much = 1, keys=[], date = Time.now)
|
96
|
+
return if how_much == 0
|
97
|
+
# Upsert value
|
98
|
+
@object.collection.update(create_key_hash(keys,date.utc),
|
99
|
+
{"$inc" => {
|
100
|
+
"#{@for}" => how_much,
|
101
|
+
}},
|
102
|
+
:upsert => true
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Reset Counter
|
107
|
+
def reset_counter(keys=[], date = Time.now)
|
108
|
+
# Upsert value
|
109
|
+
@object.collection.update(create_key_hash(keys,date.utc),
|
110
|
+
{"$set" => {
|
111
|
+
"#{@for}" => 0,
|
112
|
+
}},
|
113
|
+
:upsert => true
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Increment Counter
|
118
|
+
def inc_counter(keys,date = Time.now)
|
119
|
+
add_to_counter(1,keys,date)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Decrement Counter
|
123
|
+
def dec_counter(keys, date = Time.now)
|
124
|
+
add_to_counter(-1,keys,date)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Generate parameters
|
128
|
+
def gen_params(params)
|
129
|
+
date = Time.now # Set default date to now
|
130
|
+
key_hash = Hash.new { |hash, key| hash[key] = [] }
|
131
|
+
@object.gator_keys.each do| gk |
|
132
|
+
raise Errors::ModelNotSaved, "Missing key value #{gk}" if params[gk].nil?
|
133
|
+
key_hash[gk] = params[gk]
|
134
|
+
end
|
135
|
+
# Set Date
|
136
|
+
if !params[:date].nil?
|
137
|
+
date = params[:date]
|
138
|
+
end
|
139
|
+
return key_hash, date
|
140
|
+
end
|
141
|
+
|
142
|
+
# Create Hash Key
|
143
|
+
def create_key_hash(keys,date = Time.now)
|
144
|
+
keys = Hash[keys]
|
145
|
+
key_hash = Hash.new { |hash, key| hash[key] = [] }
|
146
|
+
@object.gator_keys.each do | gk |
|
147
|
+
if keys[gk].kind_of?(Array)
|
148
|
+
keys[gk].each do |k|
|
149
|
+
key_hash[gk] = k
|
150
|
+
end
|
151
|
+
else
|
152
|
+
key_hash[gk] = keys[gk]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
key_hash[:date] = normalize_date(date)
|
156
|
+
return key_hash
|
157
|
+
end
|
158
|
+
|
159
|
+
# Create Group Key Hash
|
160
|
+
def create_group_key_hash
|
161
|
+
keys = []
|
162
|
+
@object.gator_keys.each do | gk |
|
163
|
+
keys << gk
|
164
|
+
end
|
165
|
+
keys << :date
|
166
|
+
return keys
|
167
|
+
end
|
168
|
+
|
169
|
+
# Create Query Hash
|
170
|
+
def create_query_hash(date = Time.now, grain, opts)
|
171
|
+
key_hash = Hash.new { |hash, key| hash[key] = [] }
|
172
|
+
# Set Keys
|
173
|
+
@object.gator_keys.each do | gk |
|
174
|
+
raise Errors::ModelNotSaved, "Missing key value #{gk}" if opts[gk].nil?
|
175
|
+
if opts[gk].kind_of?(Array)
|
176
|
+
key_hash[gk] = {"$in" => opts[gk]}
|
177
|
+
else
|
178
|
+
key_hash[gk] = opts[gk]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
sdate,edate = convert_date_by_level(date,grain) # Set Dates
|
182
|
+
key_hash[:date] = {"$gte" => normalize_date(sdate), "$lt" => normalize_date(edate)}
|
183
|
+
return key_hash
|
184
|
+
end
|
185
|
+
|
186
|
+
# Normalize Dates
|
187
|
+
def normalize_date(date)
|
188
|
+
case date
|
189
|
+
when String
|
190
|
+
date = Time.parse(date).change(:sec => 0).change(:min => 0)
|
191
|
+
when Date
|
192
|
+
date = date.to_time.change(:sec => 0).change(:min => 0)
|
193
|
+
when Range
|
194
|
+
date = normalize_date(date.change(:sec => 0).change(:min => 0).first)..normalize_date(date.change(:sec => 0).change(:min => 0).last)
|
195
|
+
else
|
196
|
+
date = date.change(:sec => 0).change(:min => 0)
|
197
|
+
end
|
198
|
+
return date.to_i
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
aggregate_hour:
|
2
|
+
"function(doc) {
|
3
|
+
d = new Date(doc.date* 1000);
|
4
|
+
return {date: Date.parse((d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear() + ' ' + d.getHours() + ':00:00') / 1000};
|
5
|
+
}"
|
6
|
+
|
7
|
+
aggregate_day:
|
8
|
+
"function(doc) {
|
9
|
+
d = new Date(doc.date* 1000);
|
10
|
+
return {date: Date.parse((d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear()) / 1000};
|
11
|
+
}"
|
12
|
+
|
13
|
+
aggregate_month:
|
14
|
+
"function(doc) {
|
15
|
+
d = new Date(doc.date* 1000);
|
16
|
+
return {date: Date.parse((d.getMonth() + 1) + '/1/' + d.getFullYear()) / 1000};
|
17
|
+
}"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Gator
|
4
|
+
class Javascript
|
5
|
+
# Constant for the file that defines all the js functions.
|
6
|
+
FUNCTIONS = File.join(File.dirname(__FILE__), "javascript", "functions.yml")
|
7
|
+
|
8
|
+
# Load the javascript functions and define a class method for each one,
|
9
|
+
# that memoizes the value.
|
10
|
+
#
|
11
|
+
# @example Get the function.
|
12
|
+
# Mongoid::Javascript.aggregate
|
13
|
+
YAML.load(File.read(FUNCTIONS)).each_pair do |key, function|
|
14
|
+
(class << self; self; end).class_eval <<-EOT
|
15
|
+
def #{key}
|
16
|
+
@#{key} ||= "#{function}"
|
17
|
+
end
|
18
|
+
EOT
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Gator
|
4
|
+
module Readers
|
5
|
+
|
6
|
+
HOUR = "HOUR"
|
7
|
+
DAY = "DAY"
|
8
|
+
MONTH = "MONTH"
|
9
|
+
DEFAULT_GRAIN = DAY
|
10
|
+
|
11
|
+
# Today - Gets total for today on DAY level
|
12
|
+
def today(opts={})
|
13
|
+
total_for(Time.now, DEFAULT_GRAIN, opts).to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
# Yesterday - Gets total for tomorrow on DAY level
|
17
|
+
def yesterday(opts={})
|
18
|
+
total_for(Time.now - 1.day, DEFAULT_GRAIN,opts).to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
# On - Gets total for a specified day on DAY level
|
22
|
+
def on(date,opts={})
|
23
|
+
total_for(date, DEFAULT_GRAIN,opts).to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
# Range - retuns a collection for a specified range on specified level
|
27
|
+
def range(date, grain=DEFAULT_GRAIN, opts={})
|
28
|
+
collection_for(date,grain,opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/gatoroid.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
gem "mongoid", ">= 1.9.0"
|
5
|
+
|
6
|
+
#require 'gator/errors'
|
7
|
+
#require 'gator/core_ext'
|
8
|
+
#require 'gator/reader_extender'
|
9
|
+
#require 'gator/readers'
|
10
|
+
#require 'gator/gator'
|
11
|
+
#require 'gator/aggregates'
|
12
|
+
#require 'gator/gator_aggregates'
|
13
|
+
#require '../lib/gator/gatoring.rb'
|
14
|
+
#require File.expand_path('../gator/errors.rb', __FILE__)
|
15
|
+
#require File.expand_path('../gator/reader_extender.rb', __FILE__)
|
16
|
+
require File.expand_path('../gator/errors.rb', __FILE__)
|
17
|
+
require File.expand_path('../gator/javascript.rb', __FILE__)
|
18
|
+
require File.expand_path('../gator/readers.rb', __FILE__)
|
19
|
+
require File.expand_path('../gator/gatorer.rb', __FILE__)
|
20
|
+
require File.expand_path('../gator/gator.rb', __FILE__)
|
21
|
+
#require File.expand_path('../../lib/gator.rb', __FILE__)
|
22
|
+
|
23
|
+
module Mongoid
|
24
|
+
module Gator
|
25
|
+
VERSION = File.read(File.expand_path("../VERSION", File.dirname(__FILE__)))
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class Test
|
4
|
+
include Mongoid::Gator
|
5
|
+
field :siteid
|
6
|
+
gator_field :visits
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
describe Mongoid::Gator do
|
11
|
+
before(:all) do
|
12
|
+
@gatoroid_version = File.read(File.expand_path("../VERSION", File.dirname(__FILE__)))
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should expose the same version as the VERSION file" do
|
16
|
+
Mongoid::Gator::VERSION.should == @gatoroid_version
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not raise errors when using to/as_json" do
|
20
|
+
mock = Test.new(:siteid => 1000)
|
21
|
+
json_as = {}
|
22
|
+
json_to = ""
|
23
|
+
|
24
|
+
lambda {
|
25
|
+
json_as = mock.as_json(:except => :_id)
|
26
|
+
json_to = mock.to_json(:except => :_id)
|
27
|
+
}.should_not raise_error
|
28
|
+
json_as.should == { "siteid" => 1000 }
|
29
|
+
json_to.should == "{\"siteid\":1000}"
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "when using an instance of the object" do
|
33
|
+
before(:all) do
|
34
|
+
@obj = Test.new
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should deny access to undefined methods" do
|
38
|
+
lambda { @obj.test_method }.should raise_error NoMethodError
|
39
|
+
lambda { @obj.test_method = {} }.should raise_error NoMethodError
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should create a method for gator_fields" do
|
43
|
+
@obj.respond_to?(:visits).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should NOT create an index for the gator_fields" do
|
47
|
+
@obj.class.index_options.should_not include(:visits)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should create a method for accesing the stats of the proper class" do
|
51
|
+
@obj.visits.class.should == Mongoid::Gator::Gatorer
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create an array in the class with all gator fields" do
|
55
|
+
@obj.class.gator_fields.should == [ :visits ]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should create an array in the class with all gator fields even when monkey patching" do
|
59
|
+
class Test
|
60
|
+
gator_field :something_else
|
61
|
+
end
|
62
|
+
@obj.class.gator_fields.should == [ :visits, :something_else ]
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not increment stats when missing key" do
|
66
|
+
lambda { @obj.visits.inc }.should raise_error Mongoid::Errors::ModelNotSaved
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should increment stats when key is present" do
|
70
|
+
lambda { @obj.visits.inc(:siteid=>100) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not decrement stats when key is present" do
|
74
|
+
lambda { @obj.visits.dec() }.should raise_error Mongoid::Errors::ModelNotSaved
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should decrement stats when key is present" do
|
78
|
+
lambda { @obj.visits.dec(:siteid=>100) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not add stats when key is present" do
|
82
|
+
lambda { @obj.visits.add(1) }.should raise_error Mongoid::Errors::ModelNotSaved
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should add stats when key is present" do
|
86
|
+
lambda { @obj.visits.add(1,:siteid=>100) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
it "should give 1 for today stats" do
|
91
|
+
@obj.visits.today(:siteid=>100).should == 1
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should give 0 for yesterday stats" do
|
95
|
+
@obj.visits.yesterday(:siteid=>100).should == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should give 1 for using on for today stats" do
|
99
|
+
@obj.visits.on(Time.now,:siteid=>100).should == 1
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should have 1 record using range method for today and yesterday at day grain" do
|
103
|
+
@obj.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::DAY, :siteid=>100).should have(1).record
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should have 1 record using range method for today and yesterday at hour grain" do
|
107
|
+
@obj.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::HOUR, :siteid=>100).should have(1).record
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should have 1 record using range method for today and yesterday at month grain" do
|
111
|
+
@obj.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::HOUR, :siteid=>100).should have(1).record
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should reset value to zero" do
|
115
|
+
@obj.visits.reset(:date => Time.now, :siteid=>100).should_not raise_error Mongoid::Errors::ModelNotSaved
|
116
|
+
@obj.visits.today(:siteid=>100).should == 0
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
describe "when using as a model" do
|
123
|
+
it "should deny access to undefined methods" do
|
124
|
+
lambda { Test.test_method }.should raise_error NoMethodError
|
125
|
+
lambda { Test.test_method = {} }.should raise_error NoMethodError
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should create a method for gator_fields" do
|
129
|
+
Test.respond_to?(:visits).should be_true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should NOT create an index for the gator_fields" do
|
133
|
+
Test.index_options.should_not include(:visits)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should create a method for accesing the stats of the proper class" do
|
137
|
+
Test.visits.class.should == Mongoid::Gator::Gatorer
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should create an array in the class with all gator fields even when monkey patching" do
|
141
|
+
Test.gator_fields.should == [ :visits, :something_else ]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should not increment stats when missing key" do
|
145
|
+
lambda { Test.visits.inc }.should raise_error Mongoid::Errors::ModelNotSaved
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should increment stats when key is present" do
|
149
|
+
lambda { Test.visits.inc(:siteid=>200) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not decrement stats when key is present" do
|
153
|
+
lambda { Test.visits.dec() }.should raise_error Mongoid::Errors::ModelNotSaved
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should decrement stats when key is present" do
|
157
|
+
lambda { Test.visits.dec(:siteid=>200) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should not add stats when key is present" do
|
161
|
+
lambda { Test.visits.add(1) }.should raise_error Mongoid::Errors::ModelNotSaved
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should add stats when key is present" do
|
165
|
+
lambda { Test.visits.add(1,:siteid=>200) }.should_not raise_error Mongoid::Errors::ModelNotSaved
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should give 1 for today stats" do
|
169
|
+
Test.visits.today(:siteid=>200).should == 1
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should give 0 for yesterday stats" do
|
173
|
+
Test.visits.yesterday(:siteid=>200).should == 0
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should give 1 for using on for today stats" do
|
177
|
+
Test.visits.on(Time.now,:siteid=>200).should == 1
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should have 1 record using range method for today and yesterday at day grain" do
|
181
|
+
Test.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::DAY, :siteid=>200).should have(1).record
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should have 1 record using range method for today and yesterday at hour grain" do
|
185
|
+
Test.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::HOUR, :siteid=>200).should have(1).record
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should have 1 record using range method for today and yesterday at month grain" do
|
189
|
+
Test.visits.range(Time.now..Time.now + 1.day,Mongoid::Gator::Readers::HOUR, :siteid=>200).should have(1).record
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should reset value to zero" do
|
193
|
+
Test.visits.reset(:date => Time.now, :siteid=>200).should_not raise_error Mongoid::Errors::ModelNotSaved
|
194
|
+
Test.visits.today(:siteid=>200).should == 0
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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 'gatoroid'
|
10
|
+
require 'bson'
|
11
|
+
require 'rspec'
|
12
|
+
require 'rspec/autorun'
|
13
|
+
|
14
|
+
Mongoid.configure do |config|
|
15
|
+
name = "gatoroid_test"
|
16
|
+
host = "localhost"
|
17
|
+
port = "27017"
|
18
|
+
config.master = Mongo::Connection.new.db(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.mock_with :mocha
|
23
|
+
config.before :suite do
|
24
|
+
Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gatoroid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kevin Haight
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: &2164561420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2164561420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &2164560940 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2164560940
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: jeweler
|
38
|
+
requirement: &2164560400 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2164560400
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &2164559640 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.2.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2164559640
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: mocha
|
60
|
+
requirement: &2164558740 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2164558740
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bson_ext
|
71
|
+
requirement: &2164557900 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *2164557900
|
80
|
+
description: Gatoroid is a way to store analytics using the poweful features of MongoDB
|
81
|
+
for scalability
|
82
|
+
email: kevinjhaight@gmail.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files:
|
86
|
+
- LICENSE
|
87
|
+
- README.rdoc
|
88
|
+
files:
|
89
|
+
- .rspec
|
90
|
+
- Gemfile
|
91
|
+
- LICENSE
|
92
|
+
- README.rdoc
|
93
|
+
- Rakefile
|
94
|
+
- VERSION
|
95
|
+
- gatoroid.gemspec
|
96
|
+
- lib/gator/errors.rb
|
97
|
+
- lib/gator/gator.rb
|
98
|
+
- lib/gator/gatorer.rb
|
99
|
+
- lib/gator/javascript.rb
|
100
|
+
- lib/gator/javascript/functions.yml
|
101
|
+
- lib/gator/readers.rb
|
102
|
+
- lib/gatoroid.rb
|
103
|
+
- spec/gatoroid_spec.rb
|
104
|
+
- spec/spec.opts
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
homepage: http://github.com/alayho/gatoroid
|
107
|
+
licenses: []
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.17
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Gatoroid is an easy scalable analytics plugin using MongoDB and Mongoid
|
130
|
+
test_files: []
|