ahoy_matey 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|