ahoy_matey 4.0.3 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8291f7d5566895e2973be22a9dd2e4f3cd43449d1289a5d176b38eed17c9a81d
4
- data.tar.gz: a2551e19aefafbf339f77dea16b3544ce2d3f6084372523029c5ac2b86a13260
3
+ metadata.gz: f8f4afce5d34400d46473e74e607c7758ef18e93d719d476b83c54bca455bd76
4
+ data.tar.gz: a64234a08867ad06f2084d4191c920f1ef12c0e290161e906303e8bcb744ead1
5
5
  SHA512:
6
- metadata.gz: c29a7d295c896e8191b213f877668910f6ca751d50750e1262ac41b2bedf971ec2342dc73dc9ac27d8f4e25306e83474162687bfbef8f44ea9887287adc89f57
7
- data.tar.gz: 901f80e0995a39f63b6d4ae4f36c8a8c1b064f9820b7783d495775152c81d7310aa40efa2286398fbdf2e7108dd80bb9a6415acf44e62e5a414f71c1d0a5975c
6
+ metadata.gz: 2ef86e6a698447417887b1dbbd5491ca4e073811cba9bcff2cfd1b42d42291ae39c96652d8dce8c006945264932d611ed80321c1ab8f3c02ed4e9f8f98e6bd64
7
+ data.tar.gz: 1427722ca7b83c98e7db34eaf56fc86a46fd10c5d14659bb93e1e465cb7f24345cf15fc7125916d13512fa6e269c378cd5ca06e9c7b927ea62df967ee54762c3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## 5.1.0 (2024-03-26)
2
+
3
+ - Added support for Trilogy
4
+ - Updated Ahoy.js to 0.4.4
5
+
6
+ ## 5.0.2 (2023-10-05)
7
+
8
+ - Excluded visits from Rails health check
9
+
10
+ ## 5.0.1 (2023-10-01)
11
+
12
+ - Fixed error with geocoding with anonymity sets
13
+
14
+ ## 5.0.0 (2023-10-01)
15
+
16
+ - Changed visits to expire with anonymity sets
17
+ - Fixed error when Active Job is not available
18
+ - Fixed deprecation warning with Rails 7.1
19
+ - Dropped support for Ruby < 3 and Rails < 6.1
20
+ - Dropped support for Mongoid 6
21
+
22
+ ## 4.2.1 (2023-02-23)
23
+
24
+ - Updated Ahoy.js to 0.4.2
25
+
26
+ ## 4.2.0 (2023-02-07)
27
+
28
+ - Added primary key type to generated migration
29
+ - Updated Ahoy.js to 0.4.1
30
+
31
+ ## 4.1.0 (2022-06-12)
32
+
33
+ - Ensure `exclude_method` is only called once per request
34
+ - Fixed error with Mongoid when `Mongoid.raise_not_found_error` is `true`
35
+ - Fixed association for Mongoid
36
+
1
37
  ## 4.0.3 (2022-01-15)
2
38
 
3
39
  - Support for `importmap-rails` is no longer experimental
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2021 Andrew Kane
1
+ Copyright (c) 2014-2024 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,20 +4,20 @@
4
4
 
5
5
  Track visits and events in Ruby, JavaScript, and native apps. Data is stored in your database by default, and you can customize it for any data store as you grow.
6
6
 
