ahoy_matey 2.2.1 → 3.0.4

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.
@@ -1,13 +1,10 @@
1
1
  module Ahoy
2
2
  class BaseController < ApplicationController
3
3
  filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
4
- if Rails::VERSION::MAJOR >= 5
5
- skip_before_action(*filters, raise: false)
6
- skip_after_action(*filters, raise: false)
7
- skip_around_action(*filters, raise: false)
8
- else
9
- skip_action_callback(*filters)
10
- end
4
+ skip_before_action(*filters, raise: false)
5
+ skip_after_action(*filters, raise: false)
6
+ skip_around_action(*filters, raise: false)
7
+
11
8
  before_action :verify_request_size
12
9
  before_action :renew_cookies
13
10
 
@@ -31,7 +28,7 @@ module Ahoy
31
28
  def verify_request_size
32
29
  if request.content_length > Ahoy.max_content_length
33
30
  logger.info "[ahoy] Payload too large"
34
- render text: "Payload too large\n", status: 413
31
+ render plain: "Payload too large\n", status: 413
35
32
  end
36
33
  end
37
34
  end
@@ -1,11 +1,12 @@
1
1
  require "ipaddr"
2
2
 
3
+ # dependencies
3
4
  require "active_support"
4
5
  require "active_support/core_ext"
5
- require "addressable/uri"
6
6
  require "geocoder"
7
7
  require "safely/core"
8
8
 
9
+ # modules
9
10
  require "ahoy/utils"
10
11
  require "ahoy/base_store"
11
12
  require "ahoy/controller"
@@ -29,8 +30,12 @@ module Ahoy
29
30
  mattr_accessor :cookies
30
31
  self.cookies = true
31
32
 
33
+ # TODO deprecate in favor of cookie_options
32
34
  mattr_accessor :cookie_domain
33
35
 
36
+ mattr_accessor :cookie_options
37
+ self.cookie_options = {}
38
+
34
39
  mattr_accessor :server_side_visits
35
40
  self.server_side_visits = true
36
41
 
@@ -72,7 +77,7 @@ module Ahoy
72
77
  self.track_bots = false
73
78
 
74
79
  mattr_accessor :bot_detection_version
75
- self.bot_detection_version = 1
80
+ self.bot_detection_version = 2
76
81
 
77
82
  mattr_accessor :token_generator
78
83
  self.token_generator = -> { SecureRandom.uuid }
@@ -81,10 +86,12 @@ module Ahoy
81
86
  self.mask_ips = false
82
87
 
83
88
  mattr_accessor :user_agent_parser
84
- self.user_agent_parser = :legacy
89
+ self.user_agent_parser = :device_detector
90
+
91
+ mattr_accessor :logger
85
92
 
86
93
  def self.log(message)
87
- Rails.logger.info { "[ahoy] #{message}" }
94
+ logger.info { "[ahoy] #{message}" } if logger
88
95
  end
89
96
 
90
97
  def self.mask_ip(ip)
@@ -26,7 +26,7 @@ module Ahoy
26
26
  if Ahoy.user_method.respond_to?(:call)
27
27
  Ahoy.user_method.call(controller)
28
28
  else
29
- controller.send(Ahoy.user_method)
29
+ controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
30
30
  end
31
31
  end
32
32
  end
@@ -52,11 +52,13 @@ module Ahoy
52
52
  if Ahoy.user_agent_parser == :device_detector
53
53
  detector = DeviceDetector.new(request.user_agent)
54
54
  if Ahoy.bot_detection_version == 2
55
- detector.bot? || detector.device_type.nil?
55
+ detector.bot? || (detector.device_type.nil? && detector.os_name.nil?)
56
56
  else
57
57
  detector.bot?
58
58
  end
59
59
  else
60
+ # no need to throw friendly error if browser isn't defined
61
+ # since will error in visit_properties
60
62
  Browser.new(request.user_agent).bot?
61
63
  end
62
64
  else
@@ -1,5 +1,3 @@
1
- require "request_store"
2
-
3
1
  module Ahoy
4
2
  module Controller
5
3
  def self.included(base)
@@ -9,7 +7,7 @@ module Ahoy
9
7
  end
10
8
  base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
11
9
  base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
12
- base.before_action :set_ahoy_request_store
10
+ base.around_action :set_ahoy_request_store
13
11
  end
14
12
 
15
13
  def ahoy
@@ -41,7 +39,13 @@ module Ahoy
41
39
  end
