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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +324 -189
  4. data/ahoy_matey.gemspec +1 -0
  5. data/app/controllers/ahoy/base_controller.rb +5 -0
  6. data/app/controllers/ahoy/events_controller.rb +9 -7
  7. data/app/controllers/ahoy/visits_controller.rb +2 -1
  8. data/lib/ahoy.rb +37 -28
  9. data/lib/ahoy/controller.rb +12 -20
  10. data/lib/ahoy/deckhands/location_deckhand.rb +39 -0
  11. data/lib/ahoy/deckhands/request_deckhand.rb +41 -0
  12. data/lib/ahoy/deckhands/technology_deckhand.rb +49 -0
  13. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +24 -0
  14. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +24 -0
  15. data/lib/ahoy/engine.rb +3 -1
  16. data/lib/ahoy/model.rb +19 -82
  17. data/lib/ahoy/stores/active_record_store.rb +63 -0
  18. data/lib/ahoy/stores/active_record_token_store.rb +113 -0
  19. data/lib/ahoy/stores/base_store.rb +65 -0
  20. data/lib/ahoy/stores/log_store.rb +47 -0
  21. data/lib/ahoy/stores/mongoid_store.rb +59 -0
  22. data/lib/ahoy/tracker.rb +108 -67
  23. data/lib/ahoy/version.rb +1 -1
  24. data/lib/ahoy/visit_properties.rb +58 -0
  25. data/lib/ahoy/warden.rb +1 -10
  26. data/lib/generators/ahoy/stores/active_record_events_generator.rb +44 -0
  27. data/lib/generators/ahoy/stores/active_record_generator.rb +17 -0
  28. data/lib/generators/ahoy/{events/active_record_generator.rb → stores/active_record_visits_generator.rb} +14 -6
  29. data/lib/generators/ahoy/stores/custom_generator.rb +16 -0
  30. data/lib/generators/ahoy/stores/log_generator.rb +16 -0
  31. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +20 -0
  32. data/lib/generators/ahoy/stores/mongoid_generator.rb +16 -0
  33. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +20 -0
  34. data/{app/models/ahoy/event.rb → lib/generators/ahoy/stores/templates/active_record_event_model.rb} +2 -2
  35. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +20 -0
  36. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +3 -0
  37. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +4 -0
  38. data/lib/generators/ahoy/{templates/install.rb → stores/templates/active_record_visits_migration.rb} +6 -8
  39. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +12 -0
  40. data/lib/generators/ahoy/stores/templates/log_initializer.rb +3 -0
  41. data/lib/generators/ahoy/stores/templates/mongoid_event_model.rb +12 -0
  42. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +3 -0
  43. data/lib/generators/ahoy/stores/templates/mongoid_visit_model.rb +41 -0
  44. data/vendor/assets/javascripts/ahoy.js +19 -8
  45. metadata +45 -8
  46. data/lib/generators/ahoy/events/templates/create_events.rb +0 -20
  47. data/lib/generators/ahoy/events/templates/initializer.rb +0 -1
  48. 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
@@ -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
- if track?
11
- # publish to each subscriber
13
+ unless exclude?
12
14
  options = options.dup
13
- if @controller
14
- options[:controller] ||= @controller
15
- options[:user] ||= Ahoy.fetch_user(@controller)
16
- if @controller.respond_to?(:current_visit)
17
- options[:visit] ||= @controller.current_visit
18
- end
19
- if @controller.respond_to?(:current_visit_token)
20
- options[:visit_token] = @controller.current_visit_token
21
- end
22
- if @controller.respond_to?(:current_visitor_token)
23
- options[:visitor_token] = @controller.current_visitor_token
24
- end
25
- end
26
- options[:time] ||= Time.zone.now
27
- options[:id] ||= Ahoy.generate_id
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
- $stderr.puts "No subscribers"
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
- # not a public API - do not use
43
- def track_visit
44
- visit_token = params[:visit_token] || Ahoy.generate_id
45
- visitor_token = params[:visitor_token] || Ahoy.generate_id
46
-
47
- if track?
48
- # TODO move to subscriber
49
- visit =
50
- Ahoy.visit_model.new do |v|
51
- v.visit_token = visit_token
52
- v.visitor_token = visitor_token
53
- v.ip = request.remote_ip if v.respond_to?(:ip=)
54
- v.user_agent = request.user_agent if v.respond_to?(:user_agent=)
55
- v.referrer = params[:referrer] if v.respond_to?(:referrer=)
56
- v.landing_page = params[:landing_page] if v.respond_to?(:landing_page=)
57
- v.user = Ahoy.fetch_user(@controller) if v.respond_to?(:user=)
58
- v.platform = params[:platform] if v.respond_to?(:platform=)
59
- v.app_version = params[:app_version] if v.respond_to?(:app_version=)
60
- v.os_version = params[:os_version] if v.respond_to?(:os_version=)
61
- end
62
-
63
- begin
64
- visit.save!
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
- {visit_token: visit_token, visitor_token: visitor_token}
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 track?
76
- (Ahoy.track_bots || !bot?) && !exclude?
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 bot?
80
- @bot ||= Browser.new(ua: @request.user_agent).bot?
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
- if Ahoy.exclude_method
85
- if Ahoy.exclude_method.arity == 1
86
- Ahoy.exclude_method.call(@controller)
87
- else
88
- Ahoy.exclude_method.call(@controller, @request)
89
- end
90
- else
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 params
96
- @controller.params
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 request
100
- @request
140
+ def ensure_uuid(id)
141
+ Ahoy.ensure_uuid(id)
101
142
  end
102
143
 
103
144
  end