ahoy_matey 4.0.0 → 5.4.1
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 +4 -4
- data/CHANGELOG.md +89 -1
- data/LICENSE.txt +1 -1
- data/README.md +78 -66
- data/app/controllers/ahoy/base_controller.rb +2 -1
- data/app/controllers/ahoy/events_controller.rb +13 -2
- data/lib/ahoy/base_store.rb +4 -1
- data/lib/ahoy/controller.rb +9 -4
- data/lib/ahoy/database_store.rb +8 -1
- data/lib/ahoy/engine.rb +7 -0
- data/lib/ahoy/query_methods.rb +38 -45
- data/lib/ahoy/tracker.rb +15 -16
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy.rb +49 -14
- data/lib/ahoy_matey.rb +1 -1
- data/lib/generators/ahoy/activerecord_generator.rb +16 -11
- data/lib/generators/ahoy/install_generator.rb +5 -5
- data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +1 -1
- data/lib/generators/ahoy/templates/active_record_migration.rb.tt +8 -8
- data/lib/generators/ahoy/templates/mongoid_event_model.rb.tt +1 -1
- data/lib/generators/ahoy/templates/mongoid_visit_model.rb.tt +1 -0
- data/vendor/assets/javascripts/ahoy.js +36 -29
- metadata +13 -18
- data/app/jobs/ahoy/geocode_job.rb +0 -10
- /data/{app/jobs → lib}/ahoy/geocode_v2_job.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df3e089d0f7806605ca6cb9215118d0a7791fdd9127f91dd13d0df59add4cb51
|
|
4
|
+
data.tar.gz: 69e7c817399a7e2488f54cbd4d4e136d383542de0194c5bc638bc191bdeae6d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cb08e2f80841ef62c2ad48881912c9cb2e81b40b03b7c4dead2ce006a49726571c3b9dee235cc9450bdbc7e72def56dbf201c2bf1d72d395aec849aca6cf323a
|
|
7
|
+
data.tar.gz: b92e534e57a6d3874e9b623a2bb1d55dc5d7fbfbd4ad16e94cb6d1350112b7623c56e569320baf2718a436db6991d1a1d943ace871d947d502f81b21b58b2fc7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
|
+
## 5.4.1 (2025-09-30)
|
|
2
|
+
|
|
3
|
+
- Fixed deprecation warning with Rack 3.1+
|
|
4
|
+
|
|
5
|
+
## 5.4.0 (2025-05-04)
|
|
6
|
+
|
|
7
|
+
- Dropped support for Ruby < 3.2 and Rails < 7.1
|
|
8
|
+
|
|
9
|
+
## 5.3.0 (2025-02-01)
|
|
10
|
+
|
|
11
|
+
- Dropped support for Ruby < 3.1 and Rails < 7
|
|
12
|
+
- Dropped support for Mongoid < 8
|
|
13
|
+
|
|
14
|
+
## 5.2.1 (2024-10-07)
|
|
15
|
+
|
|
16
|
+
- Fixed connection leasing for Active Record 7.2+
|
|
17
|
+
|
|
18
|
+
## 5.2.0 (2024-09-04)
|
|
19
|
+
|
|
20
|
+
- Improved error handling for invalid API parameters
|
|
21
|
+
|
|
22
|
+
## 5.1.0 (2024-03-26)
|
|
23
|
+
|
|
24
|
+
- Added support for Trilogy
|
|
25
|
+
- Updated Ahoy.js to 0.4.4
|
|
26
|
+
|
|
27
|
+
## 5.0.2 (2023-10-05)
|
|
28
|
+
|
|
29
|
+
- Excluded visits from Rails health check
|
|
30
|
+
|
|
31
|
+
## 5.0.1 (2023-10-01)
|
|
32
|
+
|
|
33
|
+
- Fixed error with geocoding with anonymity sets
|
|
34
|
+
|
|
35
|
+
## 5.0.0 (2023-10-01)
|
|
36
|
+
|
|
37
|
+
- Changed visits to expire with anonymity sets
|
|
38
|
+
- Fixed error when Active Job is not available
|
|
39
|
+
- Fixed deprecation warning with Rails 7.1
|
|
40
|
+
- Dropped support for Ruby < 3 and Rails < 6.1
|
|
41
|
+
- Dropped support for Mongoid 6
|
|
42
|
+
|
|
43
|
+
## 4.2.1 (2023-02-23)
|
|
44
|
+
|
|
45
|
+
- Updated Ahoy.js to 0.4.2
|
|
46
|
+
|
|
47
|
+
## 4.2.0 (2023-02-07)
|
|
48
|
+
|
|
49
|
+
- Added primary key type to generated migration
|
|
50
|
+
- Updated Ahoy.js to 0.4.1
|
|
51
|
+
|
|
52
|
+
## 4.1.0 (2022-06-12)
|
|
53
|
+
|
|
54
|
+
- Ensure `exclude_method` is only called once per request
|
|
55
|
+
- Fixed error with Mongoid when `Mongoid.raise_not_found_error` is `true`
|
|
56
|
+
- Fixed association for Mongoid
|
|
57
|
+
|
|
58
|
+
## 4.0.3 (2022-01-15)
|
|
59
|
+
|
|
60
|
+
- Support for `importmap-rails` is no longer experimental
|
|
61
|
+
- Fixed asset precompilation error with `importmap-rails`
|
|
62
|
+
|
|
63
|
+
## 4.0.2 (2021-11-06)
|
|
64
|
+
|
|
65
|
+
- Added experimental support for `importmap-rails`
|
|
66
|
+
|
|
67
|
+
## 4.0.1 (2021-08-18)
|
|
68
|
+
|
|
69
|
+
- Added support for `where_event`, `where_props`, and `where_group` for SQLite
|
|
70
|
+
- Fixed results with `where_event` for MySQL, MariaDB, and Postgres `hstore`
|
|
71
|
+
- Fixed results with `where_props` and `where_group` when used with other scopes for MySQL, MariaDB, and Postgres `hstore`
|
|
72
|
+
|
|
1
73
|
## 4.0.0 (2021-08-14)
|
|
2
74
|
|
|
3
|
-
- Disabled geocoding by default
|
|
75
|
+
- Disabled geocoding by default (this was already the case for new installations with 3.2.0+)
|
|
4
76
|
- Made the `geocoder` gem an optional dependency
|
|
5
77
|
- Updated Ahoy.js to 0.4.0
|
|
6
78
|
- Updated API to return 400 status code when missing required parameters
|
|
@@ -303,3 +375,19 @@ Breaking changes
|
|
|
303
375
|
## 0.1.0 (2014-03-19)
|
|
304
376
|
|
|
305
377
|
- First major release
|
|
378
|
+
|
|
379
|
+
## 0.0.4 (2014-03-01)
|
|
380
|
+
|
|
381
|
+
- Added UTM parameters
|
|
382
|
+
|
|
383
|
+
## 0.0.3 (2014-02-26)
|
|
384
|
+
|
|
385
|
+
- Renamed `ahoy_visit` method to `current_visit`
|
|
386
|
+
|
|
387
|
+
## 0.0.2 (2014-02-26)
|
|
388
|
+
|
|
389
|
+
- Added `ahoy_visit` method to controllers
|
|
390
|
+
|
|
391
|
+
## 0.0.1 (2014-02-26)
|
|
392
|
+
|
|
393
|
+
- First release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -4,20 +4,18 @@
|
|
|
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)
|
|
8
|
-
|
|
9
7
|
: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
8
|
|
|
11
9
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
|
12
10
|
|
|
13
|
-
[](https://github.com/ankane/ahoy/actions)
|
|
14
12
|
|
|
15
13
|
## Installation
|
|
16
14
|
|
|
17
15
|
Add this line to your application’s Gemfile:
|
|
18
16
|
|
|
19
17
|
```ruby
|
|
20
|
-
gem
|
|
18
|
+
gem "ahoy_matey"
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
And run:
|
|
@@ -48,19 +46,33 @@ And restart your web server.
|
|
|
48
46
|
|
|
49
47
|
### JavaScript
|
|
50
48
|
|
|
51
|
-
For Rails
|
|
49
|
+
For Importmap (Rails 7+ default), add to `config/importmap.rb`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
pin "ahoy", to: "ahoy.js"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
And add to `app/javascript/application.js`:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
import "ahoy"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For Bun, esbuild, rollup.js, or Webpack, run:
|
|
52
62
|
|
|
53
63
|
```sh
|
|
64
|
+
bun add ahoy.js
|
|
65
|
+
# or
|
|
54
66
|
yarn add ahoy.js
|
|
55
67
|
```
|
|
56
68
|
|
|
57
|
-
And add to `app/javascript/
|
|
69
|
+
And add to `app/javascript/application.js`:
|
|
58
70
|
|
|
59
71
|
```javascript
|
|
60
|
-
import ahoy from "ahoy.js"
|
|
72
|
+
import ahoy from "ahoy.js"
|
|
61
73
|
```
|
|
62
74
|
|
|
63
|
-
For
|
|
75
|
+
For Sprockets, add to `app/assets/javascripts/application.js`:
|
|
64
76
|
|
|
65
77
|
```javascript
|
|
66
78
|
//= require ahoy
|
|
@@ -185,9 +197,9 @@ Order.joins(:ahoy_visit).group("device_type").count
|
|
|
185
197
|
Here’s what the migration to add the `ahoy_visit_id` column should look like:
|
|
186
198
|
|
|
187
199
|
```ruby
|
|
188
|
-
class
|
|
200
|
+
class AddAhoyVisitToOrders < ActiveRecord::Migration[8.0]
|
|
189
201
|
def change
|
|
190
|
-
|
|
202
|
+
add_reference :orders, :ahoy_visit
|
|
191
203
|
end
|
|
192
204
|
end
|
|
193
205
|
```
|
|
@@ -200,7 +212,7 @@ visitable :sign_up_visit
|
|
|
200
212
|
|
|
201
213
|
### Users
|
|
202
214
|
|
|
203
|
-
Ahoy automatically attaches the `current_user` to the visit. With [Devise](https://github.com/
|
|
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.
|
|
204
216
|
|
|
205
217
|
With other authentication frameworks, add this to the end of your sign in method:
|
|
206
218
|
|
|
@@ -250,28 +262,6 @@ class ApplicationController < ActionController::Base
|
|
|
250
262
|
end
|
|
251
263
|
```
|
|
252
264
|
|
|
253
|
-
#### Knock
|
|
254
|
-
|
|
255
|
-
To attach the user with [Knock](https://github.com/nsarno/knock), either include `Knock::Authenticable`in `ApplicationController`:
|
|
256
|
-
|
|
257
|
-
```ruby
|
|
258
|
-
class ApplicationController < ActionController::API
|
|
259
|
-
include Knock::Authenticable
|
|
260
|
-
end
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Or include it in Ahoy:
|
|
264
|
-
|
|
265
|
-
```ruby
|
|
266
|
-
Ahoy::BaseController.include Knock::Authenticable
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
And use:
|
|
270
|
-
|
|
271
|
-
```ruby
|
|
272
|
-
Ahoy.user_method = ->(controller) { controller.send(:authenticate_entity, "user") }
|
|
273
|
-
```
|
|
274
|
-
|
|
275
265
|
### Exclusions
|
|
276
266
|
|
|
277
267
|
Bots are excluded from tracking by default. To include them, use:
|
|
@@ -296,6 +286,14 @@ By default, a new visit is created after 4 hours of inactivity. Change this with
|
|
|
296
286
|
Ahoy.visit_duration = 30.minutes
|
|
297
287
|
```
|
|
298
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
|
+
|
|
299
297
|
### Cookies
|
|
300
298
|
|
|
301
299
|
To track visits across multiple subdomains, use:
|
|
@@ -314,15 +312,15 @@ You can also [disable cookies](#anonymity-sets--cookies)
|
|
|
314
312
|
|
|
315
313
|
### Token Generation
|
|
316
314
|
|
|
317
|
-
Ahoy uses random UUIDs for visit and visitor tokens by default, but you can use your own generator like [
|
|
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).
|
|
318
316
|
|
|
319
317
|
```ruby
|
|
320
|
-
Ahoy.token_generator = -> {
|
|
318
|
+
Ahoy.token_generator = -> { ULID.generate }
|
|
321
319
|
```
|
|
322
320
|
|
|
323
321
|
### Throttling
|
|
324
322
|
|
|
325
|
-
You can use [Rack::Attack](https://github.com/
|
|
323
|
+
You can use [Rack::Attack](https://github.com/rack/rack-attack) to throttle requests to the API.
|
|
326
324
|
|
|
327
325
|
```ruby
|
|
328
326
|
class Rack::Attack
|
|
@@ -349,7 +347,7 @@ Ahoy uses [Geocoder](https://github.com/alexreisner/geocoder) for geocoding. We
|
|
|
349
347
|
To enable geocoding, add this line to your application’s Gemfile:
|
|
350
348
|
|
|
351
349
|
```ruby
|
|
352
|
-
gem
|
|
350
|
+
gem "geocoder"
|
|
353
351
|
```
|
|
354
352
|
|
|
355
353
|
And update `config/initializers/ahoy.rb`:
|
|
@@ -366,13 +364,17 @@ Ahoy.job_queue = :low_priority
|
|
|
366
364
|
|
|
367
365
|
### Local Geocoding
|
|
368
366
|
|
|
369
|
-
For privacy and performance, we recommend geocoding locally.
|
|
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:
|
|
370
372
|
|
|
371
373
|
```ruby
|
|
372
|
-
gem
|
|
374
|
+
gem "maxminddb"
|
|
373
375
|
```
|
|
374
376
|
|
|
375
|
-
|
|
377
|
+
And create `config/initializers/geocoder.rb` with:
|
|
376
378
|
|
|
377
379
|
```ruby
|
|
378
380
|
Geocoder.configure(
|
|
@@ -389,6 +391,12 @@ For country-level geocoding, install the `geoip-database` package. It’s preins
|
|
|
389
391
|
sudo apt-get install geoip-database
|
|
390
392
|
```
|
|
391
393
|
|
|
394
|
+
Add this line to your application’s Gemfile:
|
|
395
|
+
|
|
396
|
+
```ruby
|
|
397
|
+
gem "geoip"
|
|
398
|
+
```
|
|
399
|
+
|
|
392
400
|
And create `config/initializers/geocoder.rb` with:
|
|
393
401
|
|
|
394
402
|
```ruby
|
|
@@ -438,7 +446,7 @@ class Ahoy::Store < Ahoy::DatabaseStore
|
|
|
438
446
|
end
|
|
439
447
|
|
|
440
448
|
Ahoy.mask_ips = true
|
|
441
|
-
Ahoy.cookies =
|
|
449
|
+
Ahoy.cookies = :none
|
|
442
450
|
```
|
|
443
451
|
|
|
444
452
|
This:
|
|
@@ -476,12 +484,14 @@ end
|
|
|
476
484
|
|
|
477
485
|
### Anonymity Sets & Cookies
|
|
478
486
|
|
|
479
|
-
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
|
|
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.
|
|
480
488
|
|
|
481
489
|
```ruby
|
|
482
|
-
Ahoy.cookies =
|
|
490
|
+
Ahoy.cookies = :none
|
|
483
491
|
```
|
|
484
492
|
|
|
493
|
+
Note: If Ahoy was installed before v5, [add an index](https://github.com/ankane/ahoy/tree/v5.0.0?tab=readme-ov-file#50) before making this change.
|
|
494
|
+
|
|
485
495
|
Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
|
|
486
496
|
|
|
487
497
|
```javascript
|
|
@@ -625,7 +635,7 @@ end
|
|
|
625
635
|
|
|
626
636
|
[Blazer](https://github.com/ankane/blazer) is a great tool for exploring your data.
|
|
627
637
|
|
|
628
|
-
With
|
|
638
|
+
With Active Record, you can do:
|
|
629
639
|
|
|
630
640
|
```ruby
|
|
631
641
|
Ahoy::Visit.group(:search_keyword).count
|
|
@@ -661,7 +671,7 @@ Group by properties with:
|
|
|
661
671
|
Ahoy::Event.group_prop(:product_id, :category).count
|
|
662
672
|
```
|
|
663
673
|
|
|
664
|
-
Note: MySQL and MariaDB always return string keys (
|
|
674
|
+
Note: MySQL and MariaDB always return string keys (including `"null"` for `nil`) for `group_prop`.
|
|
665
675
|
|
|
666
676
|
### Funnels
|
|
667
677
|
|
|
@@ -694,6 +704,24 @@ daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
|
|
|
694
704
|
Prophet.forecast(daily_visits)
|
|
695
705
|
```
|
|
696
706
|
|
|
707
|
+
### Anomaly Detection
|
|
708
|
+
|
|
709
|
+
To detect anomalies in visits and events, check out [AnomalyDetection.rb](https://github.com/ankane/AnomalyDetection.rb).
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
|
|
713
|
+
AnomalyDetection.detect(daily_visits, period: 7)
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Breakout Detection
|
|
717
|
+
|
|
718
|
+
To detect breakouts in visits and events, check out [Breakout](https://github.com/ankane/breakout).
|
|
719
|
+
|
|
720
|
+
```ruby
|
|
721
|
+
daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
|
|
722
|
+
Breakout.detect(daily_visits)
|
|
723
|
+
```
|
|
724
|
+
|
|
697
725
|
### Recommendations
|
|
698
726
|
|
|
699
727
|
To make recommendations based on events, check out [Disco](https://github.com/ankane/disco#ahoy).
|
|
@@ -737,28 +765,12 @@ Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` an
|
|
|
737
765
|
"properties": {
|
|
738
766
|
"item_id": 123
|
|
739
767
|
},
|
|
740
|
-
"time": "
|
|
768
|
+
"time": "2025-01-01T00:00:00-07:00"
|
|
741
769
|
}
|
|
742
770
|
]
|
|
743
771
|
}
|
|
744
772
|
```
|
|
745
773
|
|
|
746
|
-
## Upgrading
|
|
747
|
-
|
|
748
|
-
### 4.0
|
|
749
|
-
|
|
750
|
-
There are two notable changes to geocoding:
|
|
751
|
-
|
|
752
|
-
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).
|
|
753
|
-
|
|
754
|
-
2. The `geocoder` gem is now an optional dependency. To use geocoding, add it to your Gemfile:
|
|
755
|
-
|
|
756
|
-
```ruby
|
|
757
|
-
gem 'geocoder'
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
Also, check out the [upgrade notes](https://github.com/ankane/ahoy.js#upgrading) for Ahoy.js.
|
|
761
|
-
|
|
762
774
|
## History
|
|
763
775
|
|
|
764
776
|
View the [changelog](https://github.com/ankane/ahoy/blob/master/CHANGELOG.md)
|
|
@@ -781,10 +793,10 @@ bundle install
|
|
|
781
793
|
bundle exec rake test
|
|
782
794
|
```
|
|
783
795
|
|
|
784
|
-
To test
|
|
796
|
+
To test different adapters, use:
|
|
785
797
|
|
|
786
798
|
```sh
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
bundle exec rake test
|
|
799
|
+
ADAPTER=postgresql bundle exec rake test
|
|
800
|
+
ADAPTER=mysql2 bundle exec rake test
|
|
801
|
+
ADAPTER=mongoid bundle exec rake test
|
|
790
802
|
```
|
|
@@ -36,7 +36,8 @@ module Ahoy
|
|
|
36
36
|
def verify_request_size
|
|
37
37
|
if request.content_length > Ahoy.max_content_length
|
|
38
38
|
logger.info "[ahoy] Payload too large"
|
|
39
|
-
|
|
39
|
+
status = Rack::RELEASE.to_f >= 3.1 ? :content_too_large : :payload_too_large
|
|
40
|
+
render plain: "Payload too large\n", status: status
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
end
|
|
@@ -17,12 +17,23 @@ module Ahoy
|
|
|
17
17
|
begin
|
|
18
18
|
ActiveSupport::JSON.decode(data)
|
|
19
19
|
rescue ActiveSupport::JSON.parse_error
|
|
20
|
-
#
|
|
20
|
+
# TODO change to nil in Ahoy 6
|
|
21
21
|
[]
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
max_events_per_request = Ahoy.max_events_per_request
|
|
26
|
+
|
|
27
|
+
# check before creating any events
|
|
28
|
+
unless events.is_a?(Array) && events.first(max_events_per_request).all? { |v| v.is_a?(Hash) }
|
|
29
|
+
logger.info "[ahoy] Invalid parameters"
|
|
30
|
+
# :unprocessable_entity is probably more correct
|
|
31
|
+
# but keep consistent with missing parameters for now
|
|
32
|
+
render plain: "Invalid parameters\n", status: :bad_request
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
events.first(max_events_per_request).each do |event|
|
|
26
37
|
time = Time.zone.parse(event["time"]) rescue nil
|
|
27
38
|
|
|
28
39
|
# timestamp is deprecated
|
data/lib/ahoy/base_store.rb
CHANGED
|
@@ -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?) ||
|
|
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
|
data/lib/ahoy/controller.rb
CHANGED
|
@@ -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
|
data/lib/ahoy/database_store.rb
CHANGED
|
@@ -53,7 +53,14 @@ module Ahoy
|
|
|
53
53
|
|
|
54
54
|
def visit
|
|
55
55
|
unless defined?(@visit)
|
|
56
|
-
|
|
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
|
data/lib/ahoy/engine.rb
CHANGED
|
@@ -26,5 +26,12 @@ module Ahoy
|
|
|
26
26
|
alias_method :call, :call_with_quiet_ahoy
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
# for importmap
|
|
31
|
+
initializer "ahoy.importmap" do |app|
|
|
32
|
+
if app.config.respond_to?(:assets) && defined?(Importmap) && defined?(Sprockets)
|
|
33
|
+
app.config.assets.precompile << "ahoy.js"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
29
36
|
end
|
|
30
37
|
end
|
data/lib/ahoy/query_methods.rb
CHANGED
|
@@ -8,47 +8,40 @@ module Ahoy
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def where_props(properties)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
adapter_name = connection.adapter_name.downcase
|
|
15
|
-
else
|
|
16
|
-
adapter_name = "mongoid"
|
|
17
|
-
end
|
|
11
|
+
return all if properties.empty?
|
|
12
|
+
|
|
13
|
+
adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
|
|
18
14
|
case adapter_name
|
|
19
15
|
when "mongoid"
|
|
20
|
-
|
|
21
|
-
when /mysql/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
v = "null"
|
|
26
|
-
elsif v == true
|
|
27
|
-
v = "true"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
relation = relation.where("JSON_UNQUOTE(JSON_EXTRACT(#{column}, ?)) = ?", "$.#{k}", v.as_json)
|
|
31
|
-
end
|
|
32
|
-
when /postgres|postgis/
|
|
33
|
-
case column_type
|
|
34
|
-
when :jsonb
|
|
35
|
-
relation = relation.where("properties @> ?", properties.to_json)
|
|
16
|
+
where(properties.to_h { |k, v| ["properties.#{k}", v] })
|
|
17
|
+
when /mysql|trilogy/i
|
|
18
|
+
where("JSON_CONTAINS(properties, ?, '$') = 1", properties.to_json)
|
|
19
|
+
when /postg/i
|
|
20
|
+
case columns_hash["properties"].type
|
|
36
21
|
when :hstore
|
|
37
|
-
properties.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end
|
|
22
|
+
properties.inject(all) do |relation, (k, v)|
|
|
23
|
+
if v.nil?
|
|
24
|
+
relation.where("properties -> ? IS NULL", k.to_s)
|
|
25
|
+
else
|
|
26
|
+
relation.where("properties -> ? = ?", k.to_s, v.to_s)
|
|
27
|
+
end
|
|
44
28
|
end
|
|
29
|
+
when :jsonb
|
|
30
|
+
where("properties @> ?", properties.to_json)
|
|
45
31
|
else
|
|
46
|
-
|
|
32
|
+
where("properties::jsonb @> ?", properties.to_json)
|
|
33
|
+
end
|
|
34
|
+
when /sqlite/i
|
|
35
|
+
properties.inject(all) do |relation, (k, v)|
|
|
36
|
+
if v.nil?
|
|
37
|
+
relation.where("JSON_EXTRACT(properties, ?) IS NULL", "$.#{k}")
|
|
38
|
+
else
|
|
39
|
+
relation.where("JSON_EXTRACT(properties, ?) = ?", "$.#{k}", v.as_json)
|
|
40
|
+
end
|
|
47
41
|
end
|
|
48
42
|
else
|
|
49
43
|
raise "Adapter not supported: #{adapter_name}"
|
|
50
44
|
end
|
|
51
|
-
relation
|
|
52
45
|
end
|
|
53
46
|
alias_method :where_properties, :where_props
|
|
54
47
|
|
|
@@ -56,32 +49,32 @@ module Ahoy
|
|
|
56
49
|
# like with group
|
|
57
50
|
props.flatten!
|
|
58
51
|
|
|
59
|
-
relation =
|
|
60
|
-
|
|
61
|
-
column_type = columns_hash["properties"].type
|
|
62
|
-
adapter_name = connection.adapter_name.downcase
|
|
63
|
-
else
|
|
64
|
-
adapter_name = "mongoid"
|
|
65
|
-
end
|
|
52
|
+
relation = all
|
|
53
|
+
adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
|
|
66
54
|
case adapter_name
|
|
67
55
|
when "mongoid"
|
|
68
56
|
raise "Adapter not supported: #{adapter_name}"
|
|
69
|
-
when /mysql/
|
|
70
|
-
column = column_type == :json || connection.try(:mariadb?) ? "properties" : "CAST(properties AS JSON)"
|
|
57
|
+
when /mysql|trilogy/i
|
|
71
58
|
props.each do |prop|
|
|
72
|
-
quoted_prop =
|
|
73
|
-
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(
|
|
59
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
|
|
60
|
+
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(properties, #{quoted_prop}))")
|
|
74
61
|
end
|
|
75
|
-
when /
|
|
62
|
+
when /postg/i
|
|
76
63
|
# convert to jsonb to fix
|
|
77
64
|
# could not identify an equality operator for type json
|
|
78
65
|
# and for text columns
|
|
66
|
+
column_type = columns_hash["properties"].type
|
|
79
67
|
cast = [:jsonb, :hstore].include?(column_type) ? "" : "::jsonb"
|
|
80
68
|
|
|
81
69
|
props.each do |prop|
|
|
82
|
-
quoted_prop =
|
|
70
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote(prop) }
|
|
83
71
|
relation = relation.group("properties#{cast} -> #{quoted_prop}")
|
|
84
72
|
end
|
|
73
|
+
when /sqlite/i
|
|
74
|
+
props.each do |prop|
|
|
75
|
+
quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
|
|
76
|
+
relation = relation.group("JSON_EXTRACT(properties, #{quoted_prop})")
|
|
77
|
+
end
|
|
85
78
|
else
|
|
86
79
|
raise "Adapter not supported: #{adapter_name}"
|
|
87
80
|
end
|
data/lib/ahoy/tracker.rb
CHANGED
|
@@ -49,7 +49,7 @@ module Ahoy
|
|
|
49
49
|
visit_token: visit_token,
|
|
50
50
|
visitor_token: visitor_token,
|
|
51
51
|
user_id: user.try(:id),
|
|
52
|
-
started_at: trusted_time(started_at)
|
|
52
|
+
started_at: trusted_time(started_at)
|
|
53
53
|
}.merge(visit_properties).select { |_, v| v }
|
|
54
54
|
|
|
55
55
|
@store.track_visit(data)
|
|
@@ -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 ||=
|
|
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
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,27 @@ module Ahoy
|
|
|
32
33
|
properties_type == "text" || (properties_type == "json" && ActiveRecord::Base.connection.try(:mariadb?))
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
# use
|
|
36
|
+
# use connection_db_config instead of connection.adapter
|
|
36
37
|
# so database connection isn't needed
|
|
37
38
|
def adapter
|
|
38
|
-
|
|
39
|
-
ActiveRecord::Base.connection_db_config.adapter.to_s
|
|
40
|
-
else
|
|
41
|
-
ActiveRecord::Base.connection_config[:adapter].to_s
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def rails52?
|
|
46
|
-
ActiveRecord::VERSION::STRING.to_f >= 5.2
|
|
39
|
+
ActiveRecord::Base.connection_db_config.adapter.to_s
|
|
47
40
|
end
|
|
48
41
|
|
|
49
42
|
def migration_version
|
|
50
43
|
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
51
44
|
end
|
|
45
|
+
|
|
46
|
+
def primary_key_type
|
|
47
|
+
", id: :#{key_type}" if key_type
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def foreign_key_type
|
|
51
|
+
", type: :#{key_type}" if key_type
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def key_type
|
|
55
|
+
Rails.configuration.generators.options.dig(:active_record, :primary_key_type)
|
|
56
|
+
end
|
|
52
57
|
end
|
|
53
58
|
end
|
|
54
59
|
end
|
|
@@ -11,12 +11,12 @@ module Ahoy
|
|
|
11
11
|
|
|
12
12
|
selection =
|
|
13
13
|
if activerecord && mongoid
|
|
14
|
-
puts
|
|
14
|
+
puts <<~MSG
|
|
15
15
|
|
|
16
|
-
Which data store would you like to use?
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
Which data store would you like to use?
|
|
17
|
+
1. ActiveRecord (default)
|
|
18
|
+
2. Mongoid
|
|
19
|
+
3. Neither
|
|
20
20
|
MSG
|
|
21
21
|
|
|
22
22
|
ask(">")
|
|
@@ -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"
|
|
59
|
-
add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops<%
|
|
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
|
|
@@ -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 (
|
|
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 (
|
|
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
|
-
|
|
190
|
-
|
|
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 (
|
|
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(
|
|
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
|
|
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(
|
|
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 (
|
|
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:
|
|
328
|
+
section: getClosest(this, "data-section")
|
|
322
329
|
});
|
|
323
330
|
}
|
|
324
331
|
|
|
325
|
-
function
|
|
326
|
-
for (
|
|
327
|
-
if (element.hasAttribute(
|
|
328
|
-
return element.getAttribute(
|
|
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 (
|
|
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(
|
|
441
|
+
ahoy.ready(function () {
|
|
435
442
|
if (config.cookies && !ahoy.getVisitId()) {
|
|
436
443
|
createVisit();
|
|
437
444
|
}
|
|
438
445
|
|
|
439
|
-
ahoy.ready(
|
|
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(
|
|
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 (
|
|
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
|
|
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,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ahoy_matey
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 5.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -16,43 +15,42 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '7.1'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
25
|
+
version: '7.1'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: device_detector
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
32
|
+
version: '1'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
39
|
+
version: '1'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: safely_block
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
46
|
+
version: '0.4'
|
|
48
47
|
type: :runtime
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
description:
|
|
53
|
+
version: '0.4'
|
|
56
54
|
email: andrew@ankane.org
|
|
57
55
|
executables: []
|
|
58
56
|
extensions: []
|
|
@@ -65,14 +63,13 @@ files:
|
|
|
65
63
|
- app/controllers/ahoy/base_controller.rb
|
|
66
64
|
- app/controllers/ahoy/events_controller.rb
|
|
67
65
|
- app/controllers/ahoy/visits_controller.rb
|
|
68
|
-
- app/jobs/ahoy/geocode_job.rb
|
|
69
|
-
- app/jobs/ahoy/geocode_v2_job.rb
|
|
70
66
|
- config/routes.rb
|
|
71
67
|
- lib/ahoy.rb
|
|
72
68
|
- lib/ahoy/base_store.rb
|
|
73
69
|
- lib/ahoy/controller.rb
|
|
74
70
|
- lib/ahoy/database_store.rb
|
|
75
71
|
- lib/ahoy/engine.rb
|
|
72
|
+
- lib/ahoy/geocode_v2_job.rb
|
|
76
73
|
- lib/ahoy/helper.rb
|
|
77
74
|
- lib/ahoy/model.rb
|
|
78
75
|
- lib/ahoy/query_methods.rb
|
|
@@ -98,7 +95,6 @@ homepage: https://github.com/ankane/ahoy
|
|
|
98
95
|
licenses:
|
|
99
96
|
- MIT
|
|
100
97
|
metadata: {}
|
|
101
|
-
post_install_message:
|
|
102
98
|
rdoc_options: []
|
|
103
99
|
require_paths:
|
|
104
100
|
- lib
|
|
@@ -106,15 +102,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
106
102
|
requirements:
|
|
107
103
|
- - ">="
|
|
108
104
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '2
|
|
105
|
+
version: '3.2'
|
|
110
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
107
|
requirements:
|
|
112
108
|
- - ">="
|
|
113
109
|
- !ruby/object:Gem::Version
|
|
114
110
|
version: '0'
|
|
115
111
|
requirements: []
|
|
116
|
-
rubygems_version: 3.
|
|
117
|
-
signing_key:
|
|
112
|
+
rubygems_version: 3.6.9
|
|
118
113
|
specification_version: 4
|
|
119
114
|
summary: Simple, powerful, first-party analytics for Rails
|
|
120
115
|
test_files: []
|
|
File without changes
|