ahoy_matey 1.5.3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +88 -0
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +369 -380
  6. data/app/controllers/ahoy/base_controller.rb +15 -15
  7. data/app/controllers/ahoy/events_controller.rb +8 -2
  8. data/app/controllers/ahoy/visits_controller.rb +8 -1
  9. data/app/jobs/ahoy/geocode_job.rb +10 -0
  10. data/app/jobs/ahoy/geocode_v2_job.rb +28 -0
  11. data/config/routes.rb +1 -1
  12. data/lib/ahoy.rb +68 -86
  13. data/lib/ahoy/base_store.rb +97 -0
  14. data/lib/ahoy/controller.rb +26 -17
  15. data/lib/ahoy/database_store.rb +89 -0
  16. data/lib/ahoy/engine.rb +7 -7
  17. data/lib/ahoy/helper.rb +40 -0
  18. data/lib/ahoy/model.rb +5 -27
  19. data/lib/ahoy/{properties.rb → query_methods.rb} +25 -9
  20. data/lib/ahoy/tracker.rb +101 -42
  21. data/lib/ahoy/utils.rb +7 -0
  22. data/lib/ahoy/version.rb +1 -1
  23. data/lib/ahoy/visit_properties.rb +99 -37
  24. data/lib/generators/ahoy/activerecord_generator.rb +41 -0
  25. data/lib/generators/ahoy/base_generator.rb +13 -0
  26. data/lib/generators/ahoy/install_generator.rb +44 -0
  27. data/lib/generators/ahoy/mongoid_generator.rb +16 -0
  28. data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  29. data/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
  30. data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  31. data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +20 -0
  32. data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +5 -0
  33. data/lib/generators/ahoy/{stores/templates/mongoid_event_model.rb → templates/mongoid_event_model.rb.tt} +4 -2
  34. data/lib/generators/ahoy/{stores/templates/mongoid_visit_model.rb → templates/mongoid_visit_model.rb.tt} +15 -9
  35. data/vendor/assets/javascripts/ahoy.js +336 -113
  36. metadata +54 -144
  37. data/.gitignore +0 -17
  38. data/Gemfile +0 -6
  39. data/Rakefile +0 -8
  40. data/ahoy_matey.gemspec +0 -38
  41. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  42. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  43. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  44. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  45. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  46. data/lib/ahoy/geocode_job.rb +0 -13
  47. data/lib/ahoy/logger_silencer.rb +0 -75
  48. data/lib/ahoy/stores/active_record_store.rb +0 -60
  49. data/lib/ahoy/stores/active_record_token_store.rb +0 -113
  50. data/lib/ahoy/stores/base_store.rb +0 -88
  51. data/lib/ahoy/stores/bunny_store.rb +0 -33
  52. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  53. data/lib/ahoy/stores/kafka_store.rb +0 -40
  54. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  55. data/lib/ahoy/stores/log_store.rb +0 -53
  56. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  57. data/lib/ahoy/subscribers/active_record.rb +0 -19
  58. data/lib/ahoy/throttle.rb +0 -17
  59. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -53
  60. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  61. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -43
  62. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  63. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  64. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  65. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  66. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  67. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  68. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  69. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  70. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  71. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  72. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -19
  73. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  74. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  75. data/lib/generators/ahoy/stores/templates/active_record_visits_migration.rb +0 -57
  76. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  77. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  78. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  79. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  80. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  81. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  82. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  83. data/test/properties/mysql_json_test.rb +0 -18
  84. data/test/properties/mysql_text_test.rb +0 -19
  85. data/test/properties/postgresql_hstore_test.rb +0 -18
  86. data/test/properties/postgresql_json_test.rb +0 -18
  87. data/test/properties/postgresql_jsonb_test.rb +0 -18
  88. data/test/properties/postgresql_text_test.rb +0 -19
  89. data/test/test_helper.rb +0 -99
  90. data/test/visit_properties_test.rb +0 -44
@@ -1,19 +1,12 @@
1
1
  module Ahoy
2
2
  class BaseController < ApplicationController
