ahoy_matey 1.6.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +7 -0
  3. data/CHANGELOG.md +22 -0
  4. data/CONTRIBUTING.md +40 -0
  5. data/LICENSE.txt +1 -1
  6. data/README.md +210 -489
  7. data/Rakefile +1 -0
  8. data/ahoy_matey.gemspec +6 -8
  9. data/app/controllers/ahoy/base_controller.rb +2 -6
  10. data/app/controllers/ahoy/events_controller.rb +7 -1
  11. data/app/controllers/ahoy/visits_controller.rb +7 -1
  12. data/app/jobs/ahoy/geocode_job.rb +10 -0
  13. data/app/jobs/ahoy/geocode_v2_job.rb +29 -0
  14. data/config/routes.rb +1 -1
  15. data/docs/Ahoy-2-Upgrade.md +147 -0
  16. data/docs/Data-Store-Examples.md +240 -0
  17. data/lib/ahoy.rb +30 -88
  18. data/lib/ahoy/base_store.rb +72 -0
  19. data/lib/ahoy/controller.rb +4 -10
  20. data/lib/ahoy/database_store.rb +72 -0
  21. data/lib/ahoy/engine.rb +5 -7
  22. data/lib/ahoy/model.rb +4 -26
  23. data/lib/ahoy/{properties.rb → query_methods.rb} +18 -4
  24. data/lib/ahoy/tracker.rb +60 -38
  25. data/lib/ahoy/version.rb +1 -1
  26. data/lib/ahoy/visit_properties.rb +65 -39
  27. data/lib/generators/ahoy/activerecord_generator.rb +58 -0
  28. data/lib/generators/ahoy/base_generator.rb +13 -0
  29. data/lib/generators/ahoy/install_generator.rb +44 -0
  30. data/lib/generators/ahoy/mongoid_generator.rb +20 -0
  31. data/lib/generators/ahoy/templates/active_record_event_model.rb +10 -0
  32. data/lib/generators/ahoy/{stores/templates/active_record_visits_migration.rb → templates/active_record_migration.rb} +19 -21
  33. data/lib/generators/ahoy/templates/active_record_visit_model.rb +6 -0
  34. data/lib/generators/ahoy/templates/base_store_initializer.rb +17 -0
  35. data/lib/generators/ahoy/templates/database_store_initializer.rb +5 -0
  36. data/lib/generators/ahoy/{stores/templates → templates}/mongoid_event_model.rb +4 -2
  37. data/lib/generators/ahoy/{stores/templates → templates}/mongoid_visit_model.rb +7 -3
  38. data/test/query_methods/mongoid_test.rb +23 -0
  39. data/test/{properties → query_methods}/mysql_json_test.rb +1 -1
  40. data/test/{properties → query_methods}/mysql_text_test.rb +1 -1
  41. data/test/{properties → query_methods}/postgresql_hstore_test.rb +1 -1
  42. data/test/{properties → query_methods}/postgresql_json_test.rb +1 -1
  43. data/test/{properties → query_methods}/postgresql_jsonb_test.rb +1 -1
  44. data/test/{properties → query_methods}/postgresql_text_test.rb +1 -1
  45. data/test/test_helper.rb +4 -3
  46. data/vendor/assets/javascripts/ahoy.js +551 -325
  47. metadata +67 -112
  48. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  49. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  50. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  51. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  52. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  53. data/lib/ahoy/geocode_job.rb +0 -13
  54. data/lib/ahoy/logger_silencer.rb +0 -75
  55. data/lib/ahoy/stores/active_record_store.rb +0 -61
  56. data/lib/ahoy/stores/active_record_token_store.rb +0 -114
  57. data/lib/ahoy/stores/base_store.rb +0 -88
  58. data/lib/ahoy/stores/bunny_store.rb +0 -33
  59. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  60. data/lib/ahoy/stores/kafka_store.rb +0 -42
  61. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  62. data/lib/ahoy/stores/log_store.rb +0 -53
  63. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  64. data/lib/ahoy/stores/nats_store.rb +0 -34
  65. data/lib/ahoy/stores/nsq_store.rb +0 -36
  66. data/lib/ahoy/subscribers/active_record.rb +0 -19
  67. data/lib/ahoy/throttle.rb +0 -17
  68. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -59
  69. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  70. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -49
  71. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  72. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  73. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  74. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  75. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  76. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  77. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  78. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  79. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  80. data/lib/generators/ahoy/stores/nats_generator.rb +0 -15
  81. data/lib/generators/ahoy/stores/nsq_generator.rb +0 -15
  82. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  83. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -20
  84. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  85. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  86. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  87. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  88. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  89. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  90. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  91. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  92. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  93. data/lib/generators/ahoy/stores/templates/nats_initializer.rb +0 -9
  94. data/lib/generators/ahoy/stores/templates/nsq_initializer.rb +0 -9
  95. data/test/visit_properties_test.rb +0 -44
