ahoy_matey 0.3.2 → 1.0.0
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 +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
@@ -0,0 +1,63 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Stores
|
3
|
+
class ActiveRecordStore < BaseStore
|
4
|
+
|
5
|
+
def track_visit(options, &block)
|
6
|
+
visit =
|
7
|
+
visit_model.new do |v|
|
8
|
+
v.id = ahoy.visit_id
|
9
|
+
v.visitor_id = ahoy.visitor_id
|
10
|
+
v.user = user if v.respond_to?(:user=)
|
11
|
+
v.started_at = options[:started_at]
|
12
|
+
end
|
13
|
+
|
14
|
+
visit_properties.keys.each do |key|
|
15
|
+
visit.send(:"#{key}=", visit_properties[key]) if visit.respond_to?(:"#{key}=")
|
16
|
+
end
|
17
|
+
|
18
|
+
yield(visit) if block_given?
|
19
|
+
|
20
|
+
begin
|
21
|
+
visit.save!
|
22
|
+
rescue ActiveRecord::RecordNotUnique
|
23
|
+
# do nothing
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def track_event(name, properties, options, &block)
|
28
|
+
event =
|
29
|
+
event_model.new do |e|
|
30
|
+
e.id = options[:id]
|
31
|
+
e.visit_id = ahoy.visit_id
|
32
|
+
e.user = user
|
33
|
+
e.name = name
|
34
|
+
e.properties = properties
|
35
|
+
e.time = options[:time]
|
36
|
+
end
|
37
|
+
|
38
|
+
yield(event) if block_given?
|
39
|
+
|
40
|
+
begin
|
41
|
+
event.save!
|
42
|
+
rescue ActiveRecord::RecordNotUnique
|
43
|
+
# do nothing
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit
|
48
|
+
@visit ||= visit_model.where(id: ahoy.visit_id).first if ahoy.visit_id
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def visit_model
|
54
|
+
::Visit
|
55
|
+
end
|
56
|
+
|
57
|
+
def event_model
|
58
|
+
::Ahoy::Event
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Stores
|
3
|
+
class ActiveRecordTokenStore < BaseStore
|
4
|
+
|
5
|
+
def track_visit(options, &block)
|
6
|
+
visit =
|
7
|
+
visit_model.new do |v|
|
8
|
+
v.visit_token = ahoy.visit_token
|
9
|
+
v.visitor_token = ahoy.visitor_token
|
10
|
+
v.user = user if v.respond_to?(:user=)
|
11
|
+
v.created_at = options[:started_at]
|
12
|
+
end
|
13
|
+
|
14
|
+
visit_properties.keys.each do |key|
|
15
|
+
visit.send(:"#{key}=", visit_properties[key]) if visit.respond_to?(:"#{key}=")
|
16
|
+
end
|
17
|
+
|
18
|
+
yield(visit) if block_given?
|
19
|
+
|
20
|
+
begin
|
21
|
+
visit.save!
|
22
|
+
rescue ActiveRecord::RecordNotUnique
|
23
|
+
# do nothing
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def track_event(name, properties, options, &block)
|
28
|
+
if self.class.uses_deprecated_subscribers?
|
29
|
+
options[:controller] ||= controller
|
30
|
+
options[:user] ||= user
|
31
|
+
options[:visit] ||= visit
|
32
|
+
options[:visit_token] ||= ahoy.visit_token
|
33
|
+
options[:visitor_token] ||= ahoy.visitor_token
|
34
|
+
|
35
|
+
subscribers = Ahoy.subscribers
|
36
|
+
if subscribers.any?
|
37
|
+
subscribers.each do |subscriber|
|
38
|
+
subscriber.track(name, properties, options.dup)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
$stderr.puts "No subscribers"
|
42
|
+
end
|
43
|
+
else
|
44
|
+
event =
|
45
|
+
event_model.new do |e|
|
46
|
+
e.visit_id = visit.try(:id)
|
47
|
+
e.user = user
|
48
|
+
e.name = name
|
49
|
+
e.properties = properties
|
50
|
+
e.time = options[:time]
|
51
|
+
end
|
52
|
+
|
53
|
+
yield(event) if block_given?
|
54
|
+
|
55
|
+
event.save!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit
|
60
|
+
@visit ||= visit_model.where(visit_token: ahoy.visit_token).first if ahoy.visit_token
|
61
|
+
end
|
62
|
+
|
63
|
+
def exclude?
|
64
|
+
(!Ahoy.track_bots && bot?) ||
|
65
|
+
(
|
66
|
+
if Ahoy.exclude_method
|
67
|
+
warn "[DEPRECATION] Ahoy.exclude_method is deprecated - use exclude? instead"
|
68
|
+
if Ahoy.exclude_method.arity == 1
|
69
|
+
Ahoy.exclude_method.call(controller)
|
70
|
+
else
|
71
|
+
Ahoy.exclude_method.call(controller, request)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
false
|
75
|
+
end
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def user
|
80
|
+
user_method = Ahoy.user_method
|
81
|
+
if user_method.respond_to?(:call)
|
82
|
+
user_method.call(controller)
|
83
|
+
else
|
84
|
+
controller.send(user_method)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
|
90
|
+
def uses_deprecated_subscribers
|
91
|
+
warn "[DEPRECATION] Ahoy subscribers are deprecated"
|
92
|
+
@uses_deprecated_subscribers = true
|
93
|
+
end
|
94
|
+
|
95
|
+
def uses_deprecated_subscribers?
|
96
|
+
@uses_deprecated_subscribers || false
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def visit_model
|
104
|
+
Ahoy.visit_model || ::Visit
|
105
|
+
end
|
106
|
+
|
107
|
+
def event_model
|
108
|
+
::Ahoy::Event
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Stores
|
3
|
+
class BaseStore
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def track_visit(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def track_event(name, properties, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit
|
16
|
+
end
|
17
|
+
|
18
|
+
def authenticate(user)
|
19
|
+
@user = user
|
20
|
+
if visit and visit.respond_to?(:user) and !visit.user
|
21
|
+
visit.user = user
|
22
|
+
visit.save!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def report_exception(e)
|
27
|
+
end
|
28
|
+
|
29
|
+
def user
|
30
|
+
@user ||= controller.current_user
|
31
|
+
end
|
32
|
+
|
33
|
+
def exclude?
|
34
|
+
bot?
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_id
|
38
|
+
SecureRandom.uuid
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def bot?
|
44
|
+
@bot ||= Browser.new(ua: request.user_agent).bot?
|
45
|
+
end
|
46
|
+
|
47
|
+
def request
|
48
|
+
@request ||= @options[:request] || controller.try(:request)
|
49
|
+
end
|
50
|
+
|
51
|
+
def controller
|
52
|
+
@controller ||= @options[:controller]
|
53
|
+
end
|
54
|
+
|
55
|
+
def ahoy
|
56
|
+
@ahoy ||= @options[:ahoy]
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_properties
|
60
|
+
ahoy.visit_properties
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Stores
|
3
|
+
class LogStore < BaseStore
|
4
|
+
|
5
|
+
def track_visit(options, &block)
|
6
|
+
data = {
|
7
|
+
id: ahoy.visit_id,
|
8
|
+
visitor_id: ahoy.visitor_id,
|
9
|
+
}.merge(visit_properties.to_hash)
|
10
|
+
data[:user_id] = user.id if user
|
11
|
+
data[:started_at] = options[:started_at]
|
12
|
+
|
13
|
+
yield(data) if block_given?
|
14
|
+
|
15
|
+
visit_logger.info data.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
def track_event(name, properties, options, &block)
|
19
|
+
data = {
|
20
|
+
id: options[:id],
|
21
|
+
name: name,
|
22
|
+
properties: properties,
|
23
|
+
visit_id: ahoy.visit_id,
|
24
|
+
visitor_id: ahoy.visitor_id
|
25
|
+
}
|
26
|
+
data[:user_id] = user.id if user
|
27
|
+
data[:time] = options[:time]
|
28
|
+
|
29
|
+
yield(data) if block_given?
|
30
|
+
|
31
|
+
event_logger.info data.to_json
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
# TODO disable header
|
37
|
+
def visit_logger
|
38
|
+
@visit_logger ||= ActiveSupport::Logger.new(Rails.root.join("log/visits.log"))
|
39
|
+
end
|
40
|
+
|
41
|
+
def event_logger
|
42
|
+
@event_logger ||= ActiveSupport::Logger.new(Rails.root.join("log/events.log"))
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Ahoy
|
2
|
+
module Stores
|
3
|
+
class MongoidStore < BaseStore
|
4
|
+
|
5
|
+
def track_visit(options, &block)
|
6
|
+
visit =
|
7
|
+
visit_model.new do |v|
|
8
|
+
v.id = binary(ahoy.visit_id)
|
9
|
+
v.visitor_id = binary(ahoy.visitor_id)
|
10
|
+
v.user = user if v.respond_to?(:user=) && user
|
11
|
+
v.started_at = options[:started_at]
|
12
|
+
end
|
13
|
+
|
14
|
+
visit_properties.keys.each do |key|
|
15
|
+
visit.send(:"#{key}=", visit_properties[key]) if visit.respond_to?(:"#{key}=") && visit_properties[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
yield(visit) if block_given?
|
19
|
+
|
20
|
+
visit.upsert
|
21
|
+
end
|
22
|
+
|
23
|
+
def track_event(name, properties, options, &block)
|
24
|
+
event =
|
25
|
+
event_model.new do |e|
|
26
|
+
e.id = binary(options[:id])
|
27
|
+
e.visit_id = binary(ahoy.visit_id)
|
28
|
+
e.user = user if e.respond_to?(:user)
|
29
|
+
e.name = name
|
30
|
+
e.properties = properties
|
31
|
+
e.time = options[:time]
|
32
|
+
end
|
33
|
+
|
34
|
+
yield(event) if block_given?
|
35
|
+
|
36
|
+
event.upsert
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit
|
40
|
+
@visit ||= visit_model.where(_id: binary(ahoy.visit_id)).first if ahoy.visit_id
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def visit_model
|
46
|
+
::Visit
|
47
|
+
end
|
48
|
+
|
49
|
+
def event_model
|
50
|
+
::Ahoy::Event
|
51
|
+
end
|
52
|
+
|
53
|
+
def binary(token)
|
54
|
+
::BSON::Binary.new(token.delete("-"), :uuid)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/ahoy/tracker.rb
CHANGED
@@ -1,103 +1,144 @@
|
|
1
1
|
module Ahoy
|
2
2
|
class Tracker
|
3
|
+
attr_reader :request, :controller
|
3
4
|
|
4
5
|
def initialize(options = {})
|
6
|
+
@store = Ahoy::Store.new(options.merge(ahoy: self))
|
5
7
|
@controller = options[:controller]
|
6
8
|
@request = options[:request] || @controller.try(:request)
|
9
|
+
@options = options
|
7
10
|
end
|
8
11
|
|
9
12
|
def track(name, properties = {}, options = {})
|
10
|
-
|
11
|
-
# publish to each subscriber
|
13
|
+
unless exclude?
|
12
14
|
options = options.dup
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
options[:
|
27
|
-
|
28
|
-
|
29
|
-
subscribers = Ahoy.subscribers
|
30
|
-
if subscribers.any?
|
31
|
-
subscribers.each do |subscriber|
|
32
|
-
subscriber.track(name, properties, options)
|
33
|
-
end
|
15
|
+
|
16
|
+
options[:time] = trusted_time(options[:time])
|
17
|
+
options[:id] = ensure_uuid(options[:id] || generate_id)
|
18
|
+
|
19
|
+
@store.track_event(name, properties, options)
|
20
|
+
end
|
21
|
+
true
|
22
|
+
rescue => e
|
23
|
+
report_exception(e)
|
24
|
+
end
|
25
|
+
|
26
|
+
def track_visit(options = {})
|
27
|
+
unless exclude?
|
28
|
+
if options[:defer]
|
29
|
+
set_cookie("ahoy_track", true)
|
34
30
|
else
|
35
|
-
|
31
|
+
options = options.dup
|
32
|
+
|
33
|
+
options[:started_at] ||= Time.zone.now
|
34
|
+
|
35
|
+
@store.track_visit(options)
|
36
36
|
end
|
37
37
|
end
|
38
|
+
true
|
39
|
+
rescue => e
|
40
|
+
report_exception(e)
|
41
|
+
end
|
38
42
|
|
43
|
+
def authenticate(user)
|
44
|
+
unless exclude?
|
45
|
+
@store.authenticate(user)
|
46
|
+
end
|
39
47
|
true
|
48
|
+
rescue => e
|
49
|
+
report_exception(e)
|
40
50
|
end
|
41
51
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
rescue ActiveRecord::RecordNotUnique
|
66
|
-
# do nothing
|
67
|
-
end
|
52
|
+
def visit
|
53
|
+
@visit ||= @store.visit
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_id
|
57
|
+
@visit_id ||= ensure_uuid(existing_visit_id || visit_token)
|
58
|
+
end
|
59
|
+
|
60
|
+
def visitor_id
|
61
|
+
@visitor_id ||= ensure_uuid(existing_visitor_id || visitor_token)
|
62
|
+
end
|
63
|
+
|
64
|
+
def new_visit?
|
65
|
+
!existing_visit_id
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_visit_cookie
|
69
|
+
set_cookie("ahoy_visit", visit_id, Ahoy.visit_duration)
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_visitor_cookie
|
73
|
+
if !existing_visitor_id
|
74
|
+
set_cookie("ahoy_visitor", visitor_id, Ahoy.visitor_duration)
|
68
75
|
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def user
|
79
|
+
@user ||= @store.user
|
80
|
+
end
|
69
81
|
|
70
|
-
|
82
|
+
# TODO better name
|
83
|
+
def visit_properties
|
84
|
+
@visit_properties ||= Ahoy::VisitProperties.new(request, @options.slice(:api))
|
85
|
+
end
|
86
|
+
|
87
|
+
# for ActiveRecordTokenStore only - do not use
|
88
|
+
def visit_token
|
89
|
+
@visit_token ||= existing_visit_id || (@options[:api] && request.params["visit_token"]) || generate_id
|
90
|
+
end
|
91
|
+
|
92
|
+
# for ActiveRecordTokenStore only - do not use
|
93
|
+
def visitor_token
|
94
|
+
@visitor_token ||= existing_visitor_id || (@options[:api] && request.params["visitor_token"]) || generate_id
|
71
95
|
end
|
72
96
|
|
73
97
|
protected
|
74
98
|
|
75
|
-
def
|
76
|
-
|
99
|
+
def set_cookie(name, value, duration = nil)
|
100
|
+
cookie = {
|
101
|
+
value: value
|
102
|
+
}
|
103
|
+
cookie[:expires] = duration.from_now if duration
|
104
|
+
domain = Ahoy.cookie_domain || Ahoy.domain
|
105
|
+
cookie[:domain] = domain if domain
|
106
|
+
request.cookie_jar[name] = cookie
|
77
107
|
end
|
78
108
|
|
79
|
-
def
|
80
|
-
|
109
|
+
def trusted_time(time)
|
110
|
+
if !time or (@options[:api] and !(1.minute.ago..Time.now).cover?(time))
|
111
|
+
Time.zone.now
|
112
|
+
else
|
113
|
+
time
|
114
|
+
end
|
81
115
|
end
|
82
116
|
|
83
117
|
def exclude?
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
false
|
118
|
+
@store.exclude?
|
119
|
+
end
|
120
|
+
|
121
|
+
def report_exception(e)
|
122
|
+
@store.report_exception(e)
|
123
|
+
if Rails.env.development?
|
124
|
+
raise e
|
92
125
|
end
|
93
126
|
end
|
94
127
|
|
95
|
-
def
|
96
|
-
@
|
128
|
+
def generate_id
|
129
|
+
@store.generate_id
|
130
|
+
end
|
131
|
+
|
132
|
+
def existing_visit_id
|
133
|
+
@existing_visit_id ||= request.headers["Ahoy-Visit"] || request.cookies["ahoy_visit"]
|
134
|
+
end
|
135
|
+
|
136
|
+
def existing_visitor_id
|
137
|
+
@existing_visitor_id ||= request.headers["Ahoy-Visitor"] || request.cookies["ahoy_visitor"]
|
97
138
|
end
|
98
139
|
|
99
|
-
def
|
100
|
-
|
140
|
+
def ensure_uuid(id)
|
141
|
+
Ahoy.ensure_uuid(id)
|
101
142
|
end
|
102
143
|
|
103
144
|
end
|