mongoid_traffic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +112 -0
- data/Guardfile +8 -0
- data/LICENSE +21 -0
- data/README.md +229 -0
- data/Rakefile +26 -0
- data/lib/mongoid_traffic/controller_additions.rb +32 -0
- data/lib/mongoid_traffic/log.rb +86 -0
- data/lib/mongoid_traffic/logger/bots.rb +34 -0
- data/lib/mongoid_traffic/logger/browser.rb +31 -0
- data/lib/mongoid_traffic/logger/geo_ip.rb +22 -0
- data/lib/mongoid_traffic/logger/referer.rb +27 -0
- data/lib/mongoid_traffic/logger.rb +125 -0
- data/lib/mongoid_traffic/version.rb +3 -0
- data/lib/mongoid_traffic.rb +3 -0
- data/mongoid_traffic.gemspec +33 -0
- data/test/mongoid_traffic/controller_additions_test.rb +27 -0
- data/test/mongoid_traffic/log_test.rb +153 -0
- data/test/mongoid_traffic/logger/bots_test.rb +23 -0
- data/test/mongoid_traffic/logger/browser_test.rb +26 -0
- data/test/mongoid_traffic/logger/geoip_test.rb +17 -0
- data/test/mongoid_traffic/logger/referer_test.rb +30 -0
- data/test/mongoid_traffic/logger_test.rb +63 -0
- data/test/test_helper.rb +44 -0
- data/vendor/mongoid_traffic/GeoIP.dat +0 -0
- data/vendor/mongoid_traffic/allagents.xml +22170 -0
- metadata +235 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module MongoidTraffic
|
2
|
+
module ControllerAdditions
|
3
|
+
|
4
|
+
def log_traffic scope: nil
|
5
|
+
MongoidTraffic::Logger.log(
|
6
|
+
ip_address: request.remote_ip,
|
7
|
+
referer: request.headers['Referer'],
|
8
|
+
unique_id: request.session_options[:id], # FIXME: not sure about this
|
9
|
+
user_agent: request.headers['User-Agent']
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_scoped_traffic scope: nil
|
14
|
+
log_traffic(scope: (scope || request.fullpath.split('?').first))
|
15
|
+
end
|
16
|
+
|
17
|
+
# ---------------------------------------------------------------------
|
18
|
+
|
19
|
+
def self.included base
|
20
|
+
base.extend ClassMethods
|
21
|
+
base.helper_method :log_traffic
|
22
|
+
base.helper_method :log_scoped_traffic
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if defined? ActionController::Base
|
29
|
+
ActionController::Base.class_eval do
|
30
|
+
include MongoidTraffic::ControllerAdditions
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module MongoidTraffic
|
2
|
+
class Log
|
3
|
+
|
4
|
+
include Mongoid::Document
|
5
|
+
|
6
|
+
# ---------------------------------------------------------------------
|
7
|
+
|
8
|
+
field :s, as: :scope, type: String
|
9
|
+
|
10
|
+
field :ac, as: :access_count, type: Integer
|
11
|
+
|
12
|
+
field :b, as: :browsers, type: Hash, default: {}
|
13
|
+
field :c, as: :countries, type: Hash, default: {}
|
14
|
+
field :r, as: :referers, type: Hash, default: {}
|
15
|
+
field :u, as: :unique_ids, type: Hash, default: {}
|
16
|
+
|
17
|
+
field :uat, as: :updated_at, type: Time
|
18
|
+
|
19
|
+
# ---------------------------------------------------------------------
|
20
|
+
|
21
|
+
field :df, as: :date_from, type: Date
|
22
|
+
field :dt, as: :date_to, type: Date
|
23
|
+
|
24
|
+
# ---------------------------------------------------------------------
|
25
|
+
|
26
|
+
validates :date_from, presence: true
|
27
|
+
validates :date_to, presence: true
|
28
|
+
|
29
|
+
# ---------------------------------------------------------------------
|
30
|
+
|
31
|
+
default_scope -> { where(scope: nil) }
|
32
|
+
|
33
|
+
scope :for_dates, -> date_from, date_to { where(date_from: date_from, date_to: date_to) }
|
34
|
+
|
35
|
+
scope :yearly, -> year { self.for_dates(Date.parse("01/01/#{year}"), Date.parse("01/01/#{year}").at_end_of_year) }
|
36
|
+
scope :monthly, -> month, year { self.for_dates(Date.parse("01/#{month}/#{year}"), Date.parse("01/#{month}/#{year}").at_end_of_month) }
|
37
|
+
scope :weekly, -> week, year { self.for_dates(Date.commercial(year, week), Date.commercial(year, week).at_end_of_week) }
|
38
|
+
scope :daily, -> date { self.for_dates(date, date) }
|
39
|
+
|
40
|
+
scope :scoped_to, -> scope { where(scope: scope) }
|
41
|
+
|
42
|
+
# ---------------------------------------------------------------------
|
43
|
+
|
44
|
+
index({ scope: 1, date_from: 1, date_to: 1 })
|
45
|
+
|
46
|
+
# =====================================================================
|
47
|
+
|
48
|
+
def self.aggregate_on att
|
49
|
+
case find_field_by_name(att).type.to_s
|
50
|
+
when 'Integer' then sum(att)
|
51
|
+
when 'Hash' then sum_hash(att)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.sum att
|
56
|
+
if att.to_sym == :unique_ids
|
57
|
+
aggregate_on(:unique_ids).keys.count
|
58
|
+
else
|
59
|
+
super(att)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private # =============================================================
|
64
|
+
|
65
|
+
def self.find_field_by_name field_name
|
66
|
+
return unless f = fields.detect{ |k,v| k == field_name.to_s or v.options[:as].to_s == field_name.to_s }
|
67
|
+
f.last
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.sum_hash field_name
|
71
|
+
self.pluck(field_name).inject({}) do |res, h|
|
72
|
+
merger = proc { |key, v1, v2|
|
73
|
+
if Hash === v1 && Hash === v2
|
74
|
+
v1.merge(v2, &merger)
|
75
|
+
elsif Hash === v2
|
76
|
+
v2
|
77
|
+
else
|
78
|
+
v1.to_i + v2.to_i
|
79
|
+
end
|
80
|
+
}
|
81
|
+
res = res.merge(h, &merger)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# sourced from https://github.com/charlotte-ruby/impressionist/blob/master/lib/impressionist/bots.rb
|
4
|
+
|
5
|
+
module MongoidTraffic
|
6
|
+
class Logger
|
7
|
+
class Bots
|
8
|
+
|
9
|
+
DATA_URL = "http://www.user-agents.org/allagents.xml"
|
10
|
+
FILE_PATH = "vendor/mongoid_traffic/allagents.xml"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def list
|
14
|
+
@list ||= begin
|
15
|
+
response = File.open(FILE_PATH).read
|
16
|
+
doc = Nokogiri::XML(response)
|
17
|
+
list = []
|
18
|
+
doc.xpath('//user-agent').each do |agent|
|
19
|
+
type = agent.xpath("Type").text
|
20
|
+
list << agent.xpath('String').text.gsub("<","<") if %w(R S).include?(type)
|
21
|
+
end
|
22
|
+
list
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_a_bot? referer
|
27
|
+
return false unless referer.present?
|
28
|
+
list.include?(referer)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MongoidTraffic
|
2
|
+
class Logger
|
3
|
+
class Browser
|
4
|
+
|
5
|
+
def initialize user_agent_string
|
6
|
+
@user_agent_string = user_agent_string
|
7
|
+
end
|
8
|
+
|
9
|
+
# ---------------------------------------------------------------------
|
10
|
+
|
11
|
+
def platform
|
12
|
+
user_agent.platform
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
user_agent.browser
|
17
|
+
end
|
18
|
+
|
19
|
+
def version
|
20
|
+
user_agent.version.to_s.split('.')[0..1].join('.')
|
21
|
+
end
|
22
|
+
|
23
|
+
private # =============================================================
|
24
|
+
|
25
|
+
def user_agent
|
26
|
+
@user_agent ||= ::UserAgent.parse(@user_agent_string)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'geoip'
|
2
|
+
|
3
|
+
module MongoidTraffic
|
4
|
+
class Logger
|
5
|
+
class GeoIp
|
6
|
+
|
7
|
+
DATA_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"
|
8
|
+
FILE_URL = "vendor/mongoid_traffic/GeoIP.dat"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def geoip
|
12
|
+
@geoip ||= ::GeoIP.new(FILE_URL)
|
13
|
+
end
|
14
|
+
|
15
|
+
def country_code2 str
|
16
|
+
geoip.country(str).country_code2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module MongoidTraffic
|
4
|
+
class Logger
|
5
|
+
class Referer
|
6
|
+
|
7
|
+
def initialize referer_string
|
8
|
+
@referer_string = referer_string
|
9
|
+
end
|
10
|
+
|
11
|
+
def host
|
12
|
+
uri.host
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@referer_string
|
17
|
+
end
|
18
|
+
|
19
|
+
private # =============================================================
|
20
|
+
|
21
|
+
def uri
|
22
|
+
URI.parse(@referer_string)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'geoip'
|
2
|
+
require 'uri'
|
3
|
+
require 'useragent'
|
4
|
+
|
5
|
+
require_relative './log'
|
6
|
+
require_relative './logger/bots'
|
7
|
+
require_relative './logger/browser'
|
8
|
+
require_relative './logger/geo_ip'
|
9
|
+
require_relative './logger/referer'
|
10
|
+
|
11
|
+
module MongoidTraffic
|
12
|
+
class Logger
|
13
|
+
|
14
|
+
TIME_SCOPE_OPTIONS = %i(year month week day)
|
15
|
+
|
16
|
+
# ---------------------------------------------------------------------
|
17
|
+
|
18
|
+
def self.log *args
|
19
|
+
new(*args).log
|
20
|
+
end
|
21
|
+
|
22
|
+
# ---------------------------------------------------------------------
|
23
|
+
|
24
|
+
def initialize ip_address: nil, referer: nil, scope: nil, time_scope: %i(month day), unique_id: nil, user_agent: nil
|
25
|
+
@ip_address = ip_address
|
26
|
+
@referer_string = referer
|
27
|
+
@scope = scope
|
28
|
+
@time_scope = time_scope
|
29
|
+
@unique_id = unique_id
|
30
|
+
@user_agent_string = user_agent
|
31
|
+
end
|
32
|
+
|
33
|
+
def log
|
34
|
+
return if Bots.is_a_bot?(@referer_string)
|
35
|
+
raise "Invalid time scope definition: #{@time_scope}" unless @time_scope.all?{ |ts| TIME_SCOPE_OPTIONS.include?(ts) }
|
36
|
+
|
37
|
+
@time_scope.each do |ts|
|
38
|
+
Log.collection.find( find_query(ts) ).upsert( upsert_query )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# ---------------------------------------------------------------------
|
43
|
+
|
44
|
+
def upsert_query
|
45
|
+
{
|
46
|
+
'$inc' => access_count_query.
|
47
|
+
merge(browser_query).
|
48
|
+
merge(country_query).
|
49
|
+
merge(referer_query).
|
50
|
+
merge(unique_id_query),
|
51
|
+
'$set' => { uat: Time.now }
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
# ---------------------------------------------------------------------
|
56
|
+
|
57
|
+
def access_count_query
|
58
|
+
{ ac: 1 }
|
59
|
+
end
|
60
|
+
|
61
|
+
def browser_query
|
62
|
+
return {} unless browser.present?
|
63
|
+
browser_path = [browser.platform, browser.name, browser.version].map{ |s| escape_key(s) }.join('.')
|
64
|
+
{ "b.#{browser_path}" => 1 }
|
65
|
+
end
|
66
|
+
|
67
|
+
def country_query
|
68
|
+
return {} unless @ip_address.present?
|
69
|
+
return {} unless country_code2 = GeoIp.country_code2(@ip_address)
|
70
|
+
country_code_key = escape_key(country_code2)
|
71
|
+
{ "c.#{country_code_key}" => 1 }
|
72
|
+
end
|
73
|
+
|
74
|
+
def referer_query
|
75
|
+
return {} unless referer.present?
|
76
|
+
referer_key = escape_key(referer.to_s)
|
77
|
+
{ "r.#{referer_key}" => 1 }
|
78
|
+
end
|
79
|
+
|
80
|
+
def unique_id_query
|
81
|
+
return {} unless @unique_id.present?
|
82
|
+
unique_id_key = escape_key(@unique_id.to_s)
|
83
|
+
{ "u.#{unique_id_key}" => 1 }
|
84
|
+
end
|
85
|
+
|
86
|
+
# ---------------------------------------------------------------------
|
87
|
+
|
88
|
+
def escape_key key
|
89
|
+
CGI::escape(key).gsub('.', '%2E')
|
90
|
+
end
|
91
|
+
|
92
|
+
# ---------------------------------------------------------------------
|
93
|
+
|
94
|
+
def find_query ts
|
95
|
+
res = time_query(ts)
|
96
|
+
res = res.merge(scope_query) if @scope.present?
|
97
|
+
res
|
98
|
+
end
|
99
|
+
|
100
|
+
def scope_query
|
101
|
+
{ s: @scope }
|
102
|
+
end
|
103
|
+
|
104
|
+
def time_query ts
|
105
|
+
date = Date.today
|
106
|
+
case ts
|
107
|
+
when :day then { df: date, dt: date }
|
108
|
+
else { df: date.send("at_beginning_of_#{ts}"), dt: date.send("at_end_of_#{ts}") }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private # =============================================================
|
113
|
+
|
114
|
+
def browser
|
115
|
+
return unless @user_agent_string.present?
|
116
|
+
@browser ||= Browser.new(@user_agent_string)
|
117
|
+
end
|
118
|
+
|
119
|
+
def referer
|
120
|
+
return unless @referer_string.present?
|
121
|
+
@referer ||= Referer.new(@referer_string)
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongoid_traffic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mongoid_traffic"
|
8
|
+
spec.version = MongoidTraffic::VERSION
|
9
|
+
spec.authors = ["Tomas Celizna"]
|
10
|
+
spec.email = ["tomas.celizna@gmail.com"]
|
11
|
+
spec.description = %q{Aggregated traffic logs stored in MongoDB.}
|
12
|
+
spec.summary = %q{Aggregated traffic logs stored in MongoDB.}
|
13
|
+
spec.homepage = "https://github.com/tomasc/mongoid_traffic"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "geoip"
|
22
|
+
spec.add_dependency "mongoid", "~> 4.0"
|
23
|
+
spec.add_dependency "nokogiri"
|
24
|
+
spec.add_dependency "useragent", "~> 0.10.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "coveralls"
|
28
|
+
spec.add_development_dependency "database_cleaner"
|
29
|
+
spec.add_development_dependency "guard"
|
30
|
+
spec.add_development_dependency "guard-minitest"
|
31
|
+
spec.add_development_dependency "minitest"
|
32
|
+
spec.add_development_dependency "rake"
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require_relative '../../lib/mongoid_traffic/controller_additions'
|
4
|
+
|
5
|
+
module MongoidTraffic
|
6
|
+
describe 'ControllerAdditions' do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@controller_class = Class.new
|
10
|
+
@controller = @controller_class.new
|
11
|
+
def @controller_class.helper_method(name); end
|
12
|
+
@controller_class.send(:include, MongoidTraffic::ControllerAdditions)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.log_traffic' do
|
16
|
+
it 'logs with :user_agent'
|
17
|
+
it 'logs with :referer'
|
18
|
+
it 'logs with :ip_address'
|
19
|
+
it 'logs with :unique_id'
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.log_scoped_traffic' do
|
23
|
+
it 'infers scope from request path'
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require_relative '../../lib/mongoid_traffic/log'
|
4
|
+
|
5
|
+
module MongoidTraffic
|
6
|
+
describe 'Log' do
|
7
|
+
subject { Log.new }
|
8
|
+
|
9
|
+
describe 'fields' do
|
10
|
+
it 'has :scope' do
|
11
|
+
subject.must_respond_to :scope
|
12
|
+
end
|
13
|
+
it 'has :access_count' do
|
14
|
+
subject.must_respond_to :access_count
|
15
|
+
end
|
16
|
+
it 'has :browsers' do
|
17
|
+
subject.must_respond_to :browsers
|
18
|
+
subject.browsers.must_be_kind_of Hash
|
19
|
+
end
|
20
|
+
it 'has :referers' do
|
21
|
+
subject.must_respond_to :referers
|
22
|
+
subject.referers.must_be_kind_of Hash
|
23
|
+
end
|
24
|
+
it 'has :countries' do
|
25
|
+
subject.must_respond_to :countries
|
26
|
+
subject.countries.must_be_kind_of Hash
|
27
|
+
end
|
28
|
+
it 'has :unique_ids' do
|
29
|
+
subject.must_respond_to :unique_ids
|
30
|
+
subject.unique_ids.must_be_kind_of Hash
|
31
|
+
end
|
32
|
+
it 'has :updated_at' do
|
33
|
+
subject.must_respond_to :updated_at
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'scopes' do
|
38
|
+
it 'has :default_scope that assumes no :scope' do
|
39
|
+
Log.criteria.selector.fetch('s').must_be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it('has :for_dates') { Log.must_respond_to :for_dates }
|
43
|
+
it('has :yearly') { Log.must_respond_to :yearly }
|
44
|
+
it('has :monthly') { Log.must_respond_to :monthly }
|
45
|
+
it('has :weekly') { Log.must_respond_to :weekly }
|
46
|
+
it('has :daily') { Log.must_respond_to :daily }
|
47
|
+
it('has :scoped_to') { Log.must_respond_to :scoped_to }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.aggregate_on' do
|
51
|
+
let(:log_1) { Log.new(date_from: Date.today, date_to: Date.today) }
|
52
|
+
let(:log_2) { Log.new(date_from: Date.tomorrow, date_to: Date.tomorrow) }
|
53
|
+
|
54
|
+
describe '.aggregate_on(:access_count)' do
|
55
|
+
before do
|
56
|
+
log_1.tap{ |l| l.access_count = 1 }.save
|
57
|
+
log_2.tap{ |l| l.access_count = 2 }.save
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'sums the access_counts' do
|
61
|
+
Log.aggregate_on(:access_count).must_equal 3
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '.aggregate_on(:browsers)' do
|
66
|
+
# not the key names have been abbreviated
|
67
|
+
before do
|
68
|
+
log_1.tap do |l|
|
69
|
+
l.browsers = {
|
70
|
+
"Mac" => {
|
71
|
+
"Saf" => { "8" => 1 }
|
72
|
+
},
|
73
|
+
"Win" => {
|
74
|
+
"Saf" => { "7" => 5 }
|
75
|
+
}
|
76
|
+
}
|
77
|
+
end.save
|
78
|
+
|
79
|
+
log_2.tap do |l|
|
80
|
+
l.browsers = {
|
81
|
+
"Mac" => {
|
82
|
+
"Saf" => { "8" => 10, "7" => 100 },
|
83
|
+
"Chr" => { "3" => 5 }
|
84
|
+
},
|
85
|
+
"Win" => {
|
86
|
+
"Saf" => { "7" => 100 },
|
87
|
+
"IE" => { "10" => 1 }
|
88
|
+
}
|
89
|
+
}
|
90
|
+
end.save
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'sums the browsers' do
|
94
|
+
Log.aggregate_on(:browsers).must_equal({
|
95
|
+
"Mac" => {
|
96
|
+
"Saf" => { "8" => 11, "7" => 100 },
|
97
|
+
"Chr" => { "3" => 5 }
|
98
|
+
},
|
99
|
+
"Win" => {
|
100
|
+
"Saf" => { "7" => 105 },
|
101
|
+
"IE" => { "10" => 1 }
|
102
|
+
}
|
103
|
+
})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '.aggregate_on(:referers)' do
|
108
|
+
before do
|
109
|
+
log_1.tap{ |l| l.referers = { 'google' => 100, 'apple' => 1000 } }.save
|
110
|
+
log_2.tap{ |l| l.referers = { 'google' => 10, 'apple' => 100, 'ms' => 1 } }.save
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'sums the referers' do
|
114
|
+
Log.aggregate_on(:referers).must_equal({ 'google' => 110, 'apple' => 1100, 'ms' => 1 })
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '.aggregate_on(:countries)' do
|
119
|
+
before do
|
120
|
+
log_1.tap{ |l| l.countries = { 'CZ' => 100 } }.save
|
121
|
+
log_2.tap{ |l| l.countries = { 'DE' => 10 } }.save
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'sums the countries' do
|
125
|
+
Log.aggregate_on(:countries).must_equal({ 'CZ' => 100, 'DE' => 10 })
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '.aggregate_on(:unique_ids)' do
|
130
|
+
before do
|
131
|
+
log_1.tap{ |l| l.unique_ids = { '01234' => 100, '56789' => 100 } }.save
|
132
|
+
log_2.tap{ |l| l.unique_ids = { '56789' => 100 } }.save
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'sums the unique_ids' do
|
136
|
+
Log.aggregate_on(:unique_ids).must_equal({ '01234' => 100, '56789' => 200 })
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '.sum(:unique_ids)' do
|
141
|
+
before do
|
142
|
+
log_1.tap{ |l| l.unique_ids = { '01234' => 100, '56789' => 100 } }.save
|
143
|
+
log_2.tap{ |l| l.unique_ids = { '56789' => 100, 'ABCDE' => 1 } }.save
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'sums the unique_ids' do
|
147
|
+
Log.sum(:unique_ids).must_equal 3
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|