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 CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.3
@@ -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
@@ -6,16 +6,29 @@ module Mongoid #:nodoc:
6
6
 
7
7
  def initialize(owner, field, aggregate_data)
8
8
  @owner, @for = owner, field
9
- @data = @owner.read_attribute(@for)
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(@for, {})
15
- @data = @owner.read_attribute(@for)
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
- "#{@for}.#{date_literal(date)}" => num
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 ||= @owner.send(@accessor).where(@selector)
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
@@ -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.to_sym
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("#{name}") do |*aggr|
54
- Tracker.new(self, "#{name}_data".to_sym, aggr)
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?
@@ -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; "Mozilla".downcase; end
13
+ aggregate :browsers do |b| b.split.first.downcase if b; end
14
14
 
15
15
  track :uniques
16
- aggregate :referers do; "GoogleBot".downcase; end
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("Aggregate data").inc
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("googlebot").size.should == 1
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 in visits" do
189
- tt = @mock.browsers.visits.collect {|v| v.last_days(7)}
190
- tt.should == [[0, 0, 0, 0, 0, 0, 1]]
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
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{trackoid}
8
- s.version = "0.1.2"
8
+ s.version = "0.1.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jose Miguel Perez"]
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: 31
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 2
10
- version: 0.1.2
9
+ - 3
10
+ version: 0.1.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jose Miguel Perez