ahoy_matey 4.0.3 → 5.1.0

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