42
40
 
43
41
  def set_ahoy_request_store
44
- 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
45
49
  end
46
50
  end
47
51
  end
@@ -34,7 +34,7 @@ module Ahoy
34
34
  # upsert since visit might not be found due to eventual consistency
35
35
  visit_model.where(visit_token: visit_token).find_one_and_update({"$set": data}, {upsert: true})
36
36
  elsif visit
37
- visit.update_attributes(data)
37
+ visit.update!(data)
38
38
  else
39
39
  Ahoy.log "Visit for geocode not found: #{visit_token}"
40
40
  end
@@ -76,7 +76,7 @@ module Ahoy
76
76
 
77
77
  def slice_data(model, data)
78
78
  column_names = model.try(:column_names) || model.attribute_names
79
- data.slice(*column_names.map(&:to_sym)).select { |_, v| v }
79
+ data.slice(*column_names.map(&:to_sym)).select { |_, v| !v.nil? }
80
80
  end
81
81
 
82
82
  def unique_exception?(e)
@@ -1,6 +1,8 @@
1
1
  module Ahoy
2
2
  class Engine < ::Rails::Engine
3
3
  initializer "ahoy", after: "sprockets.environment" do
4
+ Ahoy.logger ||= Rails.logger
5
+
4
6
  # allow Devise to be loaded after Ahoy
5
7
  require "ahoy/warden" if defined?(Warden)
6
8
 
@@ -2,14 +2,12 @@ module Ahoy
2
2
  module Model
3
3
  def visitable(name = :visit, **options)
4
4
  class_eval do
5
- safe_options = options.dup
6
- safe_options[:optional] = true if Rails::VERSION::MAJOR >= 5
7
- belongs_to(name, class_name: "Ahoy::Visit", **safe_options)
5
+ belongs_to(name, class_name: "Ahoy::Visit", optional: true, **options)
8
6
  before_create :set_ahoy_visit
9
7
  end
10
8
  class_eval %{
11
9
  def set_ahoy_visit
12
- self.#{name} ||= RequestStore.store[:ahoy].try(:visit_or_create)
10
+ self.#{name} ||= Thread.current[:ahoy].try(:visit_or_create)
13
11
  end
14
12
  }
15
13
  end
@@ -11,6 +11,7 @@ module Ahoy
11
11
  @controller = options[:controller]
12
12
  @request = options[:request] || @controller.try(:request)
13
13
  @visit_token = options[:visit_token]
14
+ @user = options[:user]
14
15
  @options = options
15
16
  end
16
17
 
@@ -57,7 +58,7 @@ module Ahoy
57
58
 
58
59
  @store.track_visit(data)
59
60
 
60
- Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode
61
+ Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode && data[:ip]
61
62
  end
62
63
  end
63
64
  true
@@ -127,9 +128,8 @@ module Ahoy
127
128
  @user ||= @store.user
128
129
  end
129
130
 
130
- # TODO better name
131
131
  def visit_properties
132
- @visit_properties ||= Ahoy::VisitProperties.new(request, api: api?).generate
132
+ @visit_properties ||= request ? Ahoy::VisitProperties.new(request, api: api?).generate : {}
133
133
  end
134
134
 
135
135
  def visit_token
@@ -169,24 +169,23 @@ module Ahoy
169
169
 
170
170
  def set_cookie(name, value, duration = nil, use_domain = true)
171
171
  # safety net
172
- return unless Ahoy.cookies
172
+ return unless Ahoy.cookies && request
173
173
 
174
- cookie = {
175
- value: value
176
- }
174
+ cookie = Ahoy.cookie_options.merge(value: value)
177
175
  cookie[:expires] = duration.from_now if duration
178
- domain = Ahoy.cookie_domain
179
- cookie[:domain] = domain if domain && use_domain
176
+ # prefer cookie_options[:domain] over cookie_domain
177
+ cookie[:domain] ||= Ahoy.cookie_domain if Ahoy.cookie_domain
178
+ cookie.delete(:domain) unless use_domain
180
179
  request.cookie_jar[name] = cookie
181
180
  end
182
181
 
183
182
  def delete_cookie(name)
184
- request.cookie_jar.delete(name) if request.cookie_jar[name]
183
+ request.cookie_jar.delete(name) if request && request.cookie_jar[name]
185
184
  end
186
185
 
187
186
  def trusted_time(time = nil)