data/lib/ahoy.rb CHANGED
@@ -1,53 +1,21 @@
1
1
  require "active_support"
2
2
  require "active_support/core_ext"
3
3
  require "addressable/uri"
4
- require "browser"
5
4
  require "geocoder"
6
- require "referer-parser"
7
- require "user_agent_parser"
8
- require "request_store"
9
- require "uuidtools"
10
5
  require "safely/core"
11
6
 
12
- require "ahoy/version"
13
- require "ahoy/tracker"
7
+ require "ahoy/base_store"
14
8
  require "ahoy/controller"
9
+ require "ahoy/database_store"
15
10
  require "ahoy/model"
11
+ require "ahoy/query_methods"
12
+ require "ahoy/tracker"
13
+ require "ahoy/version"
16
14
  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/nats_store"
31
- require "ahoy/stores/nsq_store"
32
- require "ahoy/stores/kinesis_firehose_store"
33
- require "ahoy/stores/bunny_store"
34
- require "ahoy/engine" if defined?(Rails)
35
- require "ahoy/warden" if defined?(Warden)
36
-
37
- # background jobs
38
- begin
39
- require "active_job"
40
- rescue LoadError
41
- # do nothing
42
- end
43
- require "ahoy/geocode_job" if defined?(ActiveJob)
44
15
 
45
- # deprecated
46
- require "ahoy/subscribers/active_record"
16
+ require "ahoy/engine" if defined?(Rails)
47
17
 
48
18
  module Ahoy
49
- UUID_NAMESPACE = UUIDTools::UUID.parse("a82ae811-5011-45ab-a728-569df7499c5f")
50
-
51
19
  mattr_accessor :visit_duration
52
20
  self.visit_duration = 4.hours
53
21
 
@@ -56,8 +24,8 @@ module Ahoy
56
24
 
57
25
  mattr_accessor :cookie_domain
58
26
 
59
- mattr_accessor :track_visits_immediately
60
- self.track_visits_immediately = false
27
+ mattr_accessor :server_side_visits
28
+ self.server_side_visits = true
61
29
 
62
30
  mattr_accessor :quiet
63
31
  self.quiet = true
@@ -71,70 +39,44 @@ module Ahoy
71
39
  mattr_accessor :max_events_per_request
72
40
  self.max_events_per_request = 10
73
41
 
74
- mattr_accessor :mount
75
- self.mount = true
76
-
77
- mattr_accessor :throttle
78
- self.throttle = true
79
-
80
- mattr_accessor :throttle_limit
81
- self.throttle_limit = 20
82
-
83
- mattr_accessor :throttle_period
84
- self.throttle_period = 1.minute
85
-
86
42
  mattr_accessor :job_queue
87
43
  self.job_queue = :ahoy
88
44
 
45
+ mattr_accessor :api
46
+ self.api = false
47
+
89
48
  mattr_accessor :api_only
90
49
  self.api_only = false
91
50
 
92
51
  mattr_accessor :protect_from_forgery
93
- self.protect_from_forgery = false
94
-
95
- def self.ensure_uuid(id)
96
- valid = UUIDTools::UUID.parse(id) rescue nil
97
- if valid
98
- id
99
- else
100
- UUIDTools::UUID.sha1_create(UUID_NAMESPACE, id).to_s
101
- end
102
- end
52
+ self.protect_from_forgery = true
103
53
 
104
- # deprecated
54
+ mattr_accessor :preserve_callbacks
55
+ self.preserve_callbacks = [:load_authlogic, :activate_authlogic]
105
56
 
106
- mattr_accessor :domain
107
- mattr_accessor :visit_model
108
57
  mattr_accessor :user_method
