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 +4 -4
- data/CHANGELOG.md +36 -0
- data/LICENSE.txt +1 -1
- data/README.md +62 -64
- 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/query_methods.rb +2 -2
- data/lib/ahoy/tracker.rb +14 -15
- 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 +20 -11
- 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 -14
- 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: f8f4afce5d34400d46473e74e607c7758ef18e93d719d476b83c54bca455bd76
|
4
|
+
data.tar.gz: a64234a08867ad06f2084d4191c920f1ef12c0e290161e906303e8bcb744ead1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
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
|
-
[](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
|
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
|
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
|
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
|
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
|
200
|
+
class AddAhoyVisitToOrders < ActiveRecord::Migration[7.1]
|
201
201
|
def change
|
202
|
-
|
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/
|
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 [
|
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 = -> {
|
318
|
+
Ahoy.token_generator = -> { ULID.generate }
|
333
319
|
```
|
334
320
|
|
335
321
|
### Throttling
|
336
322
|
|
337
|
-
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.
|
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
|
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.
|
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
|
374
|
+
gem "maxminddb"
|
385
375
|
```
|
386
376
|
|
387
|
-
|
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 =
|
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
|
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 =
|
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
|
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
|
-
###
|
776
|
+
### 5.0
|
779
777
|
|
780
|
-
|
778
|
+
Visits now expire with anonymity sets. If using `Ahoy.cookies = false`, a new index is needed.
|
781
779
|
|
782
|
-
|
780
|
+
For Active Record, create a migration with:
|
783
781
|
|
784
|
-
|
782
|
+
```ruby
|
783
|
+
add_index :ahoy_visits, [:visitor_token, :started_at]
|
784
|
+
```
|
785
785
|
|
786
|
-
|
787
|
-
gem 'geocoder'
|
788
|
-
```
|
786
|
+
For Mongoid, set:
|
789
787
|
|
790
|
-
|
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
|
822
|
+
To test different adapters, use:
|
815
823
|
|
816
824
|
```sh
|
817
|
-
|
818
|
-
|
819
|
-
bundle exec rake test
|
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
|
```
|
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/query_methods.rb
CHANGED
@@ -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 ||=
|
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,31 @@ module Ahoy
|
|
32
33
|
properties_type == "text" || (properties_type == "json" && ActiveRecord::Base.connection.try(:mariadb?))
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
-
|
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
|
-
|
46
|
-
|
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
|
@@ -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,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ahoy_matey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
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:
|
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: '
|
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: '
|
26
|
+
version: '6.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: device_detector
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
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:
|
40
|
+
version: '1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
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: '
|
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.
|
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
|
File without changes
|