ahoy_matey 3.2.0 → 3.3.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 +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +60 -31
- data/app/jobs/ahoy/geocode_v2_job.rb +1 -0
- data/lib/ahoy/database_store.rb +1 -1
- data/lib/ahoy/version.rb +1 -1
- data/lib/generators/ahoy/activerecord_generator.rb +5 -0
- data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +1 -1
- data/vendor/assets/javascripts/ahoy.js +38 -105
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d09c2a1f7783c94ad857652adfbe8150deb702f9162daf28c2bf9c11454f4277
|
4
|
+
data.tar.gz: 0e0a9779762c9ad912ba6d8866f85783c00aa0a96f3c53a4b6b803a3cdf41af2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e479ea5345820038c0a4fe4296504781614b53c6cfef5a79856c51073f768dc64082d494ba64d1e03ec41a346832c320565de63c5953f3bdf91e6e2697a1b7c9
|
7
|
+
data.tar.gz: 5fc7c4e73835b1ffc7be2d9c125894baccf204b7e7785784887574d80b8d7581d10e1ced1c0562cd7804ad66a22ce05df64ef6b302a981faa567569b618c8557
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
:fire: Simple, powerful, first-party analytics for Rails
|
4
4
|
|
5
|
-
Track visits and events in Ruby, JavaScript, and native apps. Data is stored in your database by default
|
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
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
|
8
8
|
|
@@ -74,14 +74,14 @@ ahoy.track("My second event", {language: "JavaScript"});
|
|
74
74
|
|
75
75
|
Check out [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
|
76
76
|
|
77
|
-
### GDPR Compliance
|
78
|
-
|
79
|
-
Ahoy provides a number of options to help with GDPR compliance. See the [GDPR section](#gdpr-compliance-1) for more info.
|
80
|
-
|
81
77
|
### Geocoding Setup
|
82
78
|
|
83
79
|
To enable geocoding, see the [Geocoding section](#geocoding).
|
84
80
|
|
81
|
+
### GDPR Compliance
|
82
|
+
|
83
|
+
Ahoy provides a number of options to help with GDPR compliance. See the [GDPR section](#gdpr-compliance-1) for more info.
|
84
|
+
|
85
85
|
## How It Works
|
86
86
|
|
87
87
|
### Visits
|
@@ -189,7 +189,7 @@ Order.joins(:ahoy_visit).group("device_type").count
|
|
189
189
|
Here’s what the migration to add the `ahoy_visit_id` column should look like:
|
190
190
|
|
191
191
|
```ruby
|
192
|
-
class AddVisitIdToOrders < ActiveRecord::Migration[6.
|
192
|
+
class AddVisitIdToOrders < ActiveRecord::Migration[6.1]
|
193
193
|
def change
|
194
194
|
add_column :orders, :ahoy_visit_id, :bigint
|
195
195
|
end
|
@@ -204,7 +204,7 @@ visitable :sign_up_visit
|
|
204
204
|
|
205
205
|
### Users
|
206
206
|
|
207
|
-
Ahoy automatically attaches the `current_user` to the visit. With [Devise](https://github.com/plataformatec/devise), it attaches the user even if
|
207
|
+
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.
|
208
208
|
|
209
209
|
With other authentication frameworks, add this to the end of your sign in method:
|
210
210
|
|
@@ -314,9 +314,41 @@ Set other [cookie options](https://api.rubyonrails.org/classes/ActionDispatch/Co
|
|
314
314
|
Ahoy.cookie_options = {same_site: :lax}
|
315
315
|
```
|
316
316
|
|
317
|
-
|
317
|
+
You can also [disable cookies](#anonymity-sets--cookies)
|
318
|
+
|
319
|
+
### Token Generation
|
318
320
|
|
319
|
-
Ahoy uses
|
321
|
+
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).
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
Ahoy.token_generator = -> { Druuid.gen }
|
325
|
+
```
|
326
|
+
|
327
|
+
### Throttling
|
328
|
+
|
329
|
+
You can use [Rack::Attack](https://github.com/kickstarter/rack-attack) to throttle requests to the API.
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
class Rack::Attack
|
333
|
+
throttle("ahoy/ip", limit: 20, period: 1.minute) do |req|
|
334
|
+
if req.path.start_with?("/ahoy/")
|
335
|
+
req.ip
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
### Exceptions
|
342
|
+
|
343
|
+
Exceptions are rescued so analytics do not break your app. Ahoy uses [Safely](https://github.com/ankane/safely) to try to report them to a service by default. To customize this, use:
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
Safely.report_exception_method = ->(e) { Rollbar.error(e) }
|
347
|
+
```
|
348
|
+
|
349
|
+
## Geocoding
|
350
|
+
|
351
|
+
Ahoy uses [Geocoder](https://github.com/alexreisner/geocoder) for geocoding. We recommend configuring [local geocoding](#local-geocoding) or [load balancer geocoding](#load-balancer-geocoding) so IP addresses are not sent to a 3rd party service. If you do use a 3rd party service and adhere to GDPR, be sure to add it to your subprocessor list. If Ahoy is configured to [mask IPs](#ip-masking), the masked IP is used (this can reduce accuracy but is better for privacy).
|
320
352
|
|
321
353
|
To enable geocoding, update `config/initializers/ahoy.rb`:
|
322
354
|
|
@@ -367,36 +399,29 @@ Geocoder.configure(
|
|
367
399
|
)
|
368
400
|
```
|
369
401
|
|
370
|
-
###
|
402
|
+
### Load Balancer Geocoding
|
371
403
|
|
372
|
-
|
404
|
+
Some load balancers can add geocoding information to request headers.
|
373
405
|
|
374
|
-
|
375
|
-
|
376
|
-
|
406
|
+
- [nginx](https://nginx.org/en/docs/http/ngx_http_geoip_module.html)
|
407
|
+
- [Google Cloud](https://cloud.google.com/load-balancing/docs/custom-headers)
|
408
|
+
- [Cloudflare](https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation)
|
377
409
|
|
378
|
-
|
379
|
-
|
380
|
-
You can use [Rack::Attack](https://github.com/kickstarter/rack-attack) to throttle requests to the API.
|
410
|
+
Update `config/initializers/ahoy.rb` with:
|
381
411
|
|
382
412
|
```ruby
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
413
|
+
Ahoy.geocode = false
|
414
|
+
|
415
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
416
|
+
def track_visit(data)
|
417
|
+
data[:country] = request.headers["<country-header>"]
|
418
|
+
data[:region] = request.headers["<region-header>"]
|
419
|
+
data[:city] = request.headers["<city-header>"]
|
420
|
+
super(data)
|
388
421
|
end
|
389
422
|
end
|
390
423
|
```
|
391
424
|
|
392
|
-
### Exceptions
|
393
|
-
|
394
|
-
Exceptions are rescued so analytics do not break your app. Ahoy uses [Safely](https://github.com/ankane/safely) to try to report them to a service by default. To customize this, use:
|
395
|
-
|
396
|
-
```ruby
|
397
|
-
Safely.report_exception_method = ->(e) { Rollbar.error(e) }
|
398
|
-
```
|
399
|
-
|
400
425
|
## GDPR Compliance
|
401
426
|
|
402
427
|
Ahoy provides a number of options to help with [GDPR compliance](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).
|
@@ -455,7 +480,11 @@ Ahoy can switch from cookies to [anonymity sets](https://privacypatterns.org/pat
|
|
455
480
|
Ahoy.cookies = false
|
456
481
|
```
|
457
482
|
|
458
|
-
Previously set cookies are automatically deleted.
|
483
|
+
Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
|
484
|
+
|
485
|
+
```javascript
|
486
|
+
ahoy.configure({cookies: false});
|
487
|
+
```
|
459
488
|
|
460
489
|
## Data Retention
|
461
490
|
|
@@ -14,6 +14,7 @@ module Ahoy
|
|
14
14
|
if location && location.country.present?
|
15
15
|
data = {
|
16
16
|
country: location.country,
|
17
|
+
country_code: location.try(:country_code).presence,
|
17
18
|
region: location.try(:state).presence,
|
18
19
|
city: location.try(:city).presence,
|
19
20
|
postal_code: location.try(:postal_code).presence,
|
data/lib/ahoy/database_store.rb
CHANGED
data/lib/ahoy/version.rb
CHANGED
@@ -27,6 +27,11 @@ module Ahoy
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
# requires database connection to check for MariaDB
|
31
|
+
def serialize_properties?
|
32
|
+
properties_type == "text" || (properties_type == "json" && ActiveRecord::Base.connection.try(:mariadb?))
|
33
|
+
end
|
34
|
+
|
30
35
|
# use connection_config instead of connection.adapter
|
31
36
|
# so database connection isn't needed
|
32
37
|
def adapter
|
@@ -4,7 +4,7 @@ class Ahoy::Event < ApplicationRecord
|
|
4
4
|
self.table_name = "ahoy_events"
|
5
5
|
|
6
6
|
belongs_to :visit
|
7
|
-
belongs_to :user, optional: true<% if
|
7
|
+
belongs_to :user, optional: true<% if serialize_properties? %>
|
8
8
|
|
9
9
|
serialize :properties, JSON<% end %>
|
10
10
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
/*!
|
2
2
|
* Ahoy.js
|
3
3
|
* Simple, powerful JavaScript analytics
|
4
4
|
* https://github.com/ankane/ahoy.js
|
5
|
-
* v0.3.
|
5
|
+
* v0.3.9
|
6
6
|
* MIT License
|
7
7
|
*/
|
8
8
|
|
@@ -12,97 +12,6 @@
|
|
12
12
|
(global = global || self, global.ahoy = factory());
|
13
13
|
}(this, (function () { 'use strict';
|
14
14
|
|
15
|
-
var isUndefined = function (value) { return value === undefined; };
|
16
|
-
|
17
|
-
var isNull = function (value) { return value === null; };
|
18
|
-
|
19
|
-
var isBoolean = function (value) { return typeof value === 'boolean'; };
|
20
|
-
|
21
|
-
var isObject = function (value) { return value === Object(value); };
|
22
|
-
|
23
|
-
var isArray = function (value) { return Array.isArray(value); };
|
24
|
-
|
25
|
-
var isDate = function (value) { return value instanceof Date; };
|
26
|
-
|
27
|
-
var isBlob = function (value) { return value &&
|
28
|
-
typeof value.size === 'number' &&
|
29
|
-
typeof value.type === 'string' &&
|
30
|
-
typeof value.slice === 'function'; };
|
31
|
-
|
32
|
-
var isFile = function (value) { return isBlob(value) &&
|
33
|
-
typeof value.name === 'string' &&
|
34
|
-
(typeof value.lastModifiedDate === 'object' ||
|
35
|
-
typeof value.lastModified === 'number'); };
|
36
|
-
|
37
|
-
var serialize = function (obj, cfg, fd, pre) {
|
38
|
-
cfg = cfg || {};
|
39
|
-
|
40
|
-
cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;
|
41
|
-
|
42
|
-
cfg.nullsAsUndefineds = isUndefined(cfg.nullsAsUndefineds)
|
43
|
-
? false
|
44
|
-
: cfg.nullsAsUndefineds;
|
45
|
-
|
46
|
-
cfg.booleansAsIntegers = isUndefined(cfg.booleansAsIntegers)
|
47
|
-
? false
|
48
|
-
: cfg.booleansAsIntegers;
|
49
|
-
|
50
|
-
cfg.allowEmptyArrays = isUndefined(cfg.allowEmptyArrays)
|
51
|
-
? false
|
52
|
-
: cfg.allowEmptyArrays;
|
53
|
-
|
54
|
-
fd = fd || new FormData();
|
55
|
-
|
56
|
-
if (isUndefined(obj)) {
|
57
|
-
return fd;
|
58
|
-
} else if (isNull(obj)) {
|
59
|
-
if (!cfg.nullsAsUndefineds) {
|
60
|
-
fd.append(pre, '');
|
61
|
-
}
|
62
|
-
} else if (isBoolean(obj)) {
|
63
|
-
if (cfg.booleansAsIntegers) {
|
64
|
-
fd.append(pre, obj ? 1 : 0);
|
65
|
-
} else {
|
66
|
-
fd.append(pre, obj);
|
67
|
-
}
|
68
|
-
} else if (isArray(obj)) {
|
69
|
-
if (obj.length) {
|
70
|
-
obj.forEach(function (value, index) {
|
71
|
-
var key = pre + '[' + (cfg.indices ? index : '') + ']';
|
72
|
-
|
73
|
-
serialize(value, cfg, fd, key);
|
74
|
-
});
|
75
|
-
} else if (cfg.allowEmptyArrays) {
|
76
|
-
fd.append(pre + '[]', '');
|
77
|
-
}
|
78
|
-
} else if (isDate(obj)) {
|
79
|
-
fd.append(pre, obj.toISOString());
|
80
|
-
} else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
|
81
|
-
Object.keys(obj).forEach(function (prop) {
|
82
|
-
var value = obj[prop];
|
83
|
-
|
84
|
-
if (isArray(value)) {
|
85
|
-
while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
|
86
|
-
prop = prop.substring(0, prop.length - 2);
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
var key = pre ? pre + '[' + prop + ']' : prop;
|
91
|
-
|
92
|
-
serialize(value, cfg, fd, key);
|
93
|
-
});
|
94
|
-
} else {
|
95
|
-
fd.append(pre, obj);
|
96
|
-
}
|
97
|
-
|
98
|
-
return fd;
|
99
|
-
};
|
100
|
-
|
101
|
-
var index_module = {
|
102
|
-
serialize: serialize,
|
103
|
-
};
|
104
|
-
var index_module_1 = index_module.serialize;
|
105
|
-
|
106
15
|
// https://www.quirksmode.org/js/cookies.html
|
107
16
|
|
108
17
|
var Cookies = {
|
@@ -190,6 +99,16 @@
|
|
190
99
|
return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
|
191
100
|
}
|
192
101
|
|
102
|
+
function serialize(object) {
|
103
|
+
var data = new FormData();
|
104
|
+
for (var key in object) {
|
105
|
+
if (object.hasOwnProperty(key)) {
|
106
|
+
data.append(key, object[key]);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
return data;
|
110
|
+
}
|
111
|
+
|
193
112
|
// cookies
|
194
113
|
|
195
114
|
function setCookie(name, value, ttl) {
|
@@ -238,7 +157,7 @@
|
|
238
157
|
if (matches.apply(element, [selector])) {
|
239
158
|
return element;
|
240
159
|
} else if (element.parentElement) {
|
241
|
-
return matchesSelector(element.parentElement, selector)
|
160
|
+
return matchesSelector(element.parentElement, selector);
|
242
161
|
}
|
243
162
|
return null;
|
244
163
|
} else {
|
@@ -370,7 +289,7 @@
|
|
370
289
|
// stringify so we keep the type
|
371
290
|
data.events_json = JSON.stringify(data.events);
|
372
291
|
delete data.events;
|
373
|
-
window.navigator.sendBeacon(eventsUrl(),
|
292
|
+
window.navigator.sendBeacon(eventsUrl(), serialize(data));
|
374
293
|
});
|
375
294
|
}
|
376
295
|
|
@@ -393,7 +312,7 @@
|
|
393
312
|
return obj;
|
394
313
|
}
|
395
314
|
|
396
|
-
function eventProperties(
|
315
|
+
function eventProperties() {
|
397
316
|
return cleanObject({
|
398
317
|
tag: this.tagName.toLowerCase(),
|
399
318
|
id: presence(this.id),
|
@@ -557,8 +476,12 @@
|
|
557
476
|
ahoy.track("$view", properties);
|
558
477
|
};
|
559
478
|
|
560
|
-
ahoy.trackClicks = function () {
|
561
|
-
|
479
|
+
ahoy.trackClicks = function (selector) {
|
480
|
+
if (selector === undefined) {
|
481
|
+
log("trackClicks will require a selector in 0.4.0");
|
482
|
+
selector = "a, button, input[type=submit]";
|
483
|
+
}
|
484
|
+
onEvent("click", selector, function (e) {
|
562
485
|
var properties = eventProperties.call(this, e);
|
563
486
|
properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
|
564
487
|
properties.href = this.href;
|
@@ -566,25 +489,35 @@
|
|
566
489
|
});
|
567
490
|
};
|
568
491
|
|
569
|
-
ahoy.trackSubmits = function () {
|
570
|
-
|
492
|
+
ahoy.trackSubmits = function (selector) {
|
493
|
+
if (selector === undefined) {
|
494
|
+
log("trackSubmits will require a selector in 0.4.0");
|
495
|
+
selector = "form";
|
496
|
+
}
|
497
|
+
onEvent("submit", selector, function (e) {
|
571
498
|
var properties = eventProperties.call(this, e);
|
572
499
|
ahoy.track("$submit", properties);
|
573
500
|
});
|
574
501
|
};
|
575
502
|
|
576
|
-
ahoy.trackChanges = function () {
|
577
|
-
|
503
|
+
ahoy.trackChanges = function (selector) {
|
504
|
+
if (selector === undefined) {
|
505
|
+
// put here instead of above to prevent message with trackAll
|
506
|
+
log("trackChanges is deprecated and will be removed in 0.4.0");
|
507
|
+
selector = "input, textarea, select";
|
508
|
+
}
|
509
|
+
onEvent("change", selector, function (e) {
|
578
510
|
var properties = eventProperties.call(this, e);
|
579
511
|
ahoy.track("$change", properties);
|
580
512
|
});
|
581
513
|
};
|
582
514
|
|
583
515
|
ahoy.trackAll = function() {
|
516
|
+
log("trackAll is deprecated and will be removed in 0.4.0");
|
584
517
|
ahoy.trackView();
|
585
|
-
ahoy.trackClicks();
|
586
|
-
ahoy.trackSubmits();
|
587
|
-
ahoy.trackChanges();
|
518
|
+
ahoy.trackClicks("a, button, input[type=submit]");
|
519
|
+
ahoy.trackSubmits("form");
|
520
|
+
ahoy.trackChanges("input, textarea, select");
|
588
521
|
};
|
589
522
|
|
590
523
|
// push events from queue
|
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: 3.
|
4
|
+
version: 3.3.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: 2021-
|
11
|
+
date: 2021-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
127
|
- !ruby/object:Gem::Version
|
128
128
|
version: '0'
|
129
129
|
requirements: []
|
130
|
-
rubygems_version: 3.2.
|
130
|
+
rubygems_version: 3.2.22
|
131
131
|
signing_key:
|
132
132
|
specification_version: 4
|
133
133
|
summary: Simple, powerful, first-party analytics for Rails
|