3
- # skip all filters except for authlogic
4
- filters = _process_action_callbacks.map(&:filter) - [:load_authlogic]
5
- if Rails::VERSION::MAJOR >= 5
6
- skip_before_action(*filters, raise: false)
7
- skip_after_action(*filters, raise: false)
8
- skip_around_action(*filters, raise: false)
9
- before_action :verify_request_size
10
- elsif respond_to?(:skip_action_callback)
11
- skip_action_callback *filters
12
- before_action :verify_request_size
13
- else
14
- skip_filter *filters
15
- before_filter :verify_request_size
16
- end
3
+ filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
4
+ skip_before_action(*filters, raise: false)
5
+ skip_after_action(*filters, raise: false)
6
+ skip_around_action(*filters, raise: false)
7
+
8
+ before_action :verify_request_size
9
+ before_action :renew_cookies
17
10
 
18
11
  if respond_to?(:protect_from_forgery)
19
12
  protect_from_forgery with: :null_session, if: -> { Ahoy.protect_from_forgery }
@@ -25,10 +18,17 @@ module Ahoy
25
18
  @ahoy ||= Ahoy::Tracker.new(controller: self, api: true)
26
19
  end
27
20
 
21
+ # set proper ttl if cookie generated from JavaScript
22
+ # approach is not perfect, as user must reload the page
23
+ # for new cookie settings to take effect
24
+ def renew_cookies
25
+ set_ahoy_cookies if params[:js] && !Ahoy.api_only
26
+ end
27
+
28
28
  def verify_request_size
29
29
  if request.content_length > Ahoy.max_content_length
30
30
  logger.info "[ahoy] Payload too large"
31
- render text: "Payload too large\n", status: 413
31
+ render plain: "Payload too large\n", status: 413
32
32
  end
33
33
  end
34
34
  end
@@ -3,13 +3,19 @@ module Ahoy
3
3
  def create
4
4
  events =
5
5
  if params[:name]
6
- # legacy API
6
+ # legacy API and AMP
7
7
  [request.params]
8
8
  elsif params[:events]
9
9
  request.params[:events]
10
10
  else
11
+ data =
12
+ if params[:events_json]
13
+ request.params[:events_json]
14
+ else
15
+ request.body.read
16
+ end
11
17
  begin
12
- ActiveSupport::JSON.decode(request.body.read)
18
+ ActiveSupport::JSON.decode(data)
13
19
  rescue ActiveSupport::JSON.parse_error
14
20
  # do nothing
15
21
  []
@@ -2,7 +2,14 @@ module Ahoy
2
2
  class VisitsController < BaseController
3
3
  def create
4
4
  ahoy.track_visit
5
- render json: {visit_id: ahoy.visit_id, visitor_id: ahoy.visitor_id}
5
+
6
+ render json: {
7
+ visit_token: ahoy.visit_token,
8
+ visitor_token: ahoy.visitor_token,
9
+ # legacy
10
+ visit_id: ahoy.visit_token,
11
+ visitor_id: ahoy.visitor_token
12
+ }
6
13
  end
7
14
  end
8
15
  end
@@ -0,0 +1,10 @@
1
+ # for smooth update from Ahoy 1 -> 2
2
+ module Ahoy
3
+ class GeocodeJob < ActiveJob::Base
4
+ queue_as { Ahoy.job_queue }
5
+
6
+ def perform(visit)
7
+ Ahoy::GeocodeV2Job.perform_now(visit.visit_token, visit.ip)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module Ahoy
2
+ class GeocodeV2Job < ActiveJob::Base
3
+ queue_as { Ahoy.job_queue }
4
+
5
+ def perform(visit_token, ip)
6
+ location =
7
+ begin
8
+ Geocoder.search(ip).first
9
+ rescue => e
10
+ Ahoy.log "Geocode error: #{e.class.name}: #{e.message}"
11
+ nil
12
+ end
13
+
14
+ if location && location.country.present?
15
+ data = {
16
+ country: location.country,
17
+ region: location.try(:state).presence,
18
+ city: location.try(:city).presence,
19
+ postal_code: location.try(:postal_code).presence,
20
+ latitude: location.try(:latitude).presence,
21
+ longitude: location.try(:longitude).presence
22
+ }
23
+
24
+ Ahoy::Tracker.new(visit_token: visit_token).geocode(data)
25
+ end
26
+ end
27
+ end
28
+ end
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Rails.application.routes.draw do
2
- mount Ahoy::Engine => "/ahoy" if Ahoy.mount
2
+ mount Ahoy::Engine => "/ahoy" if Ahoy.api
3
3
  end