109
- mattr_accessor :exclude_method
58
+ self.user_method = lambda do |controller|
59
+ (controller.respond_to?(:current_user) && controller.current_user) || (controller.respond_to?(:current_resource_owner, true) && controller.send(:current_resource_owner)) || nil
60
+ end
110
61
 
111
- mattr_accessor :subscribers
112
- self.subscribers = []
62
+ mattr_accessor :exclude_method
113
63
 
114
64
  mattr_accessor :track_bots
115
65
  self.track_bots = false
66
+
67
+ mattr_accessor :token_generator
68
+ self.token_generator = -> { SecureRandom.uuid }
116
69
  end
117
70
 
118
- if defined?(Rails)
119
- ActiveSupport.on_load(:action_controller) do
120
- include Ahoy::Controller
121
- end
71
+ ActiveSupport.on_load(:action_controller) do
72
+ include Ahoy::Controller
73
+ end
122
74
 
123
- ActiveSupport.on_load(:active_record) do
124
- extend Ahoy::Model
125
- end
75
+ ActiveSupport.on_load(:active_record) do
76
+ extend Ahoy::Model
77
+ end
126
78
 
127
- # ensure logger silence will not be added by activerecord-session_store
128
- # otherwise, we get SystemStackError: stack level too deep
129
- begin
130
- require "active_record/session_store/extension/logger_silencer"
131
- rescue LoadError
132
- require "ahoy/logger_silencer"
133
- Logger.send :include, Ahoy::LoggerSilencer
134
-
135
- begin
136
- require "syslog/logger"
137
- Syslog::Logger.send :include, Ahoy::LoggerSilencer
138
- rescue LoadError; end
139
- end
79
+ # Mongoid
80
+ if defined?(ActiveModel)
81
+ ActiveModel::Callbacks.include(Ahoy::Model)
140
82
  end
@@ -0,0 +1,72 @@
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)
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
+ protected
43
+
44
+ def bot?
45
+ @bot ||= request ? Browser.new(request.user_agent).bot? : false
46
+ end
47
+
48
+ def exclude_by_method?
49
+ if Ahoy.exclude_method
50
+ if Ahoy.exclude_method.arity == 1
51
+ Ahoy.exclude_method.call(controller)
52
+ else
53
+ Ahoy.exclude_method.call(controller, request)
54
+ end
55
+ else
56
+ false
57
+ end
58
+ end
59
+
60
+ def request
61
+ @request ||= @options[:request] || controller.try(:request)
62
+ end
63
+
64
+ def controller
65
+ @controller ||= @options[:controller]
66
+ end
67
+
68
+ def ahoy
69
+ @ahoy ||= @options[:ahoy]
70
+ end
71
+ end
72
+ end
@@ -7,15 +7,9 @@ module Ahoy
7
7
  base.helper_method :current_visit
8
8
  base.helper_method :ahoy
9
9
  end
10
- if base.respond_to?(:before_action)
11
- base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
12
- base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
13
- base.before_action :set_ahoy_request_store
14
- else
15
- base.before_filter :set_ahoy_cookies, unless: -> { Ahoy.api_only }
16
- base.before_filter :track_ahoy_visit, unless: -> { Ahoy.api_only }
17
- base.before_filter :set_ahoy_request_store
18
- end
10
+ base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
11
+ base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
12
+ base.before_action :set_ahoy_request_store
19
13
  end
20
14
 
21
15
  def ahoy
@@ -33,7 +27,7 @@ module Ahoy
33
27
 
34
28
  def track_ahoy_visit
35
29
  if ahoy.new_visit?
36
- ahoy.track_visit(defer: !Ahoy.track_visits_immediately)
30
+ ahoy.track_visit(defer: !Ahoy.server_side_visits)
37
31
  end
38
32
  end
39
33
 
