nano-pure-pkg 0.0.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 +7 -0
- data/ahoy_matey-5.5.0/CHANGELOG.md +403 -0
- data/ahoy_matey-5.5.0/CONTRIBUTING.md +42 -0
- data/ahoy_matey-5.5.0/LICENSE.txt +22 -0
- data/ahoy_matey-5.5.0/README.md +802 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/base_controller.rb +44 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/events_controller.rb +51 -0
- data/ahoy_matey-5.5.0/app/controllers/ahoy/visits_controller.rb +15 -0
- data/ahoy_matey-5.5.0/config/routes.rb +10 -0
- data/ahoy_matey-5.5.0/lib/ahoy/base_store.rb +104 -0
- data/ahoy_matey-5.5.0/lib/ahoy/controller.rb +56 -0
- data/ahoy_matey-5.5.0/lib/ahoy/database_store.rb +96 -0
- data/ahoy_matey-5.5.0/lib/ahoy/engine.rb +37 -0
- data/ahoy_matey-5.5.0/lib/ahoy/geocode_v2_job.rb +31 -0
- data/ahoy_matey-5.5.0/lib/ahoy/helper.rb +40 -0
- data/ahoy_matey-5.5.0/lib/ahoy/model.rb +15 -0
- data/ahoy_matey-5.5.0/lib/ahoy/query_methods.rb +88 -0
- data/ahoy_matey-5.5.0/lib/ahoy/tracker.rb +287 -0
- data/ahoy_matey-5.5.0/lib/ahoy/utils.rb +7 -0
- data/ahoy_matey-5.5.0/lib/ahoy/version.rb +3 -0
- data/ahoy_matey-5.5.0/lib/ahoy/visit_properties.rb +122 -0
- data/ahoy_matey-5.5.0/lib/ahoy/warden.rb +5 -0
- data/ahoy_matey-5.5.0/lib/ahoy.rb +167 -0
- data/ahoy_matey-5.5.0/lib/ahoy_matey.rb +1 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/activerecord_generator.rb +59 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/base_generator.rb +13 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/install_generator.rb +44 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/mongoid_generator.rb +16 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/base_store_initializer.rb.tt +25 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_event_model.rb.tt +14 -0
- data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_visit_model.rb.tt +50 -0
- data/ahoy_matey-5.5.0/vendor/assets/javascripts/ahoy.js +544 -0
- data/nano-pure-pkg.gemspec +12 -0
- metadata +77 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# Ahoy
|
|
2
|
+
|
|
3
|
+
:fire: Simple, powerful, first-party analytics for Rails
|
|
4
|
+
|
|
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
|
+
|
|
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
|
+
|
|
9
|
+
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
|
10
|
+
|
|
11
|
+
[](https://github.com/ankane/ahoy/actions)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application’s Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "ahoy_matey"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
And run:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bundle install
|
|
25
|
+
rails generate ahoy:install
|
|
26
|
+
rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Restart your web server, open a page in your browser, and a visit will be created :tada:
|
|
30
|
+
|
|
31
|
+
Track your first event from a controller with:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
ahoy.track "My first event", language: "Ruby"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### JavaScript, Native Apps, & AMP
|
|
38
|
+
|
|
39
|
+
Enable the API in `config/initializers/ahoy.rb`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
Ahoy.api = true
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
And restart your web server.
|
|
46
|
+
|
|
47
|
+
### JavaScript
|
|
48
|
+
|
|
49
|
+
For Importmap (Rails 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:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bun add ahoy.js
|
|
65
|
+
# or
|
|
66
|
+
yarn add ahoy.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
And add to `app/javascript/application.js`:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import ahoy from "ahoy.js"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For Sprockets, add to `app/assets/javascripts/application.js`:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
//= require ahoy
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Track an event with:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
ahoy.track("My second event", {language: "JavaScript"});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Native Apps
|
|
88
|
+
|
|
89
|
+
Check out [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
|
|
90
|
+
|
|
91
|
+
### Geocoding Setup
|
|
92
|
+
|
|
93
|
+
To enable geocoding, see the [Geocoding section](#geocoding).
|
|
94
|
+
|
|
95
|
+
### GDPR Compliance
|
|
96
|
+
|
|
97
|
+
Ahoy provides a number of options to help with GDPR compliance. See the [GDPR section](#gdpr-compliance-1) for more info.
|
|
98
|
+
|
|
99
|
+
## How It Works
|
|
100
|
+
|
|
101
|
+
### Visits
|
|
102
|
+
|
|
103
|
+
When someone visits your website, Ahoy creates a visit with lots of useful information.
|
|
104
|
+
|
|
105
|
+
- **traffic source** - referrer, referring domain, landing page
|
|
106
|
+
- **location** - country, region, city, latitude, longitude
|
|
107
|
+
- **technology** - browser, OS, device type
|
|
108
|
+
- **utm parameters** - source, medium, term, content, campaign
|
|
109
|
+
|
|
110
|
+
Use the `current_visit` method to access it.
|
|
111
|
+
|
|
112
|
+
Prevent certain Rails actions from creating visits with:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
skip_before_action :track_ahoy_visit
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This is typically useful for APIs. If your entire Rails app is an API, you can use:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
Ahoy.api_only = true
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
You can also defer visit tracking to JavaScript. This is useful for preventing bots (that aren’t detected by their user agent) and users with cookies disabled from creating a new visit on each request. `:when_needed` will create visits server-side only when needed by events, and `false` will disable server-side creation completely, discarding events without a visit.
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Ahoy.server_side_visits = :when_needed
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Events
|
|
131
|
+
|
|
132
|
+
Each event has a `name` and `properties`. There are several ways to track events.
|
|
133
|
+
|
|
134
|
+
#### Ruby
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
ahoy.track "Viewed book", title: "Hot, Flat, and Crowded"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Track actions automatically with:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
class ApplicationController < ActionController::Base
|
|
144
|
+
after_action :track_action
|
|
145
|
+
|
|
146
|
+
protected
|
|
147
|
+
|
|
148
|
+
def track_action
|
|
149
|
+
ahoy.track "Ran action", request.path_parameters
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### JavaScript
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
ahoy.track("Viewed book", {title: "The World is Flat"});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
See [Ahoy.js](https://github.com/ankane/ahoy.js) for a complete list of features.
|
|
161
|
+
|
|
162
|
+
#### Native Apps
|
|
163
|
+
|
|
164
|
+
See the docs for [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
|
|
165
|
+
|
|
166
|
+
#### AMP
|
|
167
|
+
|
|
168
|
+
```erb
|
|
169
|
+
<head>
|
|
170
|
+
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
|
|
171
|
+
</head>
|
|
172
|
+
<body>
|
|
173
|
+
<%= amp_event "Viewed article", title: "Analytics with Rails" %>
|
|
174
|
+
</body>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Associated Models
|
|
178
|
+
|
|
179
|
+
Say we want to associate orders with visits. Just add `visitable` to the model.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
class Order < ApplicationRecord
|
|
183
|
+
visitable :ahoy_visit
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
When a visitor places an order, the `ahoy_visit_id` column is automatically set :tada:
|
|
188
|
+
|
|
189
|
+
See where orders are coming from with simple joins:
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
Order.joins(:ahoy_visit).group("referring_domain").count
|
|
193
|
+
Order.joins(:ahoy_visit).group("city").count
|
|
194
|
+
Order.joins(:ahoy_visit).group("device_type").count
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Here’s what the migration to add the `ahoy_visit_id` column should look like:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
class AddAhoyVisitToOrders < ActiveRecord::Migration[8.1]
|
|
201
|
+
def change
|
|
202
|
+
add_reference :orders, :ahoy_visit
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Customize the column with:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
visitable :sign_up_visit
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Users
|
|
214
|
+
|
|
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
|
+
|
|
217
|
+
With other authentication frameworks, add this to the end of your sign in method:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
ahoy.authenticate(user)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
To see the visits for a given user, create an association:
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
class User < ApplicationRecord
|
|
227
|
+
has_many :visits, class_name: "Ahoy::Visit"
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
And use:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
User.find(123).visits
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Custom User Method
|
|
238
|
+
|
|
239
|
+
Use a method besides `current_user`
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
Ahoy.user_method = :true_user
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
or use a proc
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
Ahoy.user_method = ->(controller) { controller.true_user }
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Doorkeeper
|
|
252
|
+
|
|
253
|
+
To attach the user with [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), be sure you have a `current_resource_owner` method in `ApplicationController`.
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
class ApplicationController < ActionController::Base
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def current_resource_owner
|
|
260
|
+
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Exclusions
|
|
266
|
+
|
|
267
|
+
Bots are excluded from tracking by default. To include them, use:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
Ahoy.track_bots = true
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Add your own rules with:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
Ahoy.exclude_method = lambda do |controller, request|
|
|
277
|
+
request.ip == "192.168.1.1"
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Visit Duration
|
|
282
|
+
|
|
283
|
+
By default, a new visit is created after 4 hours of inactivity. Change this with:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
Ahoy.visit_duration = 30.minutes
|
|
287
|
+
```
|
|
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
|
+
|
|
297
|
+
### Cookies
|
|
298
|
+
|
|
299
|
+
To track visits across multiple subdomains, use:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
Ahoy.cookie_domain = :all
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Set other [cookie options](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) with:
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
Ahoy.cookie_options = {same_site: :lax}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
You can also [disable cookies](#anonymity-sets--cookies)
|
|
312
|
+
|
|
313
|
+
### Token Generation
|
|
314
|
+
|
|
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).
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
Ahoy.token_generator = -> { ULID.generate }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Throttling
|
|
322
|
+
|
|
323
|
+
You can use [Rack::Attack](https://github.com/rack/rack-attack) to throttle requests to the API.
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
class Rack::Attack
|
|
327
|
+
throttle("ahoy/ip", limit: 20, period: 1.minute) do |req|
|
|
328
|
+
if req.path.start_with?("/ahoy/")
|
|
329
|
+
req.ip
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Exceptions
|
|
336
|
+
|
|
337
|
+
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:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
Safely.report_exception_method = ->(e) { Rollbar.error(e) }
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Geocoding
|
|
344
|
+
|
|
345
|
+
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).
|
|
346
|
+
|
|
347
|
+
To enable geocoding, add this line to your application’s Gemfile:
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
gem "geocoder"
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
And update `config/initializers/ahoy.rb`:
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
Ahoy.geocode = true
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Geocoding is performed in a background job so it doesn’t slow down web requests. The default job queue is `:ahoy`. Change this with:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
Ahoy.job_queue = :low_priority
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Local Geocoding
|
|
366
|
+
|
|
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:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
gem "maxminddb"
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
And create `config/initializers/geocoder.rb` with:
|
|
378
|
+
|
|
379
|
+
```ruby
|
|
380
|
+
Geocoder.configure(
|
|
381
|
+
ip_lookup: :geoip2,
|
|
382
|
+
geoip2: {
|
|
383
|
+
file: "path/to/GeoLite2-City.mmdb"
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
For country-level geocoding, install the `geoip-database` package. It’s preinstalled on Heroku. For Ubuntu, use:
|
|
389
|
+
|
|
390
|
+
```sh
|
|
391
|
+
sudo apt-get install geoip-database
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Add this line to your application’s Gemfile:
|
|
395
|
+
|
|
396
|
+
```ruby
|
|
397
|
+
gem "geoip"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
And create `config/initializers/geocoder.rb` with:
|
|
401
|
+
|
|
402
|
+
```ruby
|
|
403
|
+
Geocoder.configure(
|
|
404
|
+
ip_lookup: :maxmind_local,
|
|
405
|
+
maxmind_local: {
|
|
406
|
+
file: "/usr/share/GeoIP/GeoIP.dat",
|
|
407
|
+
package: :country
|
|
408
|
+
}
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Load Balancer Geocoding
|
|
413
|
+
|
|
414
|
+
Some load balancers can add geocoding information to request headers.
|
|
415
|
+
|
|
416
|
+
- [nginx](https://nginx.org/en/docs/http/ngx_http_geoip_module.html)
|
|
417
|
+
- [Google Cloud](https://cloud.google.com/load-balancing/docs/custom-headers)
|
|
418
|
+
- [Cloudflare](https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation)
|
|
419
|
+
|
|
420
|
+
Update `config/initializers/ahoy.rb` with:
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
Ahoy.geocode = false
|
|
424
|
+
|
|
425
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
426
|
+
def track_visit(data)
|
|
427
|
+
data[:country] = request.headers["<country-header>"]
|
|
428
|
+
data[:region] = request.headers["<region-header>"]
|
|
429
|
+
data[:city] = request.headers["<city-header>"]
|
|
430
|
+
super(data)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## GDPR Compliance
|
|
436
|
+
|
|
437
|
+
Ahoy provides a number of options to help with [GDPR compliance](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).
|
|
438
|
+
|
|
439
|
+
Update `config/initializers/ahoy.rb` with:
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
443
|
+
def authenticate(data)
|
|
444
|
+
# disables automatic linking of visits and users
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
Ahoy.mask_ips = true
|
|
449
|
+
Ahoy.cookies = :none
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
This:
|
|
453
|
+
|
|
454
|
+
- Masks IP addresses
|
|
455
|
+
- Switches from cookies to anonymity sets
|
|
456
|
+
- Disables automatic linking of visits and users
|
|
457
|
+
|
|
458
|
+
If you use JavaScript tracking, also set:
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
ahoy.configure({cookies: false});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### IP Masking
|
|
465
|
+
|
|
466
|
+
Ahoy can mask IPs with the same approach [Google Analytics uses for IP anonymization](https://support.google.com/analytics/answer/2763052). This means:
|
|
467
|
+
|
|
468
|
+
- For IPv4, the last octet is set to 0 (`8.8.4.4` becomes `8.8.4.0`)
|
|
469
|
+
- For IPv6, the last 80 bits are set to zeros (`2001:4860:4860:0:0:0:0:8844` becomes `2001:4860:4860::`)
|
|
470
|
+
|
|
471
|
+
```ruby
|
|
472
|
+
Ahoy.mask_ips = true
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
IPs are masked before geolocation is performed.
|
|
476
|
+
|
|
477
|
+
To mask previously collected IPs, use:
|
|
478
|
+
|
|
479
|
+
```ruby
|
|
480
|
+
Ahoy::Visit.find_each do |visit|
|
|
481
|
+
visit.update_column :ip, Ahoy.mask_ip(visit.ip)
|
|
482
|
+
end
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Anonymity Sets & Cookies
|
|
486
|
+
|
|
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.
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
Ahoy.cookies = :none
|
|
491
|
+
```
|
|
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
|
+
|
|
495
|
+
Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
ahoy.configure({cookies: false});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Data Retention
|
|
502
|
+
|
|
503
|
+
Data should only be retained for as long as it’s needed. Delete older data with:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
Ahoy::Visit.where("started_at < ?", 2.years.ago).find_in_batches do |visits|
|
|
507
|
+
visit_ids = visits.map(&:id)
|
|
508
|
+
Ahoy::Event.where(visit_id: visit_ids).delete_all
|
|
509
|
+
Ahoy::Visit.where(id: visit_ids).delete_all
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
You can use [Rollup](https://github.com/ankane/rollup) to aggregate important data before you do.
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
Ahoy::Visit.rollup("Visits", interval: "hour")
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Delete data for a specific user with:
|
|
520
|
+
|
|
521
|
+
```ruby
|
|
522
|
+
user_id = 123
|
|
523
|
+
visit_ids = Ahoy::Visit.where(user_id: user_id).pluck(:id)
|
|
524
|
+
Ahoy::Event.where(visit_id: visit_ids).delete_all
|
|
525
|
+
Ahoy::Visit.where(id: visit_ids).delete_all
|
|
526
|
+
Ahoy::Event.where(user_id: user_id).delete_all
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Development
|
|
530
|
+
|
|
531
|
+
Ahoy is built with developers in mind. You can run the following code in your browser’s console.
|
|
532
|
+
|
|
533
|
+
Force a new visit
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
ahoy.reset(); // then reload the page
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
Log messages
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
ahoy.debug();
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Turn off logging
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
ahoy.debug(false);
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Debug API requests in Ruby
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
Ahoy.quiet = false
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## Data Stores
|
|
558
|
+
|
|
559
|
+
Data tracked by Ahoy is sent to your data store. Ahoy ships with a data store that uses your Rails database by default. You can find it in `config/initializers/ahoy.rb`:
|
|
560
|
+
|
|
561
|
+
```ruby
|
|
562
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
563
|
+
end
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
There are four events data stores can subscribe to:
|
|
567
|
+
|
|
568
|
+
```ruby
|
|
569
|
+
class Ahoy::Store < Ahoy::BaseStore
|
|
570
|
+
def track_visit(data)
|
|
571
|
+
# new visit
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def track_event(data)
|
|
575
|
+
# new event
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def geocode(data)
|
|
579
|
+
# visit geocoded
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def authenticate(data)
|
|
583
|
+
# user authenticates
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Data stores are designed to be highly customizable so you can scale as you grow. Check out [examples](docs/Data-Store-Examples.md) for Kafka, RabbitMQ, Fluentd, NATS, NSQ, and Amazon Kinesis Firehose.
|
|
589
|
+
|
|
590
|
+
### Track Additional Data
|
|
591
|
+
|
|
592
|
+
```ruby
|
|
593
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
594
|
+
def track_visit(data)
|
|
595
|
+
data[:accept_language] = request.headers["Accept-Language"]
|
|
596
|
+
super(data)
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Two useful methods you can use are `request` and `controller`.
|
|
602
|
+
|
|
603
|
+
You can pass additional visit data from JavaScript with:
|
|
604
|
+
|
|
605
|
+
```javascript
|
|
606
|
+
ahoy.configure({visitParams: {referral_code: 123}});
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
And use:
|
|
610
|
+
|
|
611
|
+
```ruby
|
|
612
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
613
|
+
def track_visit(data)
|
|
614
|
+
data[:referral_code] = request.parameters[:referral_code]
|
|
615
|
+
super(data)
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### Use Different Models
|
|
621
|
+
|
|
622
|
+
```ruby
|
|
623
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
|
624
|
+
def visit_model
|
|
625
|
+
MyVisit
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def event_model
|
|
629
|
+
MyEvent
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Explore the Data
|
|
635
|
+
|
|
636
|
+
[Blazer](https://github.com/ankane/blazer) is a great tool for exploring your data.
|
|
637
|
+
|
|
638
|
+
With Active Record, you can do:
|
|
639
|
+
|
|
640
|
+
```ruby
|
|
641
|
+
Ahoy::Visit.group(:search_keyword).count
|
|
642
|
+
Ahoy::Visit.group(:country).count
|
|
643
|
+
Ahoy::Visit.group(:referring_domain).count
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
[Chartkick](https://www.chartkick.com/) and [Groupdate](https://github.com/ankane/groupdate) make it easy to visualize the data.
|
|
647
|
+
|
|
648
|
+
```erb
|
|
649
|
+
<%= line_chart Ahoy::Visit.group_by_day(:started_at).count %>
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Querying Events
|
|
653
|
+
|
|
654
|
+
Ahoy provides a few methods on the event model to make querying easier.
|
|
655
|
+
|
|
656
|
+
To query on both name and properties, you can use:
|
|
657
|
+
|
|
658
|
+
```ruby
|
|
659
|
+
Ahoy::Event.where_event("Viewed product", product_id: 123).count
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Or just query properties with:
|
|
663
|
+
|
|
664
|
+
```ruby
|
|
665
|
+
Ahoy::Event.where_props(product_id: 123, category: "Books").count
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
Group by properties with:
|
|
669
|
+
|
|
670
|
+
```ruby
|
|
671
|
+
Ahoy::Event.group_prop(:product_id, :category).count
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Note: MySQL and MariaDB always return string keys (including `"null"` for `nil`) for `group_prop`.
|
|
675
|
+
|
|
676
|
+
### Funnels
|
|
677
|
+
|
|
678
|
+
It’s easy to create funnels.
|
|
679
|
+
|
|
680
|
+
```ruby
|
|
681
|
+
viewed_store_ids = Ahoy::Event.where(name: "Viewed store").distinct.pluck(:user_id)
|
|
682
|
+
added_item_ids = Ahoy::Event.where(user_id: viewed_store_ids, name: "Added item to cart").distinct.pluck(:user_id)
|
|
683
|
+
viewed_checkout_ids = Ahoy::Event.where(user_id: added_item_ids, name: "Viewed checkout").distinct.pluck(:user_id)
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
The same approach also works with visitor tokens.
|
|
687
|
+
|
|
688
|
+
### Rollups
|
|
689
|
+
|
|
690
|
+
Improve query performance by pre-aggregating data with [Rollup](https://github.com/ankane/rollup).
|
|
691
|
+
|
|
692
|
+
```ruby
|
|
693
|
+
Ahoy::Event.where(name: "Viewed store").rollup("Store views")
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
This is only needed if you have a lot of data.
|
|
697
|
+
|
|
698
|
+
### Forecasting
|
|
699
|
+
|
|
700
|
+
To forecast future visits and events, check out [Prophet](https://github.com/ankane/prophet).
|
|
701
|
+
|
|
702
|
+
```ruby
|
|
703
|
+
daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
|
|
704
|
+
Prophet.forecast(daily_visits)
|
|
705
|
+
```
|
|
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
|
+
|
|
725
|
+
### Recommendations
|
|
726
|
+
|
|
727
|
+
To make recommendations based on events, check out [Disco](https://github.com/ankane/disco#ahoy).
|
|
728
|
+
|
|
729
|
+
## Tutorials
|
|
730
|
+
|
|
731
|
+
- [Tracking Metrics with Ahoy and Blazer](https://gorails.com/episodes/internal-metrics-with-ahoy-and-blazer)
|
|
732
|
+
|
|
733
|
+
## API Spec
|
|
734
|
+
|
|
735
|
+
### Visits
|
|
736
|
+
|
|
737
|
+
Generate visit and visitor tokens as [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier), and include these values in the `Ahoy-Visit` and `Ahoy-Visitor` headers with all requests.
|
|
738
|
+
|
|
739
|
+
Send a `POST` request to `/ahoy/visits` with `Content-Type: application/json` and a body like:
|
|
740
|
+
|
|
741
|
+
```json
|
|
742
|
+
{
|
|
743
|
+
"visit_token": "<visit-token>",
|
|
744
|
+
"visitor_token": "<visitor-token>",
|
|
745
|
+
"platform": "iOS",
|
|
746
|
+
"app_version": "1.0.0",
|
|
747
|
+
"os_version": "11.2.6"
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
After 4 hours of inactivity, create another visit (use the same visitor token).
|
|
752
|
+
|
|
753
|
+
### Events
|
|
754
|
+
|
|
755
|
+
Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` and a body like:
|
|
756
|
+
|
|
757
|
+
```json
|
|
758
|
+
{
|
|
759
|
+
"visit_token": "<visit-token>",
|
|
760
|
+
"visitor_token": "<visitor-token>",
|
|
761
|
+
"events": [
|
|
762
|
+
{
|
|
763
|
+
"id": "<optional-random-id>",
|
|
764
|
+
"name": "Viewed item",
|
|
765
|
+
"properties": {
|
|
766
|
+
"item_id": 123
|
|
767
|
+
},
|
|
768
|
+
"time": "2025-01-01T00:00:00-07:00"
|
|
769
|
+
}
|
|
770
|
+
]
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
## History
|
|
775
|
+
|
|
776
|
+
View the [changelog](https://github.com/ankane/ahoy/blob/master/CHANGELOG.md)
|
|
777
|
+
|
|
778
|
+
## Contributing
|
|
779
|
+
|
|
780
|
+
Everyone is encouraged to help improve this project. Here are a few ways you can help:
|
|
781
|
+
|
|
782
|
+
- [Report bugs](https://github.com/ankane/ahoy/issues)
|
|
783
|
+
- Fix bugs and [submit pull requests](https://github.com/ankane/ahoy/pulls)
|
|
784
|
+
- Write, clarify, or fix documentation
|
|
785
|
+
- Suggest or add new features
|
|
786
|
+
|
|
787
|
+
To get started with development:
|
|
788
|
+
|
|
789
|
+
```sh
|
|
790
|
+
git clone https://github.com/ankane/ahoy.git
|
|
791
|
+
cd ahoy
|
|
792
|
+
bundle install
|
|
793
|
+
bundle exec rake test
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
To test different adapters, use:
|
|
797
|
+
|
|
798
|
+
```sh
|
|
799
|
+
ADAPTER=postgresql bundle exec rake test
|
|
800
|
+
ADAPTER=mysql2 bundle exec rake test
|
|
801
|
+
ADAPTER=mongoid bundle exec rake test
|
|
802
|
+
```
|