4
4
 
5
5
  Ahoy::Engine.routes.draw do
data/lib/ahoy.rb CHANGED
@@ -1,61 +1,39 @@
1
+ require "ipaddr"
2
+
3
+ # dependencies
1
4
  require "active_support"
2
5
  require "active_support/core_ext"
3
- require "addressable/uri"
4
- require "browser"
5
6
  require "geocoder"
6
- require "referer-parser"
7
- require "user_agent_parser"
8
- require "request_store"
9
- require "uuidtools"
10
7
  require "safely/core"
11
8
 
12
- require "ahoy/version"
13
- require "ahoy/tracker"
9
+ # modules
10
+ require "ahoy/utils"
11
+ require "ahoy/base_store"
14
12
  require "ahoy/controller"
13
+ require "ahoy/database_store"
14
+ require "ahoy/helper"
15
15
  require "ahoy/model"
16
+ require "ahoy/query_methods"
17
+ require "ahoy/tracker"
18
+ require "ahoy/version"
16
19
  require "ahoy/visit_properties"
17
- require "ahoy/properties"
18
- require "ahoy/deckhands/location_deckhand"
19
- require "ahoy/deckhands/request_deckhand"
20
- require "ahoy/deckhands/technology_deckhand"
21
- require "ahoy/deckhands/traffic_source_deckhand"
22
- require "ahoy/deckhands/utm_parameter_deckhand"
23
- require "ahoy/stores/base_store"
24
- require "ahoy/stores/active_record_store"
25
- require "ahoy/stores/active_record_token_store"
26
- require "ahoy/stores/log_store"
27
- require "ahoy/stores/fluentd_store"
28
- require "ahoy/stores/mongoid_store"
29
- require "ahoy/stores/kafka_store"
30
- require "ahoy/stores/kinesis_firehose_store"
31
- require "ahoy/stores/bunny_store"
32
- require "ahoy/engine" if defined?(Rails)
33
- require "ahoy/warden" if defined?(Warden)
34
-
35
- # background jobs
36
- begin
37
- require "active_job"
38
- rescue LoadError
39
- # do nothing
40
- end
41
- require "ahoy/geocode_job" if defined?(ActiveJob)
42
20
 
43
- # deprecated
44
- require "ahoy/subscribers/active_record"
21
+ require "ahoy/engine" if defined?(Rails)
45
22
 
46
23
  module Ahoy
47
- UUID_NAMESPACE = UUIDTools::UUID.parse("a82ae811-5011-45ab-a728-569df7499c5f")
48
-
49
24
  mattr_accessor :visit_duration
50
25
  self.visit_duration = 4.hours
51
26
 
52
27
  mattr_accessor :visitor_duration
53
28
  self.visitor_duration = 2.years
54
29
 
30
+ mattr_accessor :cookies
31
+ self.cookies = true
32
+
55
33
  mattr_accessor :cookie_domain
56
34
 
57
- mattr_accessor :track_visits_immediately
58
- self.track_visits_immediately = false
35
+ mattr_accessor :server_side_visits
36
+ self.server_side_visits = true
59
37
 
60
38
  mattr_accessor :quiet
61
39
  self.quiet = true
@@ -69,70 +47,74 @@ module Ahoy
69
47
  mattr_accessor :max_events_per_request
70
48
  self.max_events_per_request = 10
71
49
 
72
- mattr_accessor :mount
73
- self.mount = true
74
-
75
- mattr_accessor :throttle
76
- self.throttle = true
77
-
78
- mattr_accessor :throttle_limit
79
- self.throttle_limit = 20
80
-
81
- mattr_accessor :throttle_period
82
- self.throttle_period = 1.minute
83
-
84
50
  mattr_accessor :job_queue
85
51
  self.job_queue = :ahoy
86
52
 