@@ -0,0 +1,72 @@
1
+ module Ahoy
2
+ class DatabaseStore < BaseStore
3
+ def track_visit(data)
4
+ @visit = visit_model.create!(slice_data(visit_model, data))
5
+ rescue => e
6
+ raise e unless unique_exception?(e)
7
+ @visit = nil
8
+ end
9
+
10
+ def track_event(data)
11
+ # if we don't have a visit, let's try to create one first
12
+ ahoy.track_visit unless visit
13
+
14
+ event = event_model.new(slice_data(event_model, data))
15
+ event.visit = visit
16
+ begin
17
+ event.save!
18
+ rescue => e
19
+ raise e unless unique_exception?(e)
20
+ end
21
+ end
22
+
23
+ def geocode(data)
24
+ data = slice_data(visit_model, data.except(:visit_token))
25
+ if defined?(Mongoid::Document) && visit_model < Mongoid::Document
26
+ # upsert since visit might not be found due to eventual consistency
27
+ visit_model.where(visit_token: ahoy.visit_token).find_one_and_update({"$set": data}, {upsert: true})
28
+ elsif visit
29
+ visit.update_attributes(data)
30
+ else
31
+ $stderr.puts "[ahoy] Visit for geocode not found: #{data[:visit_token]}"
32
+ end
33
+ end
34
+
35
+ def authenticate(_)
36
+ if visit && visit.respond_to?(:user) && !visit.user
37
+ begin
38
+ visit.user = user
39
+ visit.save!
40
+ rescue ActiveRecord::AssociationTypeMismatch
41
+ # do nothing
42
+ end
43
+ end
44
+ end
45
+
46
+ def visit
47
+ @visit ||= visit_model.where(visit_token: ahoy.visit_token).first if ahoy.visit_token
48
+ end
49
+
50
+ protected
51
+
52
+ def visit_model
53
+ ::Ahoy::Visit
54
+ end
55
+
56
+ def event_model
57
+ ::Ahoy::Event
58
+ end
59
+
60
+ def slice_data(model, data)
61
+ column_names = model.try(:column_names) || model.attribute_names
62
+ data.slice(*column_names.map(&:to_sym)).select { |_, v| v }
63
+ end
64
+
65
+ def unique_exception?(e)
66
+ return true if defined?(ActiveRecord::RecordNotUnique) && e.is_a?(ActiveRecord::RecordNotUnique)
67
+ return true if defined?(PG::UniqueViolation) && e.is_a?(PG::UniqueViolation)
68
+ return true if defined?(Mongo::Error::OperationFailure) && e.is_a?(Mongo::Error::OperationFailure) && e.message.include?("duplicate key error")
69
+ false
70
+ end
71
+ end
72
+ end
data/lib/ahoy/engine.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  module Ahoy
2
2
  class Engine < ::Rails::Engine
3
- initializer "ahoy.middleware", after: "sprockets.environment" do |app|
4
- if Ahoy.throttle
5
- require "ahoy/throttle"
6
- app.middleware.use Ahoy::Throttle
7
- end
3
+ initializer "ahoy", after: "sprockets.environment" do |app|
4
+ # allow Devise to be loaded after Ahoy
5
+ require "ahoy/warden" if defined?(Warden)
8
6
 
9
7
  next unless Ahoy.quiet
10
8
 
@@ -14,8 +12,8 @@ module Ahoy
14
12
  # Just create an alias for call in middleware
15
13
  Rails::Rack::Logger.class_eval do
16
14
  def call_with_quiet_ahoy(env)
17
- if env["PATH_INFO"].start_with?(AHOY_PREFIX) && logger.respond_to?(:silence_logger)
18
- logger.silence_logger do
15
+ if env["PATH_INFO"].start_with?(AHOY_PREFIX) && logger.respond_to?(:silence)
16
+ logger.silence do
19
17
  call_without_quiet_ahoy(env)
20
18
  end
21
19
  else
data/lib/ahoy/model.rb CHANGED
@@ -1,37 +1,15 @@
1
1
  module Ahoy
2
2
  module Model
3
- def visitable(name = nil, options = {})
4
- if name.is_a?(Hash)
5
- options = name
6
- name = nil
7
- end
8
- name ||= :visit
3
+ def visitable(name = :visit, **options)
9
4
  class_eval do
10
- belongs_to name, options
11
- before_create :set_visit
5
+ belongs_to(name, optional: true, class_name: "Ahoy::Visit", **options)
6
+ before_create :set_ahoy_visit
12
7
  end
13
8
  class_eval %{
14
- def set_visit
9
+ def set_ahoy_visit
15
10
  self.#{name} ||= RequestStore.store[:ahoy].try(:visit)
16
11
  end
17
12
  }
18
13
  end