188
187
  if !time || (api? && !(1.minute.ago..Time.now).cover?(time))
189
- Time.zone.now
188
+ Time.current
190
189
  else
191
190
  time
192
191
  end
@@ -197,8 +196,12 @@ module Ahoy
197
196
  end
198
197
 
199
198
  def report_exception(e)
200
- raise e if Rails.env.development? || Rails.env.test?
201
- Safely.report_exception(e)
199
+ if defined?(ActionDispatch::RemoteIp::IpSpoofAttackError) && e.is_a?(ActionDispatch::RemoteIp::IpSpoofAttackError)
200
+ debug "Tracking excluded due to IP spoofing"
201
+ else
202
+ raise e if !defined?(Rails) || Rails.env.development? || Rails.env.test?
203
+ Safely.report_exception(e)
204
+ end
202
205
  end
203
206
 
204
207
  def generate_id
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "2.2.1"
2
+ VERSION = "3.0.4"
3
3
  end
@@ -1,7 +1,6 @@
1
- require "browser"
1
+ require "cgi"
2
2
  require "device_detector"
3
- require "referer-parser"
4
- require "user_agent_parser"
3
+ require "uri"
5
4
 
6
5
  module Ahoy
7
6
  class VisitProperties
@@ -21,23 +20,26 @@ module Ahoy
21
20
  private
22
21
 
23
22
  def utm_properties
24
- landing_uri = Addressable::URI.parse(landing_page) rescue nil
25
- landing_params = (landing_uri && landing_uri.query_values) || {}
23
+ landing_params = {}
24
+ begin
25
+ landing_uri = URI.parse(landing_page)
26
+ # could also use Rack::Utils.parse_nested_query
27
+ landing_params = CGI.parse(landing_uri.query) if landing_uri
28
+ rescue
29
+ # do nothing
30
+ end
26
31
 
27
32
  props = {}
28
33
  %w(utm_source utm_medium utm_term utm_content utm_campaign).each do |name|
29
- props[name.to_sym] = params[name] || landing_params[name]
34
+ props[name.to_sym] = params[name] || landing_params[name].try(:first)
30
35
  end
31
36
  props
32
37
  end
33
38
 
34
39
  def traffic_properties
35
- # cache for performance
36
- @@referrer_parser ||= RefererParser::Parser.new
37
-
40
+ uri = URI.parse(referrer) rescue nil
38
41
  {
39
- referring_domain: (Addressable::URI.parse(referrer).host.first(255) rescue nil),
40
- search_keyword: (@@referrer_parser.parse(@referrer)[:term].first(255) rescue nil).presence
42
+ referring_domain: uri.try(:host).try(:first, 255)
41
43
  }
42
44
  end
43
45
 
@@ -60,6 +62,9 @@ module Ahoy
60
62
  device_type: device_type
61
63
  }
62
64
  else
65
+ raise "Add browser to your Gemfile to use legacy user agent parsing" unless defined?(Browser)
66
+ raise "Add user_agent_parser to your Gemfile to use legacy user agent parsing" unless defined?(UserAgentParser)
67
+
63
68
  # cache for performance
64
69
  @@user_agent_parser ||= UserAgentParser::Parser.new
65
70
 
@@ -1,34 +1,19 @@
1
- # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
- require "rails/generators"
3
- require "rails/generators/migration"
4
- require "active_record"
5
1
  require "rails/generators/active_record"
6
2
 
7
3
  module Ahoy
8
4
  module Generators
9
5
  class ActiverecordGenerator < Rails::Generators::Base
10
- include Rails::Generators::Migration
11
- source_root File.expand_path("../templates", __FILE__)
6
+ include ActiveRecord::Generators::Migration
7
+ source_root File.join(__dir__, "templates")
12
8
 
13
9
  class_option :database, type: :string, aliases: "-d"
14
10
 
15
- # Implement the required interface for Rails::Generators::Migration.
16
- def self.next_migration_number(dirname) #:nodoc:
17
- next_migration_number = current_migration_number(dirname) + 1
18
- if ::ActiveRecord::Base.timestamped_migrations
19
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
20
- else
21
- "%.3d" % next_migration_number
22
- end
23
- end
24
-
25
11
  def copy_templates
26
12
  template "database_store_initializer.rb", "config/initializers/ahoy.rb"
27
13
  template "active_record_visit_model.rb", "app/models/ahoy/visit.rb"
28
14
  template "active_record_event_model.rb", "app/models/ahoy/event.rb"