7
- **Ahoy 4.0 was recently released** - see [how to upgrade](#upgrading)
7
+ **Ahoy 5.0 was recently released** - see [how to upgrade](#upgrading)
8
8
 
9
9
  :postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for emails and [Field Test](https://github.com/ankane/field_test) for A/B testing
10
10
 
11
11
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
12
12
 
13
- [![Build Status](https://github.com/ankane/ahoy/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy/actions)
13
+ [![Build Status](https://github.com/ankane/ahoy/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/ahoy/actions)
14
14
 
15
15
  ## Installation
16
16
 
17
17
  Add this line to your application’s Gemfile:
18
18
 
19
19
  ```ruby
20
- gem 'ahoy_matey'
20
+ gem "ahoy_matey"
21
21
  ```
22
22
 
23
23
  And run:
@@ -48,7 +48,7 @@ And restart your web server.
48
48
 
49
49
  ### JavaScript
50
50
 
51
- For Rails 7 / Importmap, add to `config/importmap.rb`:
51
+ For Importmap (Rails 7 default), add to `config/importmap.rb`:
52
52
 
53
53
  ```ruby
54
54
  pin "ahoy", to: "ahoy.js"
@@ -60,7 +60,7 @@ And add to `app/javascript/application.js`:
60
60
  import "ahoy"
61
61
  ```
62
62
 
63
- For Rails 6 / Webpacker, run:
63
+ For Webpacker (Rails 6 default), run:
64
64
 
65
65
  ```sh
66
66
  yarn add ahoy.js
@@ -72,7 +72,7 @@ And add to `app/javascript/packs/application.js`:
72
72
  import ahoy from "ahoy.js"
73
73
  ```
74
74
 
75
- For Rails 5 / Sprockets, add to `app/assets/javascripts/application.js`:
75
+ For Sprockets, add to `app/assets/javascripts/application.js`:
76
76
 
77
77
  ```javascript
78
78
  //= require ahoy
@@ -197,9 +197,9 @@ Order.joins(:ahoy_visit).group("device_type").count
197
197
  Here’s what the migration to add the `ahoy_visit_id` column should look like:
198
198
 
199
199
  ```ruby
200
- class AddVisitIdToOrders < ActiveRecord::Migration[6.1]
200
+ class AddAhoyVisitToOrders < ActiveRecord::Migration[7.1]
201
201
  def change
202
- add_column :orders, :ahoy_visit_id, :bigint
202
+ add_reference :orders, :ahoy_visit
203
203
  end
204
204
  end
205
205
  ```
@@ -212,7 +212,7 @@ visitable :sign_up_visit
212
212
 
213
213
  ### Users
214
214
 
215
- Ahoy automatically attaches the `current_user` to the visit. With [Devise](https://github.com/plataformatec/devise), it attaches the user even if they sign in after the visit starts.
215
+ Ahoy automatically attaches the `current_user` to the visit. With [Devise](https://github.com/heartcombo/devise), it attaches the user even if they sign in after the visit starts.
216
216
 
217
217
  With other authentication frameworks, add this to the end of your sign in method:
218
218
 
@@ -262,28 +262,6 @@ class ApplicationController < ActionController::Base
262
262
  end
263
263
  ```
264
264
 
265
- #### Knock
266
-
267
- To attach the user with [Knock](https://github.com/nsarno/knock), either include `Knock::Authenticable`in `ApplicationController`:
268
-
269
- ```ruby
270
- class ApplicationController < ActionController::API
271
- include Knock::Authenticable
272
- end
273
- ```
274
-
275
- Or include it in Ahoy:
276
-
277
- ```ruby
278
- Ahoy::BaseController.include Knock::Authenticable
279
- ```
280
-
281
- And use:
282
-
283
- ```ruby
284
- Ahoy.user_method = ->(controller) { controller.send(:authenticate_entity, "user") }
285
- ```
286
-
287
265
  ### Exclusions
288
266
 
289
267
  Bots are excluded from tracking by default. To include them, use:
@@ -308,6 +286,14 @@ By default, a new visit is created after 4 hours of inactivity. Change this with
308
286
  Ahoy.visit_duration = 30.minutes
309
287
  ```
310
288
 
289
+ ### Visitor Duration
290
+
291
+ By default, a new `visitor_token` is generated after 2 years. Change this with:
292
+
293
+ ```ruby
294
+ Ahoy.visitor_duration = 30.days
295
+ ```
296
+
311
297
  ### Cookies
312
298
 
313
299
  To track visits across multiple subdomains, use:
@@ -326,15 +312,15 @@ You can also [disable cookies](#anonymity-sets--cookies)
326
312
 
327
313
  ### Token Generation
328
314
 
329
- Ahoy uses random UUIDs for visit and visitor tokens by default, but you can use your own generator like [Druuid](https://github.com/recurly/druuid).
315
+ Ahoy uses random UUIDs for visit and visitor tokens by default, but you can use your own generator like [ULID](https://github.com/rafaelsales/ulid).
330
316
 
331
317
  ```ruby
332
- Ahoy.token_generator = -> { Druuid.gen }
318
+ Ahoy.token_generator = -> { ULID.generate }
333
319
  ```
334
320
 
335
321
  ### Throttling
336
322
 
337
- You can use [Rack::Attack](https://github.com/kickstarter/rack-attack) to throttle requests to the API.
323
+ You can use [Rack::Attack](https://github.com/rack/rack-attack) to throttle requests to the API.
338
324
 
339
325
  ```ruby
340
326
  class Rack::Attack
@@ -361,7 +347,7 @@ Ahoy uses [Geocoder](https://github.com/alexreisner/geocoder) for geocoding. We
361
347
  To enable geocoding, add this line to your application’s Gemfile:
362
348
 
363
349
  ```ruby
364
- gem 'geocoder'
350
+ gem "geocoder"
365
351
  ```
366
352
 
367
353
  And update `config/initializers/ahoy.rb`:
@@ -378,13 +364,17 @@ Ahoy.job_queue = :low_priority
378
364
 
379
365
  ### Local Geocoding
380
366
 
381
- For privacy and performance, we recommend geocoding locally. Add this line to your application’s Gemfile:
367
+ For privacy and performance, we recommend geocoding locally.
368
+
369
+ For city-level geocoding, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data).
370
+
371
+ Add this line to your application’s Gemfile:
382
372
 
383
373
  ```ruby
384
- gem 'maxminddb'
374
+ gem "maxminddb"
385
375
  ```
386
376
 
387
- For city-level geocoding, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geoip2/geolite2/) and create `config/initializers/geocoder.rb` with:
377
+ And create `config/initializers/geocoder.rb` with:
388
378
 
389
379
  ```ruby
390
380
  Geocoder.configure(
@@ -401,6 +391,12 @@ For country-level geocoding, install the `geoip-database` package. It’s preins
401
391
  sudo apt-get install geoip-database
402
392
  ```
403
393
 
394
+ Add this line to your application’s Gemfile:
395
+
396
+ ```ruby
397
+ gem "geoip"
398
+ ```
399
+
404
400
  And create `config/initializers/geocoder.rb` with:
405
401
 
406
402
  ```ruby
@@ -450,7 +446,7 @@ class Ahoy::Store < Ahoy::DatabaseStore
450
446
  end
451
447
 
452
448
  Ahoy.mask_ips = true
453
- Ahoy.cookies = false
449
+ Ahoy.cookies = :none
454
450
  ```
455
451
 
456
452
  This:
@@ -488,12 +484,14 @@ end
488
484
 
489
485
  ### Anonymity Sets & Cookies
490
486
 
491
- Ahoy can switch from cookies to [anonymity sets](https://privacypatterns.org/patterns/Anonymity-set). Instead of cookies, visitors with the same IP mask and user agent are grouped together in an anonymity set.
487
+ Ahoy can switch from cookies to [anonymity sets](https://privacypatterns.org/patterns/Anonymity-set). Instead of cookies, visitors with the same IP mask and user agent are grouped together in an anonymity set.
492
488
 
493
489
  ```ruby
494
- Ahoy.cookies = false
490
+ Ahoy.cookies = :none
495
491
  ```
496
492
 
493
+ Note: If Ahoy was installed before v5, [add an index](#50) before making this change.
494
+
497
495
  Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
498
496
 
499
497
  ```javascript
@@ -637,7 +635,7 @@ end
637
635
 
638
636
  [Blazer](https://github.com/ankane/blazer) is a great tool for exploring your data.
639
637
 
640
- With ActiveRecord, you can do:
638
+ With Active Record, you can do:
641
639
 
642
640
  ```ruby
643
641
  Ahoy::Visit.group(:search_keyword).count
@@ -775,19 +773,29 @@ Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` an
775
773
 
776
774
  ## Upgrading
777
775
 
778
- ### 4.0
776
+ ### 5.0
779
777
 
780
- There are two notable changes to geocoding:
778
+ Visits now expire with anonymity sets. If using `Ahoy.cookies = false`, a new index is needed.
781
779
 
782
- 1. Geocoding is now disabled by default (this was already the case for new installations with 3.2.0+). Check out the instructions for [how to enable it](#geocoding).
780
+ For Active Record, create a migration with:
783
781
 
784
- 2. The `geocoder` gem is now an optional dependency. To use geocoding, add it to your Gemfile:
782
+ ```ruby
783
+ add_index :ahoy_visits, [:visitor_token, :started_at]
784
+ ```
785
785
 
786
- ```ruby
787
- gem 'geocoder'
788
- ```
786
+ For Mongoid, set:
789
787
 
790
- Also, check out the [upgrade notes](https://github.com/ankane/ahoy.js#upgrading) for Ahoy.js.
788
+ ```ruby
789
+ class Ahoy::Visit
790
+ index({visitor_token: 1, started_at: 1})
791
+ end
792
+ ```
793
+
794
+ Create the index before upgrading, and set:
795
+
796
+ ```ruby
797
+ Ahoy.cookies = :none
798
+ ```
791
799
 
792
800
  ## History
793
801
 
@@ -811,20 +819,10 @@ bundle install
811
819
  bundle exec rake test
812
820
  ```
813
821
 
814
- To test query methods, use:
822
+ To test different adapters, use:
815
823
 
816
824
  ```sh
817
- # Postgres
818
- createdb ahoy_test
819
- bundle exec rake test:query_methods:postgresql
820
-
821
- # SQLite
822
- bundle exec rake test:query_methods:sqlite
823
-
824
- # MySQL and MariaDB
825
- mysqladmin create ahoy_test
826
- bundle exec rake test:query_methods:mysql
827
-
828
- # MongoDB
829
- bundle exec rake test:query_methods:mongoid
825
+ ADAPTER=postgresql bundle exec rake test
826
+ ADAPTER=mysql2 bundle exec rake test
827
+ ADAPTER=mongoid bundle exec rake test
830
828
  ```
@@ -3,6 +3,7 @@ module Ahoy
3
3
  attr_writer :user
4
4
 
5
5
  def initialize(options)
6
+ @user = options[:user]
6
7
  @options = options
7
8
  end
8
9
 
@@ -36,7 +37,9 @@ module Ahoy
36
37
  end
37
38
 
38
39
  def exclude?
39
- (!Ahoy.track_bots && bot?) || exclude_by_method?
40
+ (!Ahoy.track_bots && bot?) ||
41
+ exclude_by_method? ||
42
+ (defined?(Rails::HealthController) && controller.is_a?(Rails::HealthController))
40
43
  end
41
44
 
42
45
  def generate_id
@@ -5,9 +5,8 @@ module Ahoy
5
5
  base.helper_method :current_visit
6
6
  base.helper_method :ahoy
7
7
  end
8
- base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
9
8
  base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
10
- base.around_action :set_ahoy_request_store
9
+ base.around_action :set_ahoy_request_store, unless: -> { Ahoy.api_only }
11
10
  end
12
11
 
13
12
  def ahoy
@@ -19,7 +18,7 @@ module Ahoy
19
18
  end
20
19
 
21
20
  def set_ahoy_cookies
22
- if Ahoy.cookies
21
+ if Ahoy.cookies?
23
22
  ahoy.set_visitor_cookie
24
23
  ahoy.set_visit_cookie
25
24
  else
@@ -31,11 +30,17 @@ module Ahoy
31
30
  def track_ahoy_visit
32
31
  defer = Ahoy.server_side_visits != true
33
32
 
34
- if defer && !Ahoy.cookies
33
+ if defer && !Ahoy.cookies?
35
34
  # avoid calling new_visit?, which triggers a database call
35
+ elsif !Ahoy.cookies? && ahoy.exclude?
36
+ # avoid calling new_visit?, which triggers a database call
37
+ # may or may not be a new visit
38
+ Ahoy.log("Request excluded")
36
39
  elsif ahoy.new_visit?
37
40
  ahoy.track_visit(defer: defer)
38
41
  end
42
+
43
+ set_ahoy_cookies
39
44
  end
40
45
 
41
46
  def set_ahoy_request_store
@@ -53,7 +53,14 @@ module Ahoy
53
53
 
54
54
  def visit
55
55
  unless defined?(@visit)
56
- @visit = visit_model.find_by(visit_token: ahoy.visit_token) if ahoy.visit_token
56
+ if ahoy.send(:existing_visit_token) || ahoy.instance_variable_get(:@visit_token)
57
+ # find_by raises error by default with Mongoid when not found
58
+ @visit = visit_model.where(visit_token: ahoy.visit_token).take if ahoy.visit_token
59
+ elsif !Ahoy.cookies? && ahoy.visitor_token
60
+ @visit = visit_model.where(visitor_token: ahoy.visitor_token).where(started_at: Ahoy.visit_duration.ago..).order(started_at: :desc).first
61
+ else
62
+ @visit = nil
63
+ end
57
64
  end
58
65
  @visit
59
66
  end
@@ -14,7 +14,7 @@ module Ahoy
14
14
  case adapter_name
15
15
  when "mongoid"
16
16
  where(properties.to_h { |k, v| ["properties.#{k}", v] })
17
- when /mysql/
17
+ when /mysql|trilogy/
18
18
  where("JSON_CONTAINS(properties, ?, '$') = 1", properties.to_json)
19
19
  when /postgres|postgis/
20
20
  case columns_hash["properties"].type
@@ -54,7 +54,7 @@ module Ahoy
54
54
  case adapter_name
55
55
  when "mongoid"
56
56
  raise "Adapter not supported: #{adapter_name}"
57
- when /mysql/
57
+ when /mysql|trilogy/
58
58
  props.each do |prop|
59
59
  quoted_prop = connection.quote("$.#{prop}")
60
60
  relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(properties, #{quoted_prop}))")
data/lib/ahoy/tracker.rb CHANGED
@@ -99,7 +99,7 @@ module Ahoy
99
99
  end
100
100
 
101
101
  def new_visit?
102
- Ahoy.cookies ? !existing_visit_token : visit.nil?
102
+ Ahoy.cookies? ? !existing_visit_token : visit.nil?
103
103
  end
104
104
 
105
105
  def new_visitor?
@@ -145,6 +145,13 @@ module Ahoy
145
145
  delete_cookie("ahoy_track")
146
146
  end
147
147
 
148
+ def exclude?
149
+ unless defined?(@exclude)
150
+ @exclude = @store.exclude?
151
+ end
152
+ @exclude
153
+ end
154
+
148
155
  protected
149
156
 
150
157
  def api?
@@ -153,7 +160,7 @@ module Ahoy
153
160
 
154
161
  # private, but used by API
155
162
  def missing_params?
156
- if Ahoy.cookies && api? && Ahoy.protect_from_forgery
163
+ if Ahoy.cookies? && api? && Ahoy.protect_from_forgery
157
164
  !(existing_visit_token && existing_visitor_token)
158
165
  else
159
166
  false
@@ -162,7 +169,7 @@ module Ahoy
162
169
 
163
170
  def set_cookie(name, value, duration = nil, use_domain = true)
164
171
  # safety net
165
- return unless Ahoy.cookies && request
172
+ return unless Ahoy.cookies? && request
166
173
 
167
174
  cookie = Ahoy.cookie_options.merge(value: value)
168
175
  cookie[:expires] = duration.from_now if duration
@@ -184,10 +191,6 @@ module Ahoy
184
191
  end
185
192
  end
186
193
 
187
- def exclude?
188
- @store.exclude?
189
- end
190
-
191
194
  def report_exception(e)
192
195
  if defined?(ActionDispatch::RemoteIp::IpSpoofAttackError) && e.is_a?(ActionDispatch::RemoteIp::IpSpoofAttackError)
193
196
  debug "Tracking excluded due to IP spoofing"
@@ -204,7 +207,7 @@ module Ahoy
204
207
  def visit_token_helper
205
208
  @visit_token_helper ||= begin
206
209
  token = existing_visit_token
207
- token ||= visit_anonymity_set unless Ahoy.cookies
210
+ token ||= visit&.visit_token unless Ahoy.cookies?
208
211
  token ||= generate_id unless Ahoy.api_only
209
212
  token
210
213
  end
@@ -213,7 +216,7 @@ module Ahoy
213
216
  def visitor_token_helper
214
217
  @visitor_token_helper ||= begin
215
218
  token = existing_visitor_token
216
- token ||= visitor_anonymity_set unless Ahoy.cookies
219
+ token ||= visitor_anonymity_set unless Ahoy.cookies?
217
220
  token ||= generate_id unless Ahoy.api_only
218
221
  token
219
222
  end
@@ -222,7 +225,7 @@ module Ahoy
222
225
  def existing_visit_token
223
226
  @existing_visit_token ||= begin
224
227
  token = visit_header
225
- token ||= visit_cookie if Ahoy.cookies && !(api? && Ahoy.protect_from_forgery)
228
+ token ||= visit_cookie if Ahoy.cookies? && !(api? && Ahoy.protect_from_forgery)
226
229
  token ||= visit_param if api?
227
230
  token
228
231
  end
@@ -231,16 +234,12 @@ module Ahoy
231
234
  def existing_visitor_token
232
235
  @existing_visitor_token ||= begin
233
236
  token = visitor_header
234
- token ||= visitor_cookie if Ahoy.cookies && !(api? && Ahoy.protect_from_forgery)
237
+ token ||= visitor_cookie if Ahoy.cookies? && !(api? && Ahoy.protect_from_forgery)
235
238
  token ||= visitor_param if api?
236
239
  token
237
240
  end
238
241
  end
239
242
 
240
- def visit_anonymity_set
241
- @visit_anonymity_set ||= Digest::UUID.uuid_v5(UUID_NAMESPACE, ["visit", Ahoy.mask_ip(request.remote_ip), request.user_agent].join("/"))
242
- end
243
-
244
243
  def visitor_anonymity_set
245
244
  @visitor_anonymity_set ||= Digest::UUID.uuid_v5(UUID_NAMESPACE, ["visitor", Ahoy.mask_ip(request.remote_ip), request.user_agent].join("/"))
246
245
  end
data/lib/ahoy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "4.0.3"
2
+ VERSION = "5.1.0"
3
3
  end
data/lib/ahoy.rb CHANGED
@@ -7,27 +7,59 @@ require "active_support/core_ext"
7
7
  require "safely/core"
8
8
 
9
9
  # modules
10
- require "ahoy/utils"
11
- require "ahoy/base_store"
12
- require "ahoy/controller"
13
- require "ahoy/database_store"
14
- require "ahoy/helper"
15
- require "ahoy/model"
16
- require "ahoy/query_methods"
17
- require "ahoy/tracker"
18
- require "ahoy/version"
19
- require "ahoy/visit_properties"
20
-
21
- require "ahoy/engine" if defined?(Rails)
10
+ require_relative "ahoy/utils"
11
+ require_relative "ahoy/base_store"
12
+ require_relative "ahoy/controller"
13
+ require_relative "ahoy/database_store"
14
+ require_relative "ahoy/helper"
15
+ require_relative "ahoy/model"
16
+ require_relative "ahoy/query_methods"
17
+ require_relative "ahoy/tracker"
18
+ require_relative "ahoy/version"
19
+ require_relative "ahoy/visit_properties"
20
+
21
+ require_relative "ahoy/engine" if defined?(Rails)
22
22
 
23
23
  module Ahoy
24
+ # activejob optional
25
+ autoload :GeocodeV2Job, "ahoy/geocode_v2_job"
26
+
24
27
  mattr_accessor :visit_duration
25
28
  self.visit_duration = 4.hours
26
29
 
27
30
  mattr_accessor :visitor_duration
28
31
  self.visitor_duration = 2.years
29
32
 
30
- mattr_accessor :cookies
33
+ def self.cookies=(value)
34
+ if value == false
35
+ if defined?(Mongoid::Document) && defined?(Ahoy::Visit) && Ahoy::Visit < Mongoid::Document
36
+ raise <<~EOS
37
+ This feature requires a new index in Ahoy 5. Set:
38
+
39
+ class Ahoy::Visit
40
+ index({visitor_token: 1, started_at: 1})
41
+ end
42
+
43
+ Create the index before upgrading, and set:
44
+
45
+ Ahoy.cookies = :none
46
+ EOS
47
+ else
48
+ raise <<~EOS
49
+ This feature requires a new index in Ahoy 5. Create a migration with:
50
+
51
+ add_index :ahoy_visits, [:visitor_token, :started_at]
52
+
53
+ Run it before upgrading, and set:
54
+
55
+ Ahoy.cookies = :none
56
+ EOS
57
+ end
58
+ end
59
+ @@cookies = value
60
+ end
61
+
62
+ mattr_reader :cookies
31
63
  self.cookies = true
32
64
 
33
65
  # TODO deprecate in favor of cookie_options
@@ -94,6 +126,10 @@ module Ahoy
94
126
  logger.info { "[ahoy] #{message}" } if logger
95
127
  end
96
128
 
129
+ def self.cookies?
130
+ cookies && cookies != :none
131
+ end
132
+
97
133
  def self.mask_ip(ip)
98
134
  addr = IPAddr.new(ip)
99
135
  if addr.ipv4?
@@ -126,7 +162,6 @@ ActiveSupport.on_load(:action_view) do
126
162
  include Ahoy::Helper
127
163
  end
128
164
 
129
- # Mongoid
130
165
  ActiveSupport.on_load(:mongoid) do
131
166
  Mongoid::Document::ClassMethods.include(Ahoy::Model)
132
167
  end
data/lib/ahoy_matey.rb CHANGED
@@ -1 +1 @@
1
- require "ahoy"
1
+ require_relative "ahoy"
@@ -1,3 +1,4 @@
1
+ require "rails/generators"
1
2
  require "rails/generators/active_record"
2
3
 
3
4
  module Ahoy
@@ -20,7 +21,7 @@ module Ahoy
20
21
  case adapter
21
22
  when /postg/i # postgres, postgis
22
23
  "jsonb"
23
- when /mysql/i
24
+ when /mysql|trilogy/i
24
25
  "json"
25
26
  else
26
27
  "text"
@@ -32,23 +33,31 @@ module Ahoy
32
33
  properties_type == "text" || (properties_type == "json" && ActiveRecord::Base.connection.try(:mariadb?))
33
34
  end
34
35
 
35
- # use connection_config instead of connection.adapter
36
- # so database connection isn't needed
37
- def adapter
38
- if ActiveRecord::VERSION::STRING.to_f >= 6.1
39
- ActiveRecord::Base.connection_db_config.adapter.to_s
40
- else
41
- ActiveRecord::Base.connection_config[:adapter].to_s
42
- end
36
+ def serialize_options
37
+ ActiveRecord::VERSION::STRING.to_f >= 7.1 ? "coder: JSON" : "JSON"
43
38
  end
44
39
 
45
- def rails52?
46
- ActiveRecord::VERSION::STRING.to_f >= 5.2
40
+ # use connection_db_config instead of connection.adapter
41
+ # so database connection isn't needed
42
+ def adapter
43
+ ActiveRecord::Base.connection_db_config.adapter.to_s
47
44
  end
48
45
 
49
46
  def migration_version
50
47
  "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
51
48
  end
49
+
50
+ def primary_key_type
51
+ ", id: :#{key_type}" if key_type
52
+ end
53
+
54
+ def foreign_key_type
55
+ ", type: :#{key_type}" if key_type
56
+ end
57
+
58
+ def key_type
59
+ Rails.configuration.generators.options.dig(:active_record, :primary_key_type)
60
+ end
52
61
  end
53
62
  end
54
63
  end
@@ -6,5 +6,5 @@ class Ahoy::Event < ApplicationRecord
6
6
  belongs_to :visit
7
7
  belongs_to :user, optional: true<% if serialize_properties? %>
8
8
 
9
- serialize :properties, JSON<% end %>
9
+ serialize :properties, <%= serialize_options %><% end %>
10
10
  end
@@ -1,6 +1,6 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :ahoy_visits do |t|
3
+ create_table :ahoy_visits<%= primary_key_type %> do |t|
4
4
  t.string :visit_token
5
5
  t.string :visitor_token
6
6
 
@@ -8,7 +8,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
8
8
  # simply remove any you don't want
9
9
 
10
10
  # user
11
- t.references :user
11
+ t.references :user<%= foreign_key_type %>
12
12
 
13
13
  # standard
14
14
  t.string :ip
@@ -45,18 +45,18 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
45
45
  end
46
46
 
47
47
  add_index :ahoy_visits, :visit_token, unique: true
48
+ add_index :ahoy_visits, [:visitor_token, :started_at]
48
49
 
49
- create_table :ahoy_events do |t|
50
- t.references :visit
51
- t.references :user
50
+ create_table :ahoy_events<%= primary_key_type %> do |t|
51
+ t.references :visit<%= foreign_key_type %>
52
+ t.references :user<%= foreign_key_type %>
52
53
 
53
54
  t.string :name
54
55
  t.<%= properties_type %> :properties
55
56
  t.datetime :time
56
57
  end
57
58
 
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 %>
59
+ add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" %>
60
+ add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops<% end %>
61
61
  end
62
62
  end
@@ -2,7 +2,7 @@ class Ahoy::Event
2
2
  include Mongoid::Document
3
3
 
4
4
  # associations
5
- belongs_to :visit, index: true
5
+ belongs_to :visit, class_name: "Ahoy::Visit", index: true
6
6
  belongs_to :user, index: true, optional: true
7
7
 
8
8
  # fields
@@ -46,4 +46,5 @@ class Ahoy::Visit
46
46
  field :started_at, type: Time
47
47
 
48
48
  index({visit_token: 1}, {unique: true})
49
+ index({visitor_token: 1, started_at: 1})
49
50
  end
@@ -1,16 +1,15 @@
1
1
  /*!
2
- * Ahoy.js
2
+ * Ahoy.js v0.4.4
3
3
  * Simple, powerful JavaScript analytics
4
4
  * https://github.com/ankane/ahoy.js
5
- * v0.4.0
6
5
  * MIT License
7
6
  */
8
7
 
9
8
  (function (global, factory) {
10
9
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
11
10
  typeof define === 'function' && define.amd ? define(factory) :
12
- (global = global || self, global.ahoy = factory());
13
- }(this, (function () { 'use strict';
11
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ahoy = factory());
12
+ })(this, (function () { 'use strict';
14
13
 
15
14
  // https://www.quirksmode.org/js/cookies.html
16
15
 
@@ -67,7 +66,7 @@
67
66
 
68
67
  ahoy.configure = function (options) {
69
68
  for (var key in options) {
70
- if (options.hasOwnProperty(key)) {
69
+ if (Object.prototype.hasOwnProperty.call(options, key)) {
71
70
  config[key] = options[key];
72
71
  }
73
72
  }
@@ -102,7 +101,7 @@
102
101
  function serialize(object) {
103
102
  var data = new FormData();
104
103
  for (var key in object) {
105
- if (object.hasOwnProperty(key)) {
104
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
106
105
  data.append(key, object[key]);
107
106
  }
108
107
  }
@@ -170,6 +169,9 @@
170
169
  document.addEventListener(eventName, function (e) {
171
170
  var matchedElement = matchesSelector(e.target, selector);
172
171
  if (matchedElement) {
172
+ var skip = getClosest(matchedElement, "data-ahoy-skip");
173
+ if (skip !== null && skip !== "false") { return; }
174
+
173
175
  callback.call(matchedElement, e);
174
176
  }
175
177
  });
@@ -186,8 +188,13 @@
186
188
 
187
189
  // https://stackoverflow.com/a/2117523/1177228
188
190
  function generateId() {
189
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
190
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
191
+ if (window.crypto && window.crypto.randomUUID) {
192
+ return window.crypto.randomUUID();
193
+ }
194
+
195
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
196
+ var r = Math.random() * 16 | 0;
197
+ var v = c === 'x' ? r : (r & 0x3 | 0x8);
191
198
  return v.toString(16);
192
199
  });
193
200
  }
@@ -237,11 +244,11 @@
237
244
  xhr.withCredentials = config.withCredentials;
238
245
  xhr.setRequestHeader("Content-Type", "application/json");
239
246
  for (var header in config.headers) {
240
- if (config.headers.hasOwnProperty(header)) {
247
+ if (Object.prototype.hasOwnProperty.call(config.headers, header)) {
241
248
  xhr.setRequestHeader(header, config.headers[header]);
242
249
  }
243
250
  }
244
- xhr.onload = function() {
251
+ xhr.onload = function () {
245
252
  if (xhr.status === 200) {
246
253
  success();
247
254
  }
@@ -266,11 +273,11 @@
266
273
  }
267
274
 
268
275
  function trackEvent(event) {
269
- ahoy.ready( function () {
270
- sendRequest(eventsUrl(), eventData(event), function() {
276
+ ahoy.ready(function () {
277
+ sendRequest(eventsUrl(), eventData(event), function () {
271
278
  // remove from queue
272
279
  for (var i = 0; i < eventQueue.length; i++) {
273
- if (eventQueue[i].id == event.id) {
280
+ if (eventQueue[i].id === event.id) {
274
281
  eventQueue.splice(i, 1);
275
282
  break;
276
283
  }
@@ -281,7 +288,7 @@
281
288
  }
282
289
 
283
290
  function trackEventNow(event) {
284
- ahoy.ready( function () {
291
+ ahoy.ready(function () {
285
292
  var data = eventData(event);
286
293
  var param = csrfParam();
287
294
  var token = csrfToken();
@@ -303,7 +310,7 @@
303
310
 
304
311
  function cleanObject(obj) {
305
312
  for (var key in obj) {
306
- if (obj.hasOwnProperty(key)) {
313
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
307
314
  if (obj[key] === null) {
308
315
  delete obj[key];
309
316
  }
@@ -318,14 +325,14 @@
318
325
  id: presence(this.id),
319
326
  "class": presence(this.className),
320
327
  page: page(),
321
- section: getClosestSection(this)
328
+ section: getClosest(this, "data-section")
322
329
  });
323
330
  }
324
331
 
325
- function getClosestSection(element) {
326
- for ( ; element && element !== document; element = element.parentNode) {
327
- if (element.hasAttribute('data-section')) {
328
- return element.getAttribute('data-section');
332
+ function getClosest(element, attribute) {
333
+ for (; element && element !== document; element = element.parentNode) {
334
+ if (element.hasAttribute(attribute)) {
335
+ return element.getAttribute(attribute);
329
336
  }
330
337
  }
331
338
 
@@ -377,7 +384,7 @@
377
384
  }
378
385
 
379
386
  for (var key in config.visitParams) {
380
- if (config.visitParams.hasOwnProperty(key)) {
387
+ if (Object.prototype.hasOwnProperty.call(config.visitParams, key)) {
381
388
  data[key] = config.visitParams[key];
382
389
  }
383
390
  }
@@ -431,12 +438,12 @@
431
438
  js: true
432
439
  };
433
440
 
434
- ahoy.ready( function () {
441
+ ahoy.ready(function () {
435
442
  if (config.cookies && !ahoy.getVisitId()) {
436
443
  createVisit();
437
444
  }
438
445
 
439
- ahoy.ready( function () {
446
+ ahoy.ready(function () {
440
447
  log(event);
441
448
 
442
449
  event.visit_token = ahoy.getVisitId();
@@ -449,7 +456,7 @@
449
456
  saveEventQueue();
450
457
 
451
458
  // wait in case navigating to reduce duplicate events
452
- setTimeout( function () {
459
+ setTimeout(function () {
453
460
  trackEvent(event);
454
461
  }, 1000);
455
462
  }
@@ -467,8 +474,8 @@
467
474
  };
468
475
 
469
476
  if (additionalProperties) {
470
- for(var propName in additionalProperties) {
471
- if (additionalProperties.hasOwnProperty(propName)) {
477
+ for (var propName in additionalProperties) {
478
+ if (Object.prototype.hasOwnProperty.call(additionalProperties, propName)) {
472
479
  properties[propName] = additionalProperties[propName];
473
480
  }
474
481
  }
@@ -482,7 +489,7 @@
482
489
  }
483
490
  onEvent("click", selector, function (e) {
484
491
  var properties = eventProperties.call(this, e);
485
- properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
492
+ properties.text = properties.tag === "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
486
493
  properties.href = this.href;
487
494
  ahoy.track("$click", properties);
488
495
  });
@@ -526,7 +533,7 @@
526
533
  ahoy.start = function () {};
527
534
  };
528
535
 
529
- documentReady(function() {
536
+ documentReady(function () {
530
537
  if (config.startOnReady) {
531
538
  ahoy.start();
532
539
  }
@@ -534,4 +541,4 @@
534
541
 
535
542
  return ahoy;
536
543
 
537
- })));
544
+ }));
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ahoy_matey
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-15 00:00:00.000000000 Z
11
+ date: 2024-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.2'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '5.2'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: safely_block
28
+ name: device_detector
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.1
33
+ version: '1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.1
40
+ version: '1'
41
41
  - !ruby/object:Gem::Dependency
42
- name: device_detector
42
+ name: safely_block
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '0.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '0.4'
55
55
  description:
56
56
  email: andrew@ankane.org
57
57
  executables: []
@@ -65,14 +65,13 @@ files:
65
65
  - app/controllers/ahoy/base_controller.rb
66
66
  - app/controllers/ahoy/events_controller.rb
67
67
  - app/controllers/ahoy/visits_controller.rb
68
- - app/jobs/ahoy/geocode_job.rb
69
- - app/jobs/ahoy/geocode_v2_job.rb
70
68
  - config/routes.rb
71
69
  - lib/ahoy.rb
72
70
  - lib/ahoy/base_store.rb
73
71
  - lib/ahoy/controller.rb
74
72
  - lib/ahoy/database_store.rb
75
73
  - lib/ahoy/engine.rb
74
+ - lib/ahoy/geocode_v2_job.rb
76
75
  - lib/ahoy/helper.rb
77
76
  - lib/ahoy/model.rb
78
77
  - lib/ahoy/query_methods.rb
@@ -106,14 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
105
  requirements:
107
106
  - - ">="
108
107
  - !ruby/object:Gem::Version
109
- version: '2.6'
108
+ version: '3'
110
109
  required_rubygems_version: !ruby/object:Gem::Requirement
111
110
  requirements:
112
111
  - - ">="
113
112
  - !ruby/object:Gem::Version
114
113
  version: '0'
115
114
  requirements: []
116
- rubygems_version: 3.3.3
115
+ rubygems_version: 3.5.3
117
116
  signing_key:
118
117
  specification_version: 4
119
118
  summary: Simple, powerful, first-party analytics for Rails
@@ -1,10 +0,0 @@
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
File without changes