19
-
20
- # deprecated
21
-
22
- def ahoy_visit
23
- class_eval do
24
- warn "[DEPRECATION] ahoy_visit is deprecated"
25
-
26
- belongs_to :user, polymorphic: true
27
-
28
- def landing_params
29
- @landing_params ||= begin
30
- warn "[DEPRECATION] landing_params is deprecated"
31
- Deckhands::UtmParameterDeckhand.new(landing_page).landing_params
32
- end
33
- end
34
- end
35
- end
36
14
  end
37
15
  end
@@ -1,13 +1,23 @@
1
1
  module Ahoy
2
- module Properties
2
+ module QueryMethods
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- def where_properties(properties)
6
+ def where_event(name, properties = {})
7
+ where(name: name).where_props(properties)
8
+ end
9
+
10
+ def where_props(properties)
7
11
  relation = self
8
- column_type = columns_hash["properties"].type
9
- adapter_name = connection.adapter_name.downcase
12
+ if respond_to?(:columns_hash)
13
+ column_type = columns_hash["properties"].type
14
+ adapter_name = connection.adapter_name.downcase
15
+ else
16
+ adapter_name = "mongoid"
17
+ end
10
18
  case adapter_name
19
+ when "mongoid"
20
+ relation = where(Hash[properties.map { |k, v| ["properties.#{k}", v] }])
11
21
  when /mysql/
12
22
  if column_type == :json
13
23
  properties.each do |k, v|
@@ -55,6 +65,10 @@ module Ahoy
55
65
  end
56
66
  relation
57
67
  end
68
+ alias_method :where_properties, :where_props
58
69
  end
59
70
  end
60
71
  end
72
+
73
+ # backward compatibility
74
+ Ahoy::Properties = Ahoy::QueryMethods
data/lib/ahoy/tracker.rb CHANGED
@@ -2,45 +2,56 @@ module Ahoy
2
2
  class Tracker
3
3
  attr_reader :request, :controller
4
4
 
5
- def initialize(options = {})
5
+ def initialize(**options)
6
6
  @store = Ahoy::Store.new(options.merge(ahoy: self))
7
7
  @controller = options[:controller]
8
8
  @request = options[:request] || @controller.try(:request)
9
+ @visit_token = options[:visit_token]
9
10
  @options = options
10
11
  end
11
12
 
13
+ # can't use keyword arguments here
12
14
  def track(name, properties = {}, options = {})
13
15
  if exclude?
14
16
  debug "Event excluded"
15
17
  elsif missing_params?
16
18
  debug "Missing required parameters"
17
19
  else
18
- options = options.dup
19
-
20
- options[:time] = trusted_time(options[:time])
21
- options[:id] = ensure_uuid(options[:id] || generate_id)
22
-
23
- @store.track_event(name, properties, options)
20
+ data = {
21
+ visit_token: visit_token,
22
+ user_id: user.try(:id),
23
+ name: name.to_s,
24
+ properties: properties,
25
+ time: trusted_time(options[:time]),
26
+ event_id: options[:id] || generate_id
27
+ }.select { |_, v| v }
28
+
29
+ @store.track_event(data)
24
30
  end
25
31
  true
26
32
  rescue => e
27
33
  report_exception(e)
28
34
  end
29
35
 
30
- def track_visit(options = {})
36
+ def track_visit(defer: false)
31
37
  if exclude?
32
38
  debug "Visit excluded"
33
39
  elsif missing_params?
34
40
  debug "Missing required parameters"
35
41
  else
36
- if options[:defer]
42
+ if defer
37
43
  set_cookie("ahoy_track", true, nil, false)
38
44
  else
39
- options = options.dup
45
+ data = {
46
+ visit_token: visit_token,
47
+ visitor_token: visitor_token,
48
+ user_id: user.try(:id),
49
+ started_at: trusted_time,
50
+ }.merge(visit_properties).select { |_, v| v }
40
51
 
41
- options[:started_at] ||= Time.zone.now
52
+ @store.track_visit(data)
42
53
 
43
- @store.track_visit(options)
54
+ Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode
44
55
  end
45
56
  end
46
57
  true
@@ -48,11 +59,28 @@ module Ahoy
48
59
  report_exception(e)
49
60
  end
50
61
 
62
+ def geocode(data)
63
+ if exclude?
64
+ debug "Geocode excluded"
65
+ else
66
+ @store.geocode(data.select { |_, v| v })
67
+ true
68
+ end
69
+ rescue => e
70
+ report_exception(e)
71
+ end
72
+
51
73
  def authenticate(user)
