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
@@ -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"
@@ -2,5 +2,10 @@ module Ahoy
2
2
  class BaseController < ApplicationController
3
3
  # skip all filters
4
4
  skip_filter *_process_action_callbacks.map(&:filter)
5
+
6
+ def ahoy
7
+ @ahoy ||= Ahoy::Tracker.new(controller: self, api: true)
8
+ end
9
+
5
10
  end
6
11
  end
@@ -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
- options = {}
8
- if event["time"] and (time = Time.at(event["time"].to_f) rescue nil) and (1.minute.ago..Time.now).cover?(time)
9
- options[:time] = time
10
- end
11
- if event["id"]
12
- options[:id] = event["id"]
13
- end
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: {}
@@ -2,7 +2,8 @@ module Ahoy
2
2
  class VisitsController < BaseController
3
3
 
4
4
  def create
5
- render json: ahoy.track_visit
5
+ ahoy.track_visit
6
+ render json: {visit_id: ahoy.visit_id, visitor_id: ahoy.visitor_id}
6
7
  end
7
8
 
8
9
  end
@@ -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/subscribers/active_record"
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
- def self.generate_id
18
- SecureRandom.uuid
19
- end
33
+ mattr_accessor :visit_duration
34
+ self.visit_duration = 4.hours
20
35
 
21
- def self.visit_model
22
- @visit_model || ::Visit
23
- end
36
+ mattr_accessor :visitor_duration
37
+ self.visitor_duration = 2.years
24
38
 
25
- def self.visit_model=(visit_model)
26
- @visit_model = visit_model
27
- end
39
+ mattr_accessor :cookie_domain
28
40
 
29
- # TODO private
30
- # performance hack for referer-parser
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
- # performance
36
- def self.user_agent_parser
37
- @user_agent_parser ||= UserAgentParser::Parser.new
38
- end
44
+ mattr_accessor :quiet
45
+ self.quiet = true
39
46
 
40
- def self.fetch_user(controller)
41
- if user_method.respond_to?(:call)
42
- user_method.call(controller)
47
+ def self.ensure_uuid(id)
48
+ valid = UUIDTools::UUID.parse(id) rescue nil
49
+ if valid
50
+ id
43
51
  else
44
- controller.send(user_method)
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
@@ -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 :set_ahoy_visitor_cookie
9
+ base.before_filter :set_ahoy_cookies
10
+ base.before_filter :track_ahoy_visit
8
11
  base.before_filter do
9
- RequestStore.store[:ahoy_controller] ||= self
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
- visit_token = current_visit_token
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 current_visitor_token
29
- @current_visitor_token ||= request.headers["Ahoy-Visitor"] || cookies[:ahoy_visitor] || current_visit.try(:visitor_token) || Ahoy.generate_id
24
+ def set_ahoy_cookies
25
+ ahoy.set_visitor_cookie
26
+ ahoy.set_visit_cookie
30
27
  end
31
28
 
32
- def set_ahoy_visitor_cookie
33
- if !request.headers["Ahoy-Visitor"] && !cookies[:ahoy_visitor]
34
- cookie = {
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
@@ -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
@@ -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[:ahoy_controller].try(:send, :current_visit)
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