29
15
  migration_template "active_record_migration.rb", "db/migrate/create_ahoy_visits_and_events.rb", migration_version: migration_version
30
- migrate_command = rails5? ? "rails" : "rake"
31
- puts "\nAlmost set! Last, run:\n\n #{migrate_command} db:migrate"
16
+ puts "\nAlmost set! Last, run:\n\n rails db:migrate"
32
17
  end
33
18
 
34
19
  def properties_type
@@ -44,14 +29,12 @@ module Ahoy
44
29
  end
45
30
  end
46
31
 
47
- def rails5?
48
- Rails::VERSION::MAJOR >= 5
32
+ def rails52?
33
+ ActiveRecord::VERSION::STRING >= "5.2"
49
34
  end
50
35
 
51
36
  def migration_version
52
- if rails5?
53
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
54
- end
37
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
55
38
  end
56
39
  end
57
40
  end
@@ -3,7 +3,7 @@ require "rails/generators"
3
3
  module Ahoy
4
4
  module Generators
5
5
  class BaseGenerator < Rails::Generators::Base
6
- source_root File.expand_path("../templates", __FILE__)
6
+ source_root File.join(__dir__, "templates")
7
7
 
8
8
  def copy_templates
9
9
  template "base_store_initializer.rb", "config/initializers/ahoy.rb"
@@ -3,7 +3,7 @@ require "rails/generators"
3
3
  module Ahoy
4
4
  module Generators
5
5
  class InstallGenerator < Rails::Generators::Base
6
- source_root File.expand_path("../templates", __FILE__)
6
+ source_root File.join(__dir__, "templates")
7
7
 
8
8
  def copy_templates
9
9
  activerecord = defined?(ActiveRecord)
@@ -3,7 +3,7 @@ require "rails/generators"
3
3
  module Ahoy
4
4
  module Generators
5
5
  class MongoidGenerator < Rails::Generators::Base
6
- source_root File.expand_path("../templates", __FILE__)
6
+ source_root File.join(__dir__, "templates")
7
7
 
8
8
  def copy_templates
9
9
  template "database_store_initializer.rb", "config/initializers/ahoy.rb"
@@ -11,10 +11,6 @@ module Ahoy
11
11
  template "mongoid_event_model.rb", "app/models/ahoy/event.rb"
12
12
  puts "\nAlmost set! Last, run:\n\n rake db:mongoid:create_indexes"
13
13
  end
14
-
15
- def rails5?
16
- Rails::VERSION::MAJOR >= 5
17
- end
18
14
  end
19
15
  end
20
16
  end
@@ -1,10 +1,10 @@
1
- class Ahoy::Event < <%= rails5? ? "ApplicationRecord" : "ActiveRecord::Base" %>
1
+ class Ahoy::Event < ApplicationRecord
2
2
  include Ahoy::QueryMethods
3
3
 
4
4
  self.table_name = "ahoy_events"
5
5
 
6
6
  belongs_to :visit
7
- belongs_to :user<%= rails5? ? ", optional: true" : nil %><% if properties_type == "text" %>
7
+ belongs_to :user, optional: true<% if properties_type == "text" %>
8
8
 
9
9
  serialize :properties, JSON<% end %>
10
10
  end
@@ -26,8 +26,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
26
26
  t.string :country
27
27
  t.string :region
28
28
  t.string :city
29
- t.decimal :latitude, precision: 10, scale: 8
30
- t.decimal :longitude, precision: 11, scale: 8
29
+ t.float :latitude
30
+ t.float :longitude
31
31
 
32
32
  # utm parameters
33
33
  t.string :utm_source
@@ -55,7 +55,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
55
55
  t.timestamp :time
56
56
  end
57
57
 
58
- add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" && rails5? %>
59
- add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %>
58
+ add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" %><% if rails52? %>
59
+ add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops<% else %>
60
+ add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %><% end %>
60
61
  end
61
62
  end
@@ -1,6 +1,6 @@
1
- class Ahoy::Visit < <%= rails5? ? "ApplicationRecord" : "ActiveRecord::Base" %>
1
+ class Ahoy::Visit < ApplicationRecord
2
2
  self.table_name = "ahoy_visits"
3
3
 
4
4
  has_many :events, class_name: "Ahoy::Event"
5
- belongs_to :user<%= rails5? ? ", optional: true" : nil %>
5
+ belongs_to :user, optional: true
6
6
  end