mongoid_traffic 0.0.1
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.
- 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
|