52
74
  if exclude?
53
75
  debug "Authentication excluded"
54
76
  else
55
- @store.authenticate(user)
77
+ @store.user = user
78
+
79
+ data = {
80
+ visit_token: visit_token,
81
+ user_id: user.try(:id)
82
+ }
83
+ @store.authenticate(data)
56
84
  end
57
85
  true
58
86
  rescue => e
@@ -63,14 +91,6 @@ module Ahoy
63
91
  @visit ||= @store.visit
64
92
  end
65
93
 
66
- def visit_id
67
- @visit_id ||= ensure_uuid(visit_token_helper)
68
- end
69
-
70
- def visitor_id
71
- @visitor_id ||= ensure_uuid(visitor_token_helper)
72
- end
73
-
74
94
  def new_visit?
75
95
  !existing_visit_token
76
96
  end
@@ -80,12 +100,12 @@ module Ahoy
80
100
  end
81
101
 
82
102
  def set_visit_cookie
83
- set_cookie("ahoy_visit", visit_id, Ahoy.visit_duration)
103
+ set_cookie("ahoy_visit", visit_token, Ahoy.visit_duration)
84
104
  end
85
105
 
86
106
  def set_visitor_cookie
87
107
  if new_visitor?
88
- set_cookie("ahoy_visitor", visitor_id, Ahoy.visitor_duration)
108
+ set_cookie("ahoy_visitor", visitor_token, Ahoy.visitor_duration)
89
109
  end
90
110
  end
91
111
 
@@ -95,16 +115,29 @@ module Ahoy
95
115
 
96
116
  # TODO better name
97
117
  def visit_properties
98
- @visit_properties ||= Ahoy::VisitProperties.new(request, api: api?)
118
+ @visit_properties ||= Ahoy::VisitProperties.new(request, api: api?).generate
99
119
  end
100
120
 
101
121
  def visit_token
102
122
  @visit_token ||= ensure_token(visit_token_helper)
103
123
  end
124
+ alias_method :visit_id, :visit_token
104
125
 
105
126
  def visitor_token
106
127
  @visitor_token ||= ensure_token(visitor_token_helper)
107
128
  end
129
+ alias_method :visitor_id, :visitor_token
130
+
131
+ def reset
132
+ reset_visit
133
+ request.cookie_jar.delete("ahoy_visitor")
134
+ end
135
+
136
+ def reset_visit
137
+ request.cookie_jar.delete("ahoy_visit")
138
+ request.cookie_jar.delete("ahoy_events")
139
+ request.cookie_jar.delete("ahoy_track")
140
+ end
108
141
 
109
142
  protected
110
143
 
@@ -125,12 +158,12 @@ module Ahoy
125
158
  value: value
126
159
  }
127
160
  cookie[:expires] = duration.from_now if duration
128
- domain = Ahoy.cookie_domain || Ahoy.domain
161
+ domain = Ahoy.cookie_domain
129
162
  cookie[:domain] = domain if domain && use_domain
130
163
  request.cookie_jar[name] = cookie
131
164
  end
132
165
 
133
- def trusted_time(time)
166
+ def trusted_time(time = nil)
134
167
  if !time || (api? && !(1.minute.ago..Time.now).cover?(time))
135
168
  Time.zone.now
136
169
  else
@@ -142,15 +175,8 @@ module Ahoy
142
175
  @store.exclude?
143
176
  end
144
177
 
145
- # odd pattern for backwards compatibility
146
- # TODO remove this method in next major release
147
178
  def report_exception(e)
148
- Safely.safely do
149
- @store.report_exception(e)
150
- if Rails.env.development? || Rails.env.test?
151
- raise e
152
- end
153
- end
179
+ Safely.report_exception(e)
154
180
  end
155
181
 
156
182
  def generate_id
@@ -215,10 +241,6 @@ module Ahoy
215
241
  @visitor_param ||= request && request.params["visitor_token"]
216
242
  end
217
243
 
218
- def ensure_uuid(id)
219
- Ahoy.ensure_uuid(id) if id
220
- end
221
-
222
244
  def ensure_token(token)
223
245
  token.to_s.gsub(/[^a-z0-9\-]/i, "").first(64) if token
224
246
  end