53
+ mattr_accessor :api
54
+ self.api = false
55
+
87
56
  mattr_accessor :api_only
88
57
  self.api_only = false
89
58
 
90
59
  mattr_accessor :protect_from_forgery
91
- self.protect_from_forgery = false
92
-
93
- def self.ensure_uuid(id)
94
- valid = UUIDTools::UUID.parse(id) rescue nil
95
- if valid
96
- id
97
- else
98
- UUIDTools::UUID.sha1_create(UUID_NAMESPACE, id).to_s
99
- end
100
- end
60
+ self.protect_from_forgery = true
101
61
 
102
- # deprecated
62
+ mattr_accessor :preserve_callbacks
63
+ self.preserve_callbacks = [:load_authlogic, :activate_authlogic]
103
64
 
104
- mattr_accessor :domain
105
- mattr_accessor :visit_model
106
65
  mattr_accessor :user_method
107
- mattr_accessor :exclude_method
66
+ self.user_method = lambda do |controller|
67
+ (controller.respond_to?(:current_user, true) && controller.send(:current_user)) || (controller.respond_to?(:current_resource_owner, true) && controller.send(:current_resource_owner)) || nil
68
+ end
108
69
 
109
- mattr_accessor :subscribers
110
- self.subscribers = []
70
+ mattr_accessor :exclude_method
111
71
 
112
72
  mattr_accessor :track_bots
113
73
  self.track_bots = false
114
- end
115
74
 
116
- if defined?(Rails)
117
- ActiveSupport.on_load(:action_controller) do
118
- ActionController::Base.send :include, Ahoy::Controller
119
- end
75
+ mattr_accessor :bot_detection_version
76
+ self.bot_detection_version = 2
120
77
 
121
- ActiveSupport.on_load(:active_record) do
122
- ActiveRecord::Base.send(:extend, Ahoy::Model)
78
+ mattr_accessor :token_generator
79
+ self.token_generator = -> { SecureRandom.uuid }
80
+
81
+ mattr_accessor :mask_ips
82
+ self.mask_ips = false
83
+
84
+ mattr_accessor :user_agent_parser
85
+ self.user_agent_parser = :device_detector
86
+
87
+ mattr_accessor :logger
88
+
89
+ def self.log(message)
90
+ logger.info { "[ahoy] #{message}" } if logger
123
91
  end
124
92
 
125
- # ensure logger silence will not be added by activerecord-session_store
126
- # otherwise, we get SystemStackError: stack level too deep
127
- begin
128
- require "active_record/session_store/extension/logger_silencer"
129
- rescue LoadError
130
- require "ahoy/logger_silencer"
131
- Logger.send :include, Ahoy::LoggerSilencer
132
-
133
- begin
134
- require "syslog/logger"
135
- Syslog::Logger.send :include, Ahoy::LoggerSilencer
136
- rescue LoadError; end
93
+ def self.mask_ip(ip)
94
+ addr = IPAddr.new(ip)
95
+ if addr.ipv4?
96
+ # set last octet to 0
97
+ addr.mask(24).to_s
98
+ else
99
+ # set last 80 bits to zeros
100
+ addr.mask(48).to_s
101
+ end
137
102
  end
138
103
  end
