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