trackoid 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|