104
+
105
+ ActiveSupport.on_load(:action_controller) do
106
+ include Ahoy::Controller
107
+ end
108
+
109
+ ActiveSupport.on_load(:active_record) do
110
+ extend Ahoy::Model
111
+ end
112
+
113
+ ActiveSupport.on_load(:action_view) do
114
+ include Ahoy::Helper
115
+ end
116
+
117
+ # Mongoid
118
+ if defined?(ActiveModel)
119
+ ActiveModel::Callbacks.include(Ahoy::Model)
120
+ end
@@ -0,0 +1,97 @@
1
+ module Ahoy
2
+ class BaseStore
3
+ attr_writer :user
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def track_visit(data)
10
+ end
11
+
12
+ def track_event(data)
13
+ end
14
+
15
+ def geocode(data)
16
+ end
17
+
18
+ def authenticate(data)
19
+ end
20
+
21
+ def visit
22
+ end
23
+
24
+ def user
25
+ @user ||= begin
26
+ if Ahoy.user_method.respond_to?(:call)
27
+ Ahoy.user_method.call(controller)
28
+ else
29
+ controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
30
+ end
31
+ end
32
+ end
33
+
34
+ def exclude?
35
+ (!Ahoy.track_bots && bot?) || exclude_by_method?
36
+ end
37
+
38
+ def generate_id
39
+ Ahoy.token_generator.call
40
+ end
41
+
42
+ def visit_or_create
43
+ visit
44
+ end
45
+
46
+ protected
47
+
48
+ def bot?
49
+ unless defined?(@bot)
50
+ @bot = begin
51
+ if request
52
+ if Ahoy.user_agent_parser == :device_detector
53
+ detector = DeviceDetector.new(request.user_agent)
54
+ if Ahoy.bot_detection_version == 2
55
+ detector.bot? || (detector.device_type.nil? && detector.os_name.nil?)
56
+ else
57
+ detector.bot?
58
+ end
59
+ else
60
+ # no need to throw friendly error if browser isn't defined
61
+ # since will error in visit_properties
62
+ Browser.new(request.user_agent).bot?
63
+ end
64
+ else
65
+ false
66
+ end
67
+ end
68
+ end
69
+
70
+ @bot
71
+ end
72
+
73
+ def exclude_by_method?
74
+ if Ahoy.exclude_method
75
+ if Ahoy.exclude_method.arity == 1
76
+ Ahoy.exclude_method.call(controller)
77
+ else
78
+ Ahoy.exclude_method.call(controller, request)
79
+ end
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ def request
86
+ @request ||= @options[:request] || controller.try(:request)
87
+ end
88
+
89
+ def controller
90
+ @controller ||= @options[:controller]
91
+ end
92
+
93
+ def ahoy
94
+ @ahoy ||= @options[:ahoy]
95
+ end
96
+ end
97
+ end
@@ -1,19 +1,13 @@
1
- require "request_store"
2
-
3
1
  module Ahoy
4
2
  module Controller
5
3
  def self.included(base)
6
- base.helper_method :current_visit
7
- base.helper_method :ahoy
8
- if base.respond_to?(:before_action)
9
- base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
10
- base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
11
- base.before_action :set_ahoy_request_store
12
- else
13
- base.before_filter :set_ahoy_cookies, unless: -> { Ahoy.api_only }
14
- base.before_filter :track_ahoy_visit, unless: -> { Ahoy.api_only }
15
- base.before_filter :set_ahoy_request_store
4
+ if base.respond_to?(:helper_method)
5
+ base.helper_method :current_visit
6
+ base.helper_method :ahoy
16
7
  end
8
+ base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
9
+ base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
10
+ base.around_action :set_ahoy_request_store
17
11
  end
18
12
 
19
13
  def ahoy
@@ -25,18 +19,33 @@ module Ahoy
25
19
  end
26
20
 
27
21
  def set_ahoy_cookies
28
- ahoy.set_visitor_cookie
29
- ahoy.set_visit_cookie
22
+ if Ahoy.cookies
23
+ ahoy.set_visitor_cookie
24
+ ahoy.set_visit_cookie
25
+ else
26
+ # delete cookies if exist
27
+ ahoy.reset
28
+ end
30
29
  end
31
30
 
32
31
  def track_ahoy_visit
33
- if ahoy.new_visit?
34
- ahoy.track_visit(defer: !Ahoy.track_visits_immediately)
32
+ defer = Ahoy.server_side_visits != true
33
+
34
+ if defer && !Ahoy.cookies
35
+ # avoid calling new_visit?, which triggers a database call
36
+ elsif ahoy.new_visit?
37
+ ahoy.track_visit(defer: defer)
35
38
  end
36
39
  end
37
40
 
38
41
  def set_ahoy_request_store
39
- RequestStore.store[:ahoy] ||= ahoy
42
+ previous_value = Thread.current[:ahoy]
43
+ begin
44
+ Thread.current[:ahoy] = ahoy
45
+ yield
46
+ ensure
47
+ Thread.current[:ahoy] = previous_value
48
+ end
40
49
  end
41
50
  end
42
51
  end