ahoy_matey 0.3.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +324 -189
- data/ahoy_matey.gemspec +1 -0
- data/app/controllers/ahoy/base_controller.rb +5 -0
- data/app/controllers/ahoy/events_controller.rb +9 -7
- data/app/controllers/ahoy/visits_controller.rb +2 -1
- data/lib/ahoy.rb +37 -28
- data/lib/ahoy/controller.rb +12 -20
- data/lib/ahoy/deckhands/location_deckhand.rb +39 -0
- data/lib/ahoy/deckhands/request_deckhand.rb +41 -0
- data/lib/ahoy/deckhands/technology_deckhand.rb +49 -0
- data/lib/ahoy/deckhands/traffic_source_deckhand.rb +24 -0
- data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +24 -0
- data/lib/ahoy/engine.rb +3 -1
- data/lib/ahoy/model.rb +19 -82
- data/lib/ahoy/stores/active_record_store.rb +63 -0
- data/lib/ahoy/stores/active_record_token_store.rb +113 -0
- data/lib/ahoy/stores/base_store.rb +65 -0
- data/lib/ahoy/stores/log_store.rb +47 -0
- data/lib/ahoy/stores/mongoid_store.rb +59 -0
- data/lib/ahoy/tracker.rb +108 -67
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +58 -0
- data/lib/ahoy/warden.rb +1 -10
- data/lib/generators/ahoy/stores/active_record_events_generator.rb +44 -0
- data/lib/generators/ahoy/stores/active_record_generator.rb +17 -0
- data/lib/generators/ahoy/{events/active_record_generator.rb → stores/active_record_visits_generator.rb} +14 -6
- data/lib/generators/ahoy/stores/custom_generator.rb +16 -0
- data/lib/generators/ahoy/stores/log_generator.rb +16 -0
- data/lib/generators/ahoy/stores/mongoid_events_generator.rb +20 -0
- data/lib/generators/ahoy/stores/mongoid_generator.rb +16 -0
- data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +20 -0
- data/{app/models/ahoy/event.rb → lib/generators/ahoy/stores/templates/active_record_event_model.rb} +2 -2
- data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +20 -0
- data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +4 -0
- data/lib/generators/ahoy/{templates/install.rb → stores/templates/active_record_visits_migration.rb} +6 -8
- data/lib/generators/ahoy/stores/templates/custom_initializer.rb +12 -0
- data/lib/generators/ahoy/stores/templates/log_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/mongoid_event_model.rb +12 -0
- data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +3 -0
- data/lib/generators/ahoy/stores/templates/mongoid_visit_model.rb +41 -0
- data/vendor/assets/javascripts/ahoy.js +19 -8
- metadata +45 -8
- data/lib/generators/ahoy/events/templates/create_events.rb +0 -20
- data/lib/generators/ahoy/events/templates/initializer.rb +0 -1
- data/lib/generators/ahoy/install_generator.rb +0 -38
data/ahoy_matey.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency "referer-parser"
|
25
25
|
spec.add_dependency "user_agent_parser"
|
26
26
|
spec.add_dependency "request_store"
|
27
|
+
spec.add_dependency "uuidtools"
|
27
28
|
|
28
29
|
spec.add_development_dependency "bundler", "~> 1.5"
|
29
30
|
spec.add_development_dependency "rake"
|
@@ -4,13 +4,15 @@ module Ahoy
|
|
4
4
|
def create
|
5
5
|
events = params[:name] ? [params] : ActiveSupport::JSON.decode(request.body.read)
|
6
6
|
events.each do |event|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
time = Time.zone.parse(event["time"]) rescue nil
|
8
|
+
|
9
|
+
# timestamp is deprecated
|
10
|
+
time ||= Time.zone.at(event["time"].to_f) rescue nil
|
11
|
+
|
12
|
+
options = {
|
13
|
+
id: event["id"],
|
14
|
+
time: time
|
15
|
+
}
|
14
16
|
ahoy.track event["name"], event["properties"], options
|
15
17
|
end
|
16
18
|
render json: {}
|
data/lib/ahoy.rb
CHANGED
@@ -4,47 +4,61 @@ require "geocoder"
|
|
4
4
|
require "referer-parser"
|
5
5
|
require "user_agent_parser"
|
6
6
|
require "request_store"
|
7
|
+
require "uuidtools"
|
8
|
+
|
7
9
|
require "ahoy/version"
|
8
10
|
require "ahoy/tracker"
|
9
11
|
require "ahoy/controller"
|
10
12
|
require "ahoy/model"
|
11
|
-
require "ahoy/
|
13
|
+
require "ahoy/visit_properties"
|
14
|
+
require "ahoy/deckhands/location_deckhand"
|
15
|
+
require "ahoy/deckhands/request_deckhand"
|
16
|
+
require "ahoy/deckhands/technology_deckhand"
|
17
|
+
require "ahoy/deckhands/traffic_source_deckhand"
|
18
|
+
require "ahoy/deckhands/utm_parameter_deckhand"
|
19
|
+
require "ahoy/stores/base_store"
|
20
|
+
require "ahoy/stores/active_record_store"
|
21
|
+
require "ahoy/stores/active_record_token_store"
|
22
|
+
require "ahoy/stores/log_store"
|
23
|
+
require "ahoy/stores/mongoid_store"
|
12
24
|
require "ahoy/engine"
|
13
25
|
require "ahoy/warden" if defined?(Warden)
|
14
26
|
|
27
|
+
# deprecated
|
28
|
+
require "ahoy/subscribers/active_record"
|
29
|
+
|
15
30
|
module Ahoy
|
31
|
+
UUID_NAMESPACE = UUIDTools::UUID.parse("a82ae811-5011-45ab-a728-569df7499c5f")
|
16
32
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
33
|
+
mattr_accessor :visit_duration
|
34
|
+
self.visit_duration = 4.hours
|
20
35
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
36
|
+
mattr_accessor :visitor_duration
|
37
|
+
self.visitor_duration = 2.years
|
24
38
|
|
25
|
-
|
26
|
-
@visit_model = visit_model
|
27
|
-
end
|
39
|
+
mattr_accessor :cookie_domain
|
28
40
|
|
29
|
-
|
30
|
-
|
31
|
-
def self.referrer_parser
|
32
|
-
@referrer_parser ||= RefererParser::Referer.new("https://github.com/ankane/ahoy")
|
33
|
-
end
|
41
|
+
mattr_accessor :track_visits_immediately
|
42
|
+
self.track_visits_immediately = false
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
@user_agent_parser ||= UserAgentParser::Parser.new
|
38
|
-
end
|
44
|
+
mattr_accessor :quiet
|
45
|
+
self.quiet = true
|
39
46
|
|
40
|
-
def self.
|
41
|
-
|
42
|
-
|
47
|
+
def self.ensure_uuid(id)
|
48
|
+
valid = UUIDTools::UUID.parse(id) rescue nil
|
49
|
+
if valid
|
50
|
+
id
|
43
51
|
else
|
44
|
-
|
52
|
+
UUIDTools::UUID.sha1_create(UUID_NAMESPACE, id).to_s
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
56
|
+
# deprecated
|
57
|
+
|
58
|
+
mattr_accessor :domain
|
59
|
+
|
60
|
+
mattr_accessor :visit_model
|
61
|
+
|
48
62
|
mattr_accessor :user_method
|
49
63
|
self.user_method = proc do |controller|
|
50
64
|
(controller.respond_to?(:current_user) && controller.current_user) || (controller.respond_to?(:current_resource_owner, true) && controller.send(:current_resource_owner)) || nil
|
@@ -57,11 +71,6 @@ module Ahoy
|
|
57
71
|
|
58
72
|
mattr_accessor :track_bots
|
59
73
|
self.track_bots = false
|
60
|
-
|
61
|
-
mattr_accessor :quiet
|
62
|
-
self.quiet = true
|
63
|
-
|
64
|
-
mattr_accessor :domain
|
65
74
|
end
|
66
75
|
|
67
76
|
ActionController::Base.send :include, Ahoy::Controller
|
data/lib/ahoy/controller.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
require "request_store"
|
2
|
+
|
1
3
|
module Ahoy
|
2
4
|
module Controller
|
3
5
|
|
4
6
|
def self.included(base)
|
5
7
|
base.helper_method :current_visit
|
6
8
|
base.helper_method :ahoy
|
7
|
-
base.before_filter :
|
9
|
+
base.before_filter :set_ahoy_cookies
|
10
|
+
base.before_filter :track_ahoy_visit
|
8
11
|
base.before_filter do
|
9
|
-
RequestStore.store[:
|
12
|
+
RequestStore.store[:ahoy] ||= ahoy
|
10
13
|
end
|
11
14
|
end
|
12
15
|
|
@@ -15,28 +18,17 @@ module Ahoy
|
|
15
18
|
end
|
16
19
|
|
17
20
|
def current_visit
|
18
|
-
|
19
|
-
if visit_token
|
20
|
-
@current_visit ||= Ahoy.visit_model.where(visit_token: visit_token).first
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def current_visit_token
|
25
|
-
@current_visit_token ||= request.headers["Ahoy-Visit"] || cookies[:ahoy_visit]
|
21
|
+
ahoy.visit
|
26
22
|
end
|
27
23
|
|
28
|
-
def
|
29
|
-
|
24
|
+
def set_ahoy_cookies
|
25
|
+
ahoy.set_visitor_cookie
|
26
|
+
ahoy.set_visit_cookie
|
30
27
|
end
|
31
28
|
|
32
|
-
def
|
33
|
-
if
|
34
|
-
|
35
|
-
value: current_visitor_token,
|
36
|
-
expires: 2.years.from_now
|
37
|
-
}
|
38
|
-
cookie[:domain] = Ahoy.domain if Ahoy.domain
|
39
|
-
cookies[:ahoy_visitor] = cookie
|
29
|
+
def track_ahoy_visit
|
30
|
+
if ahoy.new_visit?
|
31
|
+
ahoy.track_visit(defer: !Ahoy.track_visits_immediately)
|
40
32
|
end
|
41
33
|
end
|
42
34
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Deckhands
|
3
|
+
class LocationDeckhand
|
4
|
+
|
5
|
+
def initialize(ip)
|
6
|
+
@ip = ip
|
7
|
+
end
|
8
|
+
|
9
|
+
def country
|
10
|
+
location.try(:country).presence
|
11
|
+
end
|
12
|
+
|
13
|
+
def region
|
14
|
+
location.try(:state).presence
|
15
|
+
end
|
16
|
+
|
17
|
+
def city
|
18
|
+
location.try(:city).presence
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def location
|
24
|
+
if !@checked
|
25
|
+
@location =
|
26
|
+
begin
|
27
|
+
Geocoder.search(@ip).first
|
28
|
+
rescue => e
|
29
|
+
$stderr.puts e.message
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
@checked = true
|
33
|
+
end
|
34
|
+
@location
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Deckhands
|
3
|
+
class RequestDeckhand
|
4
|
+
attr_reader :request
|
5
|
+
|
6
|
+
def initialize(request, options = {})
|
7
|
+
@request = request
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def ip
|
12
|
+
request.remote_ip
|
13
|
+
end
|
14
|
+
|
15
|
+
def user_agent
|
16
|
+
request.user_agent
|
17
|
+
end
|
18
|
+
|
19
|
+
def referrer
|
20
|
+
@options[:api] ? request.params["referrer"] : request.referer
|
21
|
+
end
|
22
|
+
|
23
|
+
def landing_page
|
24
|
+
@options[:api] ? request.params["landing_page"] : request.original_url
|
25
|
+
end
|
26
|
+
|
27
|
+
def platform
|
28
|
+
request.params["platform"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def app_version
|
32
|
+
request.params["app_version"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def os_version
|
36
|
+
request.params["os_version"]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Deckhands
|
3
|
+
class TechnologyDeckhand
|
4
|
+
|
5
|
+
def initialize(user_agent)
|
6
|
+
@user_agent = user_agent
|
7
|
+
end
|
8
|
+
|
9
|
+
def browser
|
10
|
+
agent.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def os
|
14
|
+
agent.os.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def device_type
|
18
|
+
@device_type ||= begin
|
19
|
+
browser = Browser.new(ua: @user_agent)
|
20
|
+
if browser.bot?
|
21
|
+
"Bot"
|
22
|
+
elsif browser.tv?
|
23
|
+
"TV"
|
24
|
+
elsif browser.console?
|
25
|
+
"Console"
|
26
|
+
elsif browser.tablet?
|
27
|
+
"Tablet"
|
28
|
+
elsif browser.mobile?
|
29
|
+
"Mobile"
|
30
|
+
else
|
31
|
+
"Desktop"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def agent
|
39
|
+
@agent ||= self.class.user_agent_parser.parse(@user_agent)
|
40
|
+
end
|
41
|
+
|
42
|
+
# performance
|
43
|
+
def self.user_agent_parser
|
44
|
+
@user_agent_parser ||= UserAgentParser::Parser.new
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Deckhands
|
3
|
+
class TrafficSourceDeckhand
|
4
|
+
|
5
|
+
def initialize(referrer)
|
6
|
+
@referrer = referrer
|
7
|
+
end
|
8
|
+
|
9
|
+
def referring_domain
|
10
|
+
@referring_domain ||= Addressable::URI.parse(@referrer).host.first(255) rescue nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def search_keyword
|
14
|
+
@search_keyword ||= (self.class.referrer_parser.parse(@referrer)[1].first(255) rescue nil).presence
|
15
|
+
end
|
16
|
+
|
17
|
+
# performance hack for referer-parser
|
18
|
+
def self.referrer_parser
|
19
|
+
@referrer_parser ||= RefererParser::Referer.new("https://github.com/ankane/ahoy")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Deckhands
|
3
|
+
class UtmParameterDeckhand
|
4
|
+
|
5
|
+
def initialize(landing_page)
|
6
|
+
@landing_page = landing_page
|
7
|
+
end
|
8
|
+
|
9
|
+
def landing_params
|
10
|
+
@landing_params ||= begin
|
11
|
+
landing_uri = Addressable::URI.parse(@landing_page) rescue nil
|
12
|
+
(landing_uri && landing_uri.query_values) || {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[utm_source utm_medium utm_term utm_content utm_campaign].each do |name|
|
17
|
+
define_method name do
|
18
|
+
landing_params[name]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/ahoy/engine.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Ahoy
|
2
2
|
class Engine < ::Rails::Engine
|
3
|
+
|
3
4
|
# from https://github.com/evrone/quiet_assets/blob/master/lib/quiet_assets.rb
|
4
|
-
initializer "ahoy", after: "sprockets.environment" do |app|
|
5
|
+
initializer "ahoy.middleware", after: "sprockets.environment" do |app|
|
5
6
|
next unless Ahoy.quiet
|
6
7
|
|
7
8
|
# Parse PATH_INFO by assets prefix
|
@@ -24,5 +25,6 @@ module Ahoy
|
|
24
25
|
alias_method_chain :call, :quiet_ahoy
|
25
26
|
end
|
26
27
|
end
|
28
|
+
|
27
29
|
end
|
28
30
|
end
|
data/lib/ahoy/model.rb
CHANGED
@@ -1,87 +1,6 @@
|
|
1
1
|
module Ahoy
|
2
2
|
module Model
|
3
3
|
|
4
|
-
def ahoy_visit
|
5
|
-
class_eval do
|
6
|
-
|
7
|
-
belongs_to :user, polymorphic: true
|
8
|
-
|
9
|
-
before_create :set_traffic_source
|
10
|
-
before_create :set_utm_parameters
|
11
|
-
before_create :set_technology
|
12
|
-
before_create :set_location
|
13
|
-
|
14
|
-
def set_traffic_source
|
15
|
-
referring_domain = Addressable::URI.parse(referrer).host.first(255) rescue nil
|
16
|
-
self.referring_domain = referring_domain if respond_to?(:referring_domain=)
|
17
|
-
# performance hack for referer-parser
|
18
|
-
search_keyword = Ahoy.referrer_parser.parse(referrer)[1].first(255) rescue nil
|
19
|
-
self.search_keyword = search_keyword.presence if respond_to?(:search_keyword=)
|
20
|
-
true
|
21
|
-
end
|
22
|
-
|
23
|
-
def set_utm_parameters
|
24
|
-
%w[utm_source utm_medium utm_term utm_content utm_campaign].each do |name|
|
25
|
-
self[name] = landing_params[name] if respond_to?(:"#{name}=")
|
26
|
-
end
|
27
|
-
true
|
28
|
-
end
|
29
|
-
|
30
|
-
def set_technology
|
31
|
-
if respond_to?(:user_agent)
|
32
|
-
agent = Ahoy.user_agent_parser.parse(user_agent)
|
33
|
-
|
34
|
-
self.browser = agent.name if respond_to?(:browser=)
|
35
|
-
self.os = agent.os.name if respond_to?(:os=)
|
36
|
-
|
37
|
-
browser = Browser.new(ua: user_agent)
|
38
|
-
self.device_type =
|
39
|
-
if browser.bot?
|
40
|
-
"Bot"
|
41
|
-
elsif browser.tv?
|
42
|
-
"TV"
|
43
|
-
elsif browser.console?
|
44
|
-
"Console"
|
45
|
-
elsif browser.tablet?
|
46
|
-
"Tablet"
|
47
|
-
elsif browser.mobile?
|
48
|
-
"Mobile"
|
49
|
-
else
|
50
|
-
"Desktop"
|
51
|
-
end if respond_to?(:device_type=)
|
52
|
-
end
|
53
|
-
true
|
54
|
-
end
|
55
|
-
|
56
|
-
def set_location
|
57
|
-
if respond_to?(:ip) and [:country=, :region=, :city=].any?{|method| respond_to?(method) }
|
58
|
-
location =
|
59
|
-
begin
|
60
|
-
Geocoder.search(ip).first
|
61
|
-
rescue => e
|
62
|
-
$stderr.puts e.message
|
63
|
-
nil
|
64
|
-
end
|
65
|
-
|
66
|
-
if location
|
67
|
-
self.country = location.country.presence if respond_to?(:country=)
|
68
|
-
self.region = location.state.presence if respond_to?(:region=)
|
69
|
-
self.city = location.city.presence if respond_to?(:city=)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
true
|
73
|
-
end
|
74
|
-
|
75
|
-
def landing_params
|
76
|
-
@landing_params ||= begin
|
77
|
-
landing_uri = Addressable::URI.parse(landing_page) rescue nil
|
78
|
-
ActiveSupport::HashWithIndifferentAccess.new((landing_uri && landing_uri.query_values) || {})
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
end # end class_eval
|
83
|
-
end
|
84
|
-
|
85
4
|
def visitable(name = nil, options = {})
|
86
5
|
if name.is_a?(Hash)
|
87
6
|
name = nil
|
@@ -94,10 +13,28 @@ module Ahoy
|
|
94
13
|
end
|
95
14
|
class_eval %Q{
|
96
15
|
def set_visit
|
97
|
-
self.#{name} ||= RequestStore.store[:
|
16
|
+
self.#{name} ||= RequestStore.store[:ahoy].visit
|
98
17
|
end
|
99
18
|
}
|
100
19
|
end
|
101
20
|
|
21
|
+
# deprecated
|
22
|
+
|
23
|
+
def ahoy_visit
|
24
|
+
class_eval do
|
25
|
+
warn "[DEPRECATION] ahoy_visit is deprecated"
|
26
|
+
|
27
|
+
belongs_to :user, polymorphic: true
|
28
|
+
|
29
|
+
def landing_params
|
30
|
+
@landing_params ||= begin
|
31
|
+
warn "[DEPRECATION] landing_params is deprecated"
|
32
|
+
Deckhands::UtmParameterDeckhand.new(landing_page).landing_params
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
102
39
|
end
|
103
40
|
end
|