trackoid 0.1.2 → 0.1.3
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/VERSION +1 -1
- data/lib/trackoid/aggregates.rb +4 -0
- data/lib/trackoid/tracker.rb +22 -8
- data/lib/trackoid/tracker_aggregates.rb +39 -5
- data/lib/trackoid/tracking.rb +6 -6
- data/spec/aggregates_spec.rb +45 -10
- data/trackoid.gemspec +1 -1
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/trackoid/aggregates.rb
CHANGED
@@ -129,6 +129,10 @@ module Mongoid #:nodoc:
|
|
129
129
|
TrackerAggregates.new(self, name, args)
|
130
130
|
end
|
131
131
|
|
132
|
+
define_method("#{name}_with_track") do |track_field, *args|
|
133
|
+
TrackerAggregates.new(self, name, args, track_field)
|
134
|
+
end
|
135
|
+
|
132
136
|
define_method("#{name}=") do
|
133
137
|
raise NoMethodError
|
134
138
|
end
|
data/lib/trackoid/tracker.rb
CHANGED
@@ -6,16 +6,29 @@ module Mongoid #:nodoc:
|
|
6
6
|
|
7
7
|
def initialize(owner, field, aggregate_data)
|
8
8
|
@owner, @for = owner, field
|
9
|
-
@
|
9
|
+
@for_data = @owner.internal_track_name(@for)
|
10
|
+
@data = @owner.read_attribute(@for_data)
|
10
11
|
|
11
12
|
# The following is needed if the "field" Mongoid definition for our
|
12
13
|
# internal tracking field does not include option ":default => {}"
|
13
14
|
if @data.nil?
|
14
|
-
@owner.write_attribute(@
|
15
|
-
@data = @owner.read_attribute(@
|
15
|
+
@owner.write_attribute(@for_data, {})
|
16
|
+
@data = @owner.read_attribute(@for_data)
|
16
17
|
end
|
17
18
|
|
18
|
-
@aggregate_data = aggregate_data
|
19
|
+
@aggregate_data = aggregate_data.first if aggregate_data.first
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delegate all missing methods to the aggregate accessors. This enables
|
23
|
+
# us to call an aggregation token after the tracking field.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# <tt>@object.visits.browsers ...</tt>
|
28
|
+
#
|
29
|
+
def method_missing(name, *args, &block)
|
30
|
+
super unless @owner.aggregate_fields.member?(name)
|
31
|
+
@owner.send("#{name}_with_track".to_sym, @for, *args, &block)
|
19
32
|
end
|
20
33
|
|
21
34
|
# Update methods
|
@@ -30,10 +43,10 @@ module Mongoid #:nodoc:
|
|
30
43
|
return unless @owner.aggregated?
|
31
44
|
|
32
45
|
@owner.aggregate_fields.each do |(k,v)|
|
33
|
-
token = v.call(@aggregate_data)
|
46
|
+
next unless token = v.call(@aggregate_data)
|
47
|
+
|
34
48
|
fk = @owner.class.name.to_s.foreign_key.to_sym
|
35
|
-
selector = { fk => @owner.id, :ns => k, :key => token }
|
36
|
-
|
49
|
+
selector = { fk => @owner.id, :ns => k, :key => token.to_s }
|
37
50
|
@owner.aggregate_klass.collection.update( selector,
|
38
51
|
{ (how_much > 0 ? "$inc" : "$dec") => update_hash(how_much.abs, date) },
|
39
52
|
:upsert => true)
|
@@ -50,6 +63,7 @@ module Mongoid #:nodoc:
|
|
50
63
|
|
51
64
|
def set(how_much, date = DateTime.now)
|
52
65
|
raise Errors::ModelNotSaved, "Can't update a new record" if @owner.new_record?
|
66
|
+
|
53
67
|
update_data(how_much, date)
|
54
68
|
@owner.collection.update( @owner._selector,
|
55
69
|
{ "$set" => update_hash(how_much, date) },
|
@@ -113,7 +127,7 @@ module Mongoid #:nodoc:
|
|
113
127
|
|
114
128
|
def update_hash(num, date)
|
115
129
|
{
|
116
|
-
"#{@
|
130
|
+
"#{@for_data}.#{date_literal(date)}" => num
|
117
131
|
}
|
118
132
|
end
|
119
133
|
|
@@ -4,23 +4,57 @@ module Mongoid #:nodoc:
|
|
4
4
|
# This internal class handles all interaction of an aggregation token.
|
5
5
|
class TrackerAggregates
|
6
6
|
|
7
|
-
def initialize(owner, token, key_selector)
|
7
|
+
def initialize(owner, token, key_selector, track_field = nil)
|
8
8
|
@owner, @token = owner, token
|
9
9
|
@key = key_selector
|
10
|
+
@track_field = track_field
|
10
11
|
|
11
12
|
@accessor = @owner.class.send(:internal_accessor_name, @token)
|
12
13
|
@selector = {:ns => @token}
|
13
14
|
@selector.merge!(:key => @key.first) if @key.first
|
15
|
+
@criteria = @owner.send(@accessor).where(@selector)
|
14
16
|
end
|
15
17
|
|
18
|
+
# REFACTOR THIS
|
19
|
+
# WE ARE DOING SOMETHING LIKE:
|
20
|
+
#
|
21
|
+
# => browsers("something").visits
|
22
|
+
#
|
23
|
+
# WHILE A BEST APPROACH IS LIKE
|
24
|
+
#
|
25
|
+
# => visits.browsers("something")
|
26
|
+
|
27
|
+
# visits (Tracker) -> browsers (TrackerAggregates) -> count (to Criteria)
|
28
|
+
# -> today (data)
|
29
|
+
|
30
|
+
# Delegate all missing methods to the underlying Mongoid Criteria
|
16
31
|
def method_missing(name, *args, &block)
|
17
|
-
@criteria
|
18
|
-
|
32
|
+
@criteria.send(name)
|
33
|
+
|
19
34
|
# Delegate all missing methods to the underlying Mongoid Criteria
|
20
|
-
return @criteria.send(name) unless @owner.tracked_fields.member?(name)
|
35
|
+
# return @criteria.send(name) unless @owner.tracked_fields.member?(name)
|
36
|
+
# super
|
21
37
|
|
22
38
|
# Otherwise, it's a track method, so process it
|
23
|
-
@criteria.collect {|x| x.send(name, *args, &block) }
|
39
|
+
# @criteria.collect {|x| x.send(name, *args, &block) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def today
|
43
|
+
@criteria.collect {|c|
|
44
|
+
[c.key, c.send(@track_field).today] if @track_field
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def yesterday
|
49
|
+
@criteria.collect {|c|
|
50
|
+
[c.key, c.send(@track_field).yesterday] if @track_field
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def last_days(how_much = 7)
|
55
|
+
@criteria.collect {|c|
|
56
|
+
[c.key, c.send(@track_field).last_days(how_much)] if @track_field
|
57
|
+
}
|
24
58
|
end
|
25
59
|
|
26
60
|
end
|
data/lib/trackoid/tracking.rb
CHANGED
@@ -25,9 +25,9 @@ module Mongoid #:nodoc:
|
|
25
25
|
# This is necessary so that Mongoid does not "dirty" the field
|
26
26
|
# potentially overwriting the original data.
|
27
27
|
def track(name)
|
28
|
-
set_tracking_field(name)
|
29
|
-
create_tracking_accessors(name)
|
30
|
-
update_aggregates(name) if aggregated?
|
28
|
+
set_tracking_field(name.to_sym)
|
29
|
+
create_tracking_accessors(name.to_sym)
|
30
|
+
update_aggregates(name.to_sym) if aggregated?
|
31
31
|
end
|
32
32
|
|
33
33
|
protected
|
@@ -42,7 +42,7 @@ module Mongoid #:nodoc:
|
|
42
42
|
field internal_track_name(name), :type => Hash # , :default => {}
|
43
43
|
# Should we make an index for this field?
|
44
44
|
index internal_track_name(name)
|
45
|
-
tracked_fields << name
|
45
|
+
tracked_fields << name
|
46
46
|
end
|
47
47
|
|
48
48
|
# Creates the tracking field accessor and also disables the original
|
@@ -50,8 +50,8 @@ module Mongoid #:nodoc:
|
|
50
50
|
# Mongoid fields ensures they doesn't get dirty, so Mongoid does not
|
51
51
|
# overwrite old data.
|
52
52
|
def create_tracking_accessors(name)
|
53
|
-
define_method(
|
54
|
-
Tracker.new(self,
|
53
|
+
define_method(name) do |*aggr|
|
54
|
+
Tracker.new(self, name, aggr)
|
55
55
|
end
|
56
56
|
|
57
57
|
# Should we just "undef" this methods?
|
data/spec/aggregates_spec.rb
CHANGED
@@ -10,10 +10,10 @@ class TestModel
|
|
10
10
|
# for testing pourposes. Trackoid does not make any difference in the
|
11
11
|
# declaration order of tracking fields and aggregate tokens.
|
12
12
|
track :visits
|
13
|
-
aggregate :browsers do
|
13
|
+
aggregate :browsers do |b| b.split.first.downcase if b; end
|
14
14
|
|
15
15
|
track :uniques
|
16
|
-
aggregate :referers do
|
16
|
+
aggregate :referers do |r| r.split.last.downcase if r; end
|
17
17
|
end
|
18
18
|
|
19
19
|
class SecondTestModel
|
@@ -62,8 +62,8 @@ describe Mongoid::Tracking::Aggregates do
|
|
62
62
|
|
63
63
|
it "should create an array in the class with all aggregate fields even when monkey patching" do
|
64
64
|
class TestModel
|
65
|
-
aggregate :quarters do
|
66
|
-
"Q1"
|
65
|
+
aggregate :quarters do |q|
|
66
|
+
"Q1";
|
67
67
|
end
|
68
68
|
end
|
69
69
|
@mock.class.aggregate_fields.keys.to_set.should == [ :browsers, :referers, :quarters ].to_set
|
@@ -167,7 +167,7 @@ describe Mongoid::Tracking::Aggregates do
|
|
167
167
|
end
|
168
168
|
|
169
169
|
it "should increment visits for all aggregated instances" do
|
170
|
-
@mock.visits("
|
170
|
+
@mock.visits("Mozilla Firefox").inc
|
171
171
|
@mock.browsers.count.should == 1
|
172
172
|
@mock.referers.count.should == 1
|
173
173
|
@mock.quarters.count.should == 1
|
@@ -175,7 +175,7 @@ describe Mongoid::Tracking::Aggregates do
|
|
175
175
|
|
176
176
|
it "should increment visits for specific aggregation keys" do
|
177
177
|
@mock.browsers("mozilla").size.should == 1
|
178
|
-
@mock.referers("
|
178
|
+
@mock.referers("firefox").size.should == 1
|
179
179
|
@mock.quarters("Q1").size.should == 1
|
180
180
|
end
|
181
181
|
|
@@ -185,12 +185,47 @@ describe Mongoid::Tracking::Aggregates do
|
|
185
185
|
@mock.quarters("Q2").size.should == 0
|
186
186
|
end
|
187
187
|
|
188
|
-
it "should have 1
|
189
|
-
|
190
|
-
|
188
|
+
it "should have 1 visits today" do
|
189
|
+
@mock.visits.browsers.today.should == [["mozilla", 1]]
|
190
|
+
@mock.visits.referers.today.should == [["firefox", 1]]
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should have 0 visits yesterday" do
|
194
|
+
@mock.visits.browsers.today.should == [["mozilla", 1]]
|
195
|
+
@mock.visits.referers.today.should == [["firefox", 1]]
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should have 1 visits last 7 days" do
|
199
|
+
@mock.visits.browsers.last_days(7).should == [["mozilla", [0, 0, 0, 0, 0, 0, 1]]]
|
200
|
+
@mock.visits.referers.last_days(7).should == [["firefox", [0, 0, 0, 0, 0, 0, 1]]]
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should work adding 1 visit with different aggregation data" do
|
204
|
+
@mock.visits("Google Chrome").inc
|
205
|
+
@mock.visits.browsers.today.should == [["mozilla", 1], ["google", 1]]
|
206
|
+
@mock.visits.referers.today.should == [["firefox", 1], ["chrome", 1]]
|
207
|
+
|
208
|
+
# Just for testing array manipulations
|
209
|
+
@mock.visits.browsers.today.inject(0) {|total, c| total + c.last }.should == 2
|
210
|
+
end
|
211
|
+
|
212
|
+
it "let's chek what happens when sorting the best browser..." do
|
213
|
+
@mock.visits("Google Chrome").inc
|
214
|
+
@mock.visits.browsers.today.should == [["mozilla", 1], ["google", 2]]
|
215
|
+
@mock.visits.browsers.today.max {|a,b| a.second <=> b.second }.should == ["google", 2]
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should work without aggregation information" do
|
219
|
+
@mock.visits.inc
|
220
|
+
@mock.visits.browsers.today.should == [["mozilla", 1], ["google", 2]]
|
221
|
+
@mock.visits.referers.today.should == [["firefox", 1], ["chrome", 2]]
|
222
|
+
|
223
|
+
# A more throughout test would check totals...
|
224
|
+
visits_today = @mock.visits.today
|
225
|
+
visits_today_with_browser = @mock.visits.browsers.today.inject(0) {|total, c| total + c.last }
|
226
|
+
visits_today.should == visits_today_with_browser + 1
|
191
227
|
end
|
192
228
|
|
193
|
-
|
194
229
|
end
|
195
230
|
|
196
231
|
|
data/trackoid.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trackoid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 29
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 3
|
10
|
+
version: 0.1.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jose Miguel Perez
|