ahoy_matey 1.5.5 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +184 -34
  3. data/CONTRIBUTING.md +42 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +464 -407
  6. data/app/controllers/ahoy/base_controller.rb +23 -15
  7. data/app/controllers/ahoy/events_controller.rb +8 -2
  8. data/app/controllers/ahoy/visits_controller.rb +8 -1
  9. data/app/jobs/ahoy/geocode_job.rb +11 -0
  10. data/app/jobs/ahoy/geocode_v2_job.rb +31 -0
  11. data/config/routes.rb +1 -1
  12. data/lib/ahoy/base_store.rb +101 -0
  13. data/lib/ahoy/controller.rb +23 -16
  14. data/lib/ahoy/database_store.rb +94 -0
  15. data/lib/ahoy/engine.rb +14 -7
  16. data/lib/ahoy/helper.rb +40 -0
  17. data/lib/ahoy/model.rb +5 -27
  18. data/lib/ahoy/query_methods.rb +88 -0
  19. data/lib/ahoy/tracker.rb +105 -51
  20. data/lib/ahoy/utils.rb +7 -0
  21. data/lib/ahoy/version.rb +1 -1
  22. data/lib/ahoy/visit_properties.rb +99 -37
  23. data/lib/ahoy.rb +83 -93
  24. data/lib/ahoy_matey.rb +1 -1
  25. data/lib/generators/ahoy/activerecord_generator.rb +67 -0
  26. data/lib/generators/ahoy/base_generator.rb +13 -0
  27. data/lib/generators/ahoy/install_generator.rb +44 -0
  28. data/lib/generators/ahoy/mongoid_generator.rb +16 -0
  29. data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  30. data/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
  31. data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  32. data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +25 -0
  33. data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
  34. data/lib/generators/ahoy/{stores/templates/mongoid_event_model.rb → templates/mongoid_event_model.rb.tt} +4 -2
  35. data/lib/generators/ahoy/{stores/templates/mongoid_visit_model.rb → templates/mongoid_visit_model.rb.tt} +15 -9
  36. data/vendor/assets/javascripts/ahoy.js +271 -133
  37. metadata +37 -273
  38. data/.gitignore +0 -17
  39. data/Gemfile +0 -6
  40. data/Rakefile +0 -8
  41. data/ahoy_matey.gemspec +0 -38
  42. data/lib/ahoy/deckhands/location_deckhand.rb +0 -49
  43. data/lib/ahoy/deckhands/request_deckhand.rb +0 -52
  44. data/lib/ahoy/deckhands/technology_deckhand.rb +0 -47
  45. data/lib/ahoy/deckhands/traffic_source_deckhand.rb +0 -22
  46. data/lib/ahoy/deckhands/utm_parameter_deckhand.rb +0 -23
  47. data/lib/ahoy/geocode_job.rb +0 -13
  48. data/lib/ahoy/logger_silencer.rb +0 -75
  49. data/lib/ahoy/properties.rb +0 -58
  50. data/lib/ahoy/stores/active_record_store.rb +0 -61
  51. data/lib/ahoy/stores/active_record_token_store.rb +0 -114
  52. data/lib/ahoy/stores/base_store.rb +0 -88
  53. data/lib/ahoy/stores/bunny_store.rb +0 -33
  54. data/lib/ahoy/stores/fluentd_store.rb +0 -17
  55. data/lib/ahoy/stores/kafka_store.rb +0 -40
  56. data/lib/ahoy/stores/kinesis_firehose_store.rb +0 -42
  57. data/lib/ahoy/stores/log_store.rb +0 -53
  58. data/lib/ahoy/stores/mongoid_store.rb +0 -63
  59. data/lib/ahoy/stores/nats_store.rb +0 -34
  60. data/lib/ahoy/stores/nsq_store.rb +0 -36
  61. data/lib/ahoy/subscribers/active_record.rb +0 -19
  62. data/lib/ahoy/throttle.rb +0 -17
  63. data/lib/generators/ahoy/stores/active_record_events_generator.rb +0 -53
  64. data/lib/generators/ahoy/stores/active_record_generator.rb +0 -16
  65. data/lib/generators/ahoy/stores/active_record_visits_generator.rb +0 -43
  66. data/lib/generators/ahoy/stores/bunny_generator.rb +0 -15
  67. data/lib/generators/ahoy/stores/custom_generator.rb +0 -15
  68. data/lib/generators/ahoy/stores/fluentd_generator.rb +0 -15
  69. data/lib/generators/ahoy/stores/kafka_generator.rb +0 -15
  70. data/lib/generators/ahoy/stores/kinesis_firehose_generator.rb +0 -15
  71. data/lib/generators/ahoy/stores/log_generator.rb +0 -15
  72. data/lib/generators/ahoy/stores/mongoid_events_generator.rb +0 -19
  73. data/lib/generators/ahoy/stores/mongoid_generator.rb +0 -14
  74. data/lib/generators/ahoy/stores/mongoid_visits_generator.rb +0 -27
  75. data/lib/generators/ahoy/stores/nats_generator.rb +0 -15
  76. data/lib/generators/ahoy/stores/nsq_generator.rb +0 -15
  77. data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +0 -12
  78. data/lib/generators/ahoy/stores/templates/active_record_events_migration.rb +0 -19
  79. data/lib/generators/ahoy/stores/templates/active_record_initializer.rb +0 -3
  80. data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +0 -4
  81. data/lib/generators/ahoy/stores/templates/active_record_visits_migration.rb +0 -57
  82. data/lib/generators/ahoy/stores/templates/bunny_initializer.rb +0 -9
  83. data/lib/generators/ahoy/stores/templates/custom_initializer.rb +0 -10
  84. data/lib/generators/ahoy/stores/templates/fluentd_initializer.rb +0 -3
  85. data/lib/generators/ahoy/stores/templates/kafka_initializer.rb +0 -9
  86. data/lib/generators/ahoy/stores/templates/kinesis_firehose_initializer.rb +0 -17
  87. data/lib/generators/ahoy/stores/templates/log_initializer.rb +0 -3
  88. data/lib/generators/ahoy/stores/templates/mongoid_initializer.rb +0 -3
  89. data/lib/generators/ahoy/stores/templates/nats_initializer.rb +0 -9
  90. data/lib/generators/ahoy/stores/templates/nsq_initializer.rb +0 -9
  91. data/test/properties/mysql_json_test.rb +0 -18
  92. data/test/properties/mysql_text_test.rb +0 -19
  93. data/test/properties/postgresql_hstore_test.rb +0 -18
  94. data/test/properties/postgresql_json_test.rb +0 -18
  95. data/test/properties/postgresql_jsonb_test.rb +0 -18
  96. data/test/properties/postgresql_text_test.rb +0 -19
  97. data/test/test_helper.rb +0 -99
  98. data/test/visit_properties_test.rb +0 -44
data/README.md CHANGED
@@ -1,451 +1,536 @@
1
1
  # Ahoy
2
2
 
3
- Ahoy provides a solid foundation to track visits and events in Ruby, JavaScript, and native apps. Works with any data store so you can easily scale.
3
+ :fire: Simple, powerful, first-party analytics for Rails
4
4
 
5
- :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
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
6
8
 
7
- :postbox: To track emails, check out [Ahoy Email](https://github.com/ankane/ahoy_email).
9
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
8
10
 
9
- :maple_leaf: For A/B testing, check out [Field Test](https://github.com/ankane/field_test).
11
+ [![Build Status](https://github.com/ankane/ahoy/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy/actions)
10
12
 
11
13
  ## Installation
12
14
 
13
15
  Add this line to your application’s Gemfile:
14
16
 
15
17
  ```ruby
16
- gem 'ahoy_matey'
18
+ gem "ahoy_matey"
17
19
  ```
18
20
 
19
- And add the javascript file in `app/assets/javascripts/application.js` after jQuery.
21
+ And run:
20
22
 
21
- ```javascript
22
- //= require jquery
23
- //= require ahoy
23
+ ```sh
24
+ bundle install
25
+ rails generate ahoy:install
26
+ rails db:migrate
24
27
  ```
25
28
 
26
- ## Choose a Data Store
29
+ Restart your web server, open a page in your browser, and a visit will be created :tada:
27
30
 
28
- Ahoy supports a number of data stores out of the box. You can start with one of them and customize as needed, or create your own store from scratch.
31
+ Track your first event from a controller with:
29
32
 
30
- - [PostgreSQL, MySQL, or SQLite](#postgresql-mysql-or-sqlite)
31
- - [MongoDB](#mongodb)
32
- - [Kafka](#kafka), [Fluentd](#fluentd), [RabbitMQ](#rabbitmq), [NATS](#nats), [NSQ](#nsq), or [Amazon Kinesis Firehose](#amazon-kinesis-firehose)
33
- - [Logs](#logs)
34
- - [Custom](#custom)
33
+ ```ruby
34
+ ahoy.track "My first event", language: "Ruby"
35
+ ```
35
36
 
36
- ### PostgreSQL, MySQL, or SQLite
37
+ ### JavaScript, Native Apps, & AMP
37
38
 
38
- Run:
39
+ Enable the API in `config/initializers/ahoy.rb`:
39
40
 
40
- ```sh
41
- rails generate ahoy:stores:active_record
42
- rake db:migrate
41
+ ```ruby
42
+ Ahoy.api = true
43
43
  ```
44
44
 
45
- ### MongoDB
45
+ And restart your web server.
46
46
 
47
- Run:
47
+ ### JavaScript
48
48
 
49
- ```sh
50
- rails generate ahoy:stores:mongoid
51
- ```
49
+ For Rails 7 / Importmap, add to `config/importmap.rb`:
52
50
 
53
- ### Kafka
51
+ ```ruby
52
+ pin "ahoy", to: "ahoy.js"
53
+ ```
54
54
 
55
- Add [ruby-kafka](https://github.com/zendesk/ruby-kafka) to your Gemfile.
55
+ And add to `app/javascript/application.js`:
56
56
 
57
- ```ruby
58
- gem 'ruby-kafka'
57
+ ```javascript
58
+ import "ahoy"
59
59
  ```
60
60
 
61
- And run:
61
+ For Rails 6 / Webpacker, run:
62
62
 
63
63
  ```sh
64
- rails generate ahoy:stores:kafka
64
+ yarn add ahoy.js
65
65
  ```
66
66
 
67
- Use `ENV["KAFKA_URL"]` to configure.
67
+ And add to `app/javascript/packs/application.js`:
68
68
 
69
- ### Fluentd
69
+ ```javascript
70
+ import ahoy from "ahoy.js"
71
+ ```
70
72
 
71
- Add [fluent-logger](https://github.com/fluent/fluent-logger-ruby) to your Gemfile.
73
+ For Rails 5 / Sprockets, add to `app/assets/javascripts/application.js`:
72
74
 
73
- ```ruby
74
- gem 'fluent-logger'
75
+ ```javascript
76
+ //= require ahoy
75
77
  ```
76
78
 
77
- And run:
79
+ Track an event with:
78
80
 
79
- ```sh
80
- rails generate ahoy:stores:fluentd
81
+ ```javascript
82
+ ahoy.track("My second event", {language: "JavaScript"});
81
83
  ```
82
84
 
83
- Use `ENV["FLUENTD_HOST"]` and `ENV["FLUENTD_PORT"]` to configure.
85
+ ### Native Apps
84
86
 
85
- ### RabbitMQ
87
+ Check out [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
86
88
 
87
- Add [bunny](https://github.com/ruby-amqp/bunny) to your Gemfile.
89
+ ### Geocoding Setup
88
90
 
89
- ```ruby
90
- gem 'bunny'
91
- ```
91
+ To enable geocoding, see the [Geocoding section](#geocoding).
92
92
 
93
- And run:
93
+ ### GDPR Compliance
94
94
 
95
- ```sh
96
- rails generate ahoy:stores:bunny
97
- ```
95
+ Ahoy provides a number of options to help with GDPR compliance. See the [GDPR section](#gdpr-compliance-1) for more info.
98
96
 
99
- Use `ENV["RABBITMQ_URL"]` to configure.
97
+ ## How It Works
100
98
 
101
- ### NATS
99
+ ### Visits
102
100
 
103
- Add [nats-pure](https://github.com/nats-io/pure-ruby-nats) to your Gemfile.
101
+ When someone visits your website, Ahoy creates a visit with lots of useful information.
104
102
 
105
- ```ruby
106
- gem 'nats-pure'
107
- ```
103
+ - **traffic source** - referrer, referring domain, landing page
104
+ - **location** - country, region, city, latitude, longitude
105
+ - **technology** - browser, OS, device type
106
+ - **utm parameters** - source, medium, term, content, campaign
108
107
 
109
- And run:
108
+ Use the `current_visit` method to access it.
110
109
 
111
- ```sh
112
- rails generate ahoy:stores:nats
110
+ Prevent certain Rails actions from creating visits with:
111
+
112
+ ```ruby
113
+ skip_before_action :track_ahoy_visit
113
114
  ```
114
115
 
115
- Use `ENV["NATS_URL"]` to configure.
116
+ This is typically useful for APIs. If your entire Rails app is an API, you can use:
116
117
 
117
- ### NSQ
118
+ ```ruby
119
+ Ahoy.api_only = true
120
+ ```
118
121
 
119
- Add [nsq-ruby](https://github.com/wistia/nsq-ruby) to your Gemfile.
122
+ 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.
120
123
 
121
124
  ```ruby
122
- gem 'nsq-ruby'
125
+ Ahoy.server_side_visits = :when_needed
123
126
  ```
124
127
 
125
- And run:
128
+ ### Events
126
129
 
127
- ```sh
128
- rails generate ahoy:stores:nsq
129
- ```
130
+ Each event has a `name` and `properties`. There are several ways to track events.
130
131
 
131
- Use `ENV["NSQ_URL"]` to configure.
132
+ #### Ruby
132
133
 
133
- ### Amazon Kinesis Firehose
134
+ ```ruby
135
+ ahoy.track "Viewed book", title: "Hot, Flat, and Crowded"
136
+ ```
134
137
 
135
- Add [aws-sdk](https://github.com/aws/aws-sdk-ruby) to your Gemfile.
138
+ Track actions automatically with:
136
139
 
137
140
  ```ruby
138
- gem 'aws-sdk', '>= 2.0.0'
141
+ class ApplicationController < ActionController::Base
142
+ after_action :track_action
143
+
144
+ protected
145
+
146
+ def track_action
147
+ ahoy.track "Ran action", request.path_parameters
148
+ end
149
+ end
139
150
  ```
140
151
 
141
- And run:
152
+ #### JavaScript
142
153
 
143
- ```sh
144
- rails generate ahoy:stores:kinesis_firehose
154
+ ```javascript
155
+ ahoy.track("Viewed book", {title: "The World is Flat"});
145
156
  ```
146
157
 
147
- Configure delivery streams and credentials in the initializer.
158
+ See [Ahoy.js](https://github.com/ankane/ahoy.js) for a complete list of features.
148
159
 
149
- ### Logs
160
+ #### Native Apps
150
161
 
151
- ```sh
152
- rails generate ahoy:stores:log
162
+ See the docs for [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
163
+
164
+ #### AMP
165
+
166
+ ```erb
167
+ <head>
168
+ <script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
169
+ </head>
170
+ <body>
171
+ <%= amp_event "Viewed article", title: "Analytics with Rails" %>
172
+ </body>
153
173
  ```
154
174
 
155
- This logs visits to `log/visits.log` and events to `log/events.log`.
175
+ ### Associated Models
156
176
 
157
- ### Custom
177
+ Say we want to associate orders with visits. Just add `visitable` to the model.
158
178
 
159
- ```sh
160
- rails generate ahoy:stores:custom
179
+ ```ruby
180
+ class Order < ApplicationRecord
181
+ visitable :ahoy_visit
182
+ end
161
183
  ```
162
184
 
163
- This creates a class for you to fill out.
185
+ When a visitor places an order, the `ahoy_visit_id` column is automatically set :tada:
186
+
187
+ See where orders are coming from with simple joins:
164
188
 
165
189
  ```ruby
166
- class Ahoy::Store < Ahoy::Stores::BaseStore
167
- def track_visit(options)
168
- end
190
+ Order.joins(:ahoy_visit).group("referring_domain").count
191
+ Order.joins(:ahoy_visit).group("city").count
192
+ Order.joins(:ahoy_visit).group("device_type").count
193
+ ```
194
+
195
+ Here’s what the migration to add the `ahoy_visit_id` column should look like:
169
196
 
170
- def track_event(name, properties, options)
197
+ ```ruby
198
+ class AddAhoyVisitToOrders < ActiveRecord::Migration[7.0]
199
+ def change
200
+ add_reference :orders, :ahoy_visit
171
201
  end
172
202
  end
173
203
  ```
174
204
 
175
- See the [ActiveRecordTokenStore](https://github.com/ankane/ahoy/blob/master/lib/ahoy/stores/active_record_token_store.rb) for an example.
205
+ Customize the column with:
176
206
 
177
- ## How It Works
207
+ ```ruby
208
+ visitable :sign_up_visit
209
+ ```
178
210
 
179
- ### Visits
211
+ ### Users
180
212
 
181
- When someone visits your website, Ahoy creates a visit with lots of useful information.
213
+ 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.
182
214
 
183
- - **traffic source** - referrer, referring domain, landing page, search keyword
184
- - **location** - country, region, and city
185
- - **technology** - browser, OS, and device type
186
- - **utm parameters** - source, medium, term, content, campaign
215
+ With other authentication frameworks, add this to the end of your sign in method:
187
216
 
188
- Use the `current_visit` method to access it.
217
+ ```ruby
218
+ ahoy.authenticate(user)
219
+ ```
189
220
 
190
- ### Events
221
+ To see the visits for a given user, create an association:
191
222
 
192
- Each event has a `name` and `properties`.
223
+ ```ruby
224
+ class User < ApplicationRecord
225
+ has_many :visits, class_name: "Ahoy::Visit"
226
+ end
227
+ ```
193
228
 
194
- There are three ways to track events.
229
+ And use:
195
230
 
196
- #### JavaScript
231
+ ```ruby
232
+ User.find(123).visits
233
+ ```
197
234
 
198
- ```javascript
199
- ahoy.track("Viewed book", {title: "The World is Flat"});
235
+ #### Custom User Method
236
+
237
+ Use a method besides `current_user`
238
+
239
+ ```ruby
240
+ Ahoy.user_method = :true_user
200
241
  ```
201
242
 
202
- or track events automatically with:
243
+ or use a proc
203
244
 
204
- ```javascript
205
- ahoy.trackAll();
245
+ ```ruby
246
+ Ahoy.user_method = ->(controller) { controller.true_user }
206
247
  ```
207
248
 
208
- See [Ahoy.js](https://github.com/ankane/ahoy.js) for a complete list of features.
249
+ #### Doorkeeper
209
250
 
210
- #### Ruby
251
+ To attach the user with [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), be sure you have a `current_resource_owner` method in `ApplicationController`.
211
252
 
212
253
  ```ruby
213
- ahoy.track "Viewed book", title: "Hot, Flat, and Crowded"
254
+ class ApplicationController < ActionController::Base
255
+ private
256
+
257
+ def current_resource_owner
258
+ User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
259
+ end
260
+ end
214
261
  ```
215
262
 
216
- #### Native Apps
263
+ #### Knock
217
264
 
218
- See the [HTTP spec](#native-apps-1) until libraries are built.
265
+ To attach the user with [Knock](https://github.com/nsarno/knock), either include `Knock::Authenticable`in `ApplicationController`:
219
266
 
220
- ### Users
267
+ ```ruby
268
+ class ApplicationController < ActionController::API
269
+ include Knock::Authenticable
270
+ end
271
+ ```
221
272
 
222
- Ahoy automatically attaches the `current_user` to the visit.
273
+ Or include it in Ahoy:
223
274
 
224
- With [Devise](https://github.com/plataformatec/devise), it will attach the user even if he or she signs in after the visit starts.
275
+ ```ruby
276
+ Ahoy::BaseController.include Knock::Authenticable
277
+ ```
225
278
 
226
- With other authentication frameworks, add this to the end of your sign in method:
279
+ And use:
227
280
 
228
281
  ```ruby
229
- ahoy.authenticate(user)
282
+ Ahoy.user_method = ->(controller) { controller.send(:authenticate_entity, "user") }
230
283
  ```
231
284
 
232
- ## Customize the Store
285
+ ### Exclusions
233
286
 
234
- Stores are built to be highly customizable.
287
+ Bots are excluded from tracking by default. To include them, use:
235
288
 
236
289
  ```ruby
237
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
238
- # add methods here
239
- end
290
+ Ahoy.track_bots = true
240
291
  ```
241
292
 
242
- ### Exclude Bots and More
243
-
244
- Exclude visits and events from being tracked with:
293
+ Add your own rules with:
245
294
 
246
295
  ```ruby
247
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
248
- def exclude?
249
- bot? || request.ip == "192.168.1.1"
250
- end
296
+ Ahoy.exclude_method = lambda do |controller, request|
297
+ request.ip == "192.168.1.1"
251
298
  end
252
299
  ```
253
300
 
254
- Bots are excluded by default.
301
+ ### Visit Duration
255
302
 
256
- ### Track Additional Values
303
+ By default, a new visit is created after 4 hours of inactivity. Change this with:
257
304
 
258
305
  ```ruby
259
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
260
- def track_visit(options)
261
- super do |visit|
262
- visit.gclid = visit_properties.landing_params["gclid"]
263
- end
264
- end
265
-
266
- def track_event(name, properties, options)
267
- super do |event|
268
- event.ip = request.ip
269
- end
270
- end
271
- end
306
+ Ahoy.visit_duration = 30.minutes
272
307
  ```
273
308
 
274
- Some methods you can use are `request`, `controller`, `visit_properties`, and `ahoy`.
309
+ ### Cookies
275
310
 
276
- ### Customize User
311
+ To track visits across multiple subdomains, use:
277
312
 
278
- If you use a method other than `current_user`, set it here:
313
+ ```ruby
314
+ Ahoy.cookie_domain = :all
315
+ ```
316
+
317
+ Set other [cookie options](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) with:
279
318
 
280
319
  ```ruby
281
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
282
- def user
283
- controller.true_user
284
- end
285
- end
320
+ Ahoy.cookie_options = {same_site: :lax}
286
321
  ```
287
322
 
288
- ### Report Exceptions
323
+ You can also [disable cookies](#anonymity-sets--cookies)
289
324
 
290
- 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.
325
+ ### Token Generation
291
326
 
292
- To customize this, use:
327
+ 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).
293
328
 
294
329
  ```ruby
295
- Safely.report_exception_method = -> (e) { Rollbar.error(e) }
330
+ Ahoy.token_generator = -> { Druuid.gen }
296
331
  ```
297
332
 
298
- ### Use Different Models
333
+ ### Throttling
299
334
 
300
- For ActiveRecord and Mongoid stores
335
+ You can use [Rack::Attack](https://github.com/kickstarter/rack-attack) to throttle requests to the API.
301
336
 
302
337
  ```ruby
303
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
304
- def visit_model
305
- CustomVisit
306
- end
307
-
308
- def event_model
309
- CustomEvent
338
+ class Rack::Attack
339
+ throttle("ahoy/ip", limit: 20, period: 1.minute) do |req|
340
+ if req.path.start_with?("/ahoy/")
341
+ req.ip
342
+ end
310
343
  end
311
344
  end
312
345
  ```
313
346
 
314
- ## More Features
315
-
316
- ### Automatic Tracking
347
+ ### Exceptions
317
348
 
318
- Page views
349
+ 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:
319
350
 
320
- ```javascript
321
- ahoy.trackView();
351
+ ```ruby
352
+ Safely.report_exception_method = ->(e) { Rollbar.error(e) }
322
353
  ```
323
354
 
324
- Clicks
355
+ ## Geocoding
325
356
 
326
- ```javascript
327
- ahoy.trackClicks();
357
+ 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).
358
+
359
+ To enable geocoding, add this line to your application’s Gemfile:
360
+
361
+ ```ruby
362
+ gem "geocoder"
328
363
  ```
329
364
 
330
- Rails actions
365
+ And update `config/initializers/ahoy.rb`:
331
366
 
332
367
  ```ruby
333
- class ApplicationController < ActionController::Base
334
- after_action :track_action
368
+ Ahoy.geocode = true
369
+ ```
335
370
 
336
- protected
371
+ Geocoding is performed in a background job so it doesn’t slow down web requests. The default job queue is `:ahoy`. Change this with:
337
372
 
338
- def track_action
339
- ahoy.track "Viewed #{controller_name}##{action_name}"
340
- end
341
- end
373
+ ```ruby
374
+ Ahoy.job_queue = :low_priority
342
375
  ```
343
376
 
344
- ### Multiple Subdomains
377
+ ### Local Geocoding
345
378
 
346
- To track visits across multiple subdomains, use:
379
+ For privacy and performance, we recommend geocoding locally. Add this line to your application’s Gemfile:
347
380
 
348
381
  ```ruby
349
- Ahoy.cookie_domain = :all
382
+ gem "maxminddb"
350
383
  ```
351
384
 
352
- ### Visit Duration
385
+ For city-level geocoding, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geoip2/geolite2/) and create `config/initializers/geocoder.rb` with:
353
386
 
354
- By default, a new visit is created after 4 hours of inactivity.
387
+ ```ruby
388
+ Geocoder.configure(
389
+ ip_lookup: :geoip2,
390
+ geoip2: {
391
+ file: "path/to/GeoLite2-City.mmdb"
392
+ }
393
+ )
394
+ ```
395
+
396
+ For country-level geocoding, install the `geoip-database` package. It’s preinstalled on Heroku. For Ubuntu, use:
397
+
398
+ ```sh
399
+ sudo apt-get install geoip-database
400
+ ```
355
401
 
356
- Change this with:
402
+ And create `config/initializers/geocoder.rb` with:
357
403
 
358
404
  ```ruby
359
- Ahoy.visit_duration = 30.minutes
405
+ Geocoder.configure(
406
+ ip_lookup: :maxmind_local,
407
+ maxmind_local: {
408
+ file: "/usr/share/GeoIP/GeoIP.dat",
409
+ package: :country
410
+ }
411
+ )
360
412
  ```
361
413
 
362
- ### ActiveRecord
414
+ ### Load Balancer Geocoding
415
+
416
+ Some load balancers can add geocoding information to request headers.
363
417
 
364
- Let’s associate orders with visits.
418
+ - [nginx](https://nginx.org/en/docs/http/ngx_http_geoip_module.html)
419
+ - [Google Cloud](https://cloud.google.com/load-balancing/docs/custom-headers)
420
+ - [Cloudflare](https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation)
365
421
 
366
- First, generate a migration and add a `visit_id` column.
422
+ Update `config/initializers/ahoy.rb` with:
367
423
 
368
424
  ```ruby
369
- class AddVisitIdToOrders < ActiveRecord::Migration
370
- def change
371
- add_column :orders, :visit_id, :integer
425
+ Ahoy.geocode = false
426
+
427
+ class Ahoy::Store < Ahoy::DatabaseStore
428
+ def track_visit(data)
429
+ data[:country] = request.headers["<country-header>"]
430
+ data[:region] = request.headers["<region-header>"]
431
+ data[:city] = request.headers["<city-header>"]
432
+ super(data)
372
433
  end
373
434
  end
374
435
  ```
375
436
 
376
- **Note**: Use the `uuid` column type if the `id` column on `visits` is a `uuid`.
437
+ ## GDPR Compliance
438
+
439
+ Ahoy provides a number of options to help with [GDPR compliance](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).
377
440
 
378
- Then, add `visitable` to the model.
441
+ Update `config/initializers/ahoy.rb` with:
379
442
 
380
443
  ```ruby
381
- class Order < ActiveRecord::Base
382
- visitable
444
+ class Ahoy::Store < Ahoy::DatabaseStore
445
+ def authenticate(data)
446
+ # disables automatic linking of visits and users
447
+ end
383
448
  end
449
+
450
+ Ahoy.mask_ips = true
451
+ Ahoy.cookies = false
384
452
  ```
385
453
 
386
- When a visitor places an order, the `visit_id` column is automatically set. :tada:
454
+ This:
387
455
 
388
- Customize the column and class name with:
456
+ - Masks IP addresses
457
+ - Switches from cookies to anonymity sets
458
+ - Disables automatic linking of visits and users
389
459
 
390
- ```ruby
391
- visitable :sign_up_visit, class_name: "Visit"
460
+ If you use JavaScript tracking, also set:
461
+
462
+ ```javascript
463
+ ahoy.configure({cookies: false});
392
464
  ```
393
465
 
394
- ### Doorkeeper
466
+ ### IP Masking
395
467
 
396
- To attach the user with [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), be sure you have a `current_resource_owner` method in `ApplicationController`.
468
+ Ahoy can mask IPs with the same approach [Google Analytics uses for IP anonymization](https://support.google.com/analytics/answer/2763052). This means:
397
469
 
398
- ```ruby
399
- class ApplicationController < ActionController::Base
400
- private
470
+ - For IPv4, the last octet is set to 0 (`8.8.4.4` becomes `8.8.4.0`)
471
+ - For IPv6, the last 80 bits are set to zeros (`2001:4860:4860:0:0:0:0:8844` becomes `2001:4860:4860::`)
401
472
 
402
- def current_resource_owner
403
- User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
404
- end
405
- end
473
+ ```ruby
474
+ Ahoy.mask_ips = true
406
475
  ```
407
476
 
408
- ### Geocoding
477
+ IPs are masked before geolocation is performed.
409
478
 
410
- By default, geocoding is performed inline. For performance, move it to the background with:
479
+ To mask previously collected IPs, use:
411
480
 
412
481
  ```ruby
413
- Ahoy.geocode = :async
482
+ Ahoy::Visit.find_each do |visit|
483
+ visit.update_column :ip, Ahoy.mask_ip(visit.ip)
484
+ end
414
485
  ```
415
486
 
416
- For Rails 4.0 and 4.1, you’ll need to add [activejob_backport](https://github.com/ankane/activejob_backport).
487
+ ### Anonymity Sets & Cookies
417
488
 
418
- To change the queue name (`ahoy` by default), use:
489
+ 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.
419
490
 
420
491
  ```ruby
421
- Ahoy.job_queue = :low_priority
492
+ Ahoy.cookies = false
422
493
  ```
423
494
 
424
- Or disable geocoding with:
495
+ Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
425
496
 
426
- ```ruby
427
- Ahoy.geocode = false
497
+ ```javascript
498
+ ahoy.configure({cookies: false});
428
499
  ```
429
500
 
430
- ### Track Visits Immediately
501
+ Note: With anonymity sets, visits no longer expire after 4 hours of inactivity. A new visit is only created when the IP mask or user agent changes (for instance, when a user updates their browser). There are plans to address this in the next major version.
502
+
503
+ ## Data Retention
431
504
 
432
- Visitor and visit ids are generated on the first request (so you can use them immediately), but the `track_visit` method isnt called until the JavaScript library posts to the server. This prevents browsers with cookies disabled from creating multiple visits and ensures visits are not created for API endpoints. Change this with:
505
+ Data should only be retained for as long as its needed. Delete older data with:
433
506
 
434
507
  ```ruby
435
- Ahoy.track_visits_immediately = true
508
+ Ahoy::Visit.where("started_at < ?", 2.years.ago).find_in_batches do |visits|
509
+ visit_ids = visits.map(&:id)
510
+ Ahoy::Event.where(visit_id: visit_ids).delete_all
511
+ Ahoy::Visit.where(id: visit_ids).delete_all
512
+ end
436
513
  ```
437
514
 
438
- **Note:** It’s highly recommended to perform geocoding in the background with this option.
515
+ You can use [Rollup](https://github.com/ankane/rollup) to aggregate important data before you do.
439
516
 
440
- You can exclude API endpoints and other actions with:
517
+ ```ruby
518
+ Ahoy::Visit.rollup("Visits", interval: "hour")
519
+ ```
520
+
521
+ Delete data for a specific user with:
441
522
 
442
523
  ```ruby
443
- skip_before_action :track_ahoy_visit
524
+ user_id = 123
525
+ visit_ids = Ahoy::Visit.where(user_id: user_id).pluck(:id)
526
+ Ahoy::Event.where(visit_id: visit_ids).delete_all
527
+ Ahoy::Visit.where(id: visit_ids).delete_all
528
+ Ahoy::Event.where(user_id: user_id).delete_all
444
529
  ```
445
530
 
446
531
  ## Development
447
532
 
448
- Ahoy is built with developers in mind. You can run the following code in your browser’s console.
533
+ Ahoy is built with developers in mind. You can run the following code in your browser’s console.
449
534
 
450
535
  Force a new visit
451
536
 
@@ -465,289 +550,244 @@ Turn off logging
465
550
  ahoy.debug(false);
466
551
  ```
467
552
 
468
- Debug endpoint requests in Ruby
553
+ Debug API requests in Ruby
469
554
 
470
555
  ```ruby
471
556
  Ahoy.quiet = false
472
557
  ```
473
558
 
474
- ## Explore the Data
475
-
476
- How you explore the data depends on the data store used.
477
-
478
- For SQL databases, you can use [Blazer](https://github.com/ankane/blazer) to easily generate charts and dashboards.
559
+ ## Data Stores
479
560
 
480
- With ActiveRecord, you can do:
561
+ 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`:
481
562
 
482
563
  ```ruby
483
- Visit.group(:search_keyword).count
484
- Visit.group(:country).count
485
- Visit.group(:referring_domain).count
486
- ```
487
-
488
- [Chartkick](http://chartkick.com/) and [Groupdate](https://github.com/ankane/groupdate) make it easy to visualize the data.
489
-
490
- ```erb
491
- <%= line_chart Visit.group_by_day(:started_at).count %>
564
+ class Ahoy::Store < Ahoy::DatabaseStore
565
+ end
492
566
  ```
493
567
 
494
- See where orders are coming from with simple joins:
568
+ There are four events data stores can subscribe to:
495
569
 
496
570
  ```ruby
497
- Order.joins(:visit).group("referring_domain").count
498
- Order.joins(:visit).group("city").count
499
- Order.joins(:visit).group("device_type").count
500
- ```
571
+ class Ahoy::Store < Ahoy::BaseStore
572
+ def track_visit(data)
573
+ # new visit
574
+ end
501
575
 
502
- To see the visits for a given user, create an association:
576
+ def track_event(data)
577
+ # new event
578
+ end
503
579
 
504
- ```ruby
505
- class User < ActiveRecord::Base
506
- has_many :visits
580
+ def geocode(data)
581
+ # visit geocoded
582
+ end
583
+
584
+ def authenticate(data)
585
+ # user authenticates
586
+ end
507
587
  end
508
588
  ```
509
589
 
510
- And use:
511
-
512
- ```ruby
513
- user = User.first
514
- user.visits
515
- ```
590
+ 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.
516
591
 
517
- ### Create Funnels
592
+ ### Track Additional Data
518
593
 
519
594
  ```ruby
520
- viewed_store_ids = Ahoy::Event.where(name: "Viewed store").uniq.pluck(:user_id)
521
- added_item_ids = Ahoy::Event.where(user_id: viewed_store_ids, name: "Added item to cart").uniq.pluck(:user_id)
522
- viewed_checkout_ids = Ahoy::Event.where(user_id: added_item_ids, name: "Viewed checkout").uniq.pluck(:user_id)
595
+ class Ahoy::Store < Ahoy::DatabaseStore
596
+ def track_visit(data)
597
+ data[:accept_language] = request.headers["Accept-Language"]
598
+ super(data)
599
+ end
600
+ end
523
601
  ```
524
602
 
525
- The same approach also works with visitor tokens.
526
-
527
- ### Querying Properties
603
+ Two useful methods you can use are `request` and `controller`.
528
604
 
529
- With ActiveRecord, use:
605
+ You can pass additional visit data from JavaScript with:
530
606
 
531
- ```ruby
532
- Ahoy::Event.where(name: "Viewed product").where_properties(product_id: 123).count
607
+ ```javascript
608
+ ahoy.configure({visitParams: {referral_code: 123}});
533
609
  ```
534
610
 
535
- **Note:** If you get a `NoMethodError`, upgrade Ahoy and add `include Ahoy::Properties` to the Ahoy::Event class:
611
+ And use:
536
612
 
537
613
  ```ruby
538
- module Ahoy
539
- class Event < ActiveRecord::Base
540
- include Ahoy::Properties
541
- ...
614
+ class Ahoy::Store < Ahoy::DatabaseStore
615
+ def track_visit(data)
616
+ data[:referral_code] = request.parameters[:referral_code]
617
+ super(data)
542
618
  end
543
619
  end
544
620
  ```
545
621
 
546
- ### Throttling
547
-
548
- By default, Ahoy uses [rack-attack](https://github.com/kickstarter/rack-attack) to throttle requests to Ahoy endpoints. Turn this off with:
622
+ ### Use Different Models
549
623
 
550
624
  ```ruby
551
- Ahoy.throttle = false
552
- ```
553
-
554
- The default limit is 20 requests per minute. This can be overridden with:
625
+ class Ahoy::Store < Ahoy::DatabaseStore
626
+ def visit_model
627
+ MyVisit
628
+ end
555
629
 
556
- ```ruby
557
- # limit number of requests to 100 requests every 5 minutes
558
- Ahoy.throttle_limit = 100
559
- Ahoy.throttle_period = 5.minutes
630
+ def event_model
631
+ MyEvent
632
+ end
633
+ end
560
634
  ```
561
635
 
562
- ## Native Apps
563
-
564
- ### Visits
565
-
566
- When a user launches the app, create a visit.
567
-
568
- Generate a `visit_token` and `visitor_token` as [UUIDs](http://en.wikipedia.org/wiki/Universally_unique_identifier).
569
-
570
- Send these values in the `Ahoy-Visit` and `Ahoy-Visitor` headers with all requests.
571
-
572
- Send a `POST` request to `/ahoy/visits` with:
636
+ ## Explore the Data
573
637
 
574
- - platform - `iOS`, `Android`, etc.
575
- - app_version - `1.0.0`
576
- - os_version - `7.0.6`
638
+ [Blazer](https://github.com/ankane/blazer) is a great tool for exploring your data.
577
639
 
578
- After 4 hours of inactivity, create another visit and use the updated visit id.
640
+ With ActiveRecord, you can do:
579
641
 
580
- ### Events
642
+ ```ruby
643
+ Ahoy::Visit.group(:search_keyword).count
644
+ Ahoy::Visit.group(:country).count
645
+ Ahoy::Visit.group(:referring_domain).count
646
+ ```
581
647
 
582
- Send a `POST` request as `Content-Type: application/json` to `/ahoy/events` with:
648
+ [Chartkick](https://www.chartkick.com/) and [Groupdate](https://github.com/ankane/groupdate) make it easy to visualize the data.
583
649
 
584
- - id - `5aea7b70-182d-4070-b062-b0a09699ad5e` - UUID
585
- - name - `Viewed item`
586
- - properties - `{"item_id": 123}`
587
- - time - `2014-06-17T00:00:00-07:00` - [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)
588
- - `Ahoy-Visit` and `Ahoy-Visitor` headers
589
- - user token (depends on your authentication framework)
650
+ ```erb
651
+ <%= line_chart Ahoy::Visit.group_by_day(:started_at).count %>
652
+ ```
590
653
 
591
- Use an array to pass multiple events at once.
654
+ ### Querying Events
592
655
 
593
- ## Reference
656
+ Ahoy provides a few methods on the event model to make querying easier.
594
657
 
595
- By default, Ahoy create endpoints at `/ahoy/visits` and `/ahoy/events`. To disable, use:
658
+ To query on both name and properties, you can use:
596
659
 
597
660
  ```ruby
598
- Ahoy.mount = false
661
+ Ahoy::Event.where_event("Viewed product", product_id: 123).count
599
662
  ```
600
663
 
601
- ## Upgrading
602
-
603
- ### 1.4.0
604
-
605
- There’s nothing mandatory to do, but it’s worth noting the default store was changed from `ActiveRecordStore` to `ActiveRecordTokenStore` for new installations. `ActiveRecordStore` will continue to be supported.
606
-
607
- **Optional** Consider migrating `ahoy_events` table to have the following multi-column index as this *may* benefit
608
- query performance depending on your usage:
664
+ Or just query properties with:
609
665
 
610
666
  ```ruby
611
- add_index :ahoy_events, [:name, :time]
667
+ Ahoy::Event.where_props(product_id: 123, category: "Books").count
612
668
  ```
613
669
 
614
- ### json -> jsonb
615
-
616
- Create a migration to add a new `jsonb` column.
670
+ Group by properties with:
617
671
 
618
672
  ```ruby
619
- rename_column :ahoy_events, :properties, :properties_json
620
- add_column :ahoy_events, :properties, :jsonb
673
+ Ahoy::Event.group_prop(:product_id, :category).count
621
674
  ```
622
675
 
623
- Restart your web server immediately afterwards, as Ahoy will rescue and report errors until then.
676
+ Note: MySQL and MariaDB always return string keys (including `"null"` for `nil`) for `group_prop`.
624
677
 
625
- Sync the new column.
678
+ ### Funnels
626
679
 
627
- ```ruby
628
- Ahoy::Event.where(properties: nil).select(:id).find_in_batches do |events|
629
- Ahoy::Event.where(id: events.map(&:id)).update_all("properties = properties_json::jsonb")
630
- end
631
- ```
632
-
633
- Then create a migration to drop the old column.
680
+ It’s easy to create funnels.
634
681
 
635
682
  ```ruby
636
- remove_column :ahoy_events, :properties_json
683
+ viewed_store_ids = Ahoy::Event.where(name: "Viewed store").distinct.pluck(:user_id)
684
+ added_item_ids = Ahoy::Event.where(user_id: viewed_store_ids, name: "Added item to cart").distinct.pluck(:user_id)
685
+ viewed_checkout_ids = Ahoy::Event.where(user_id: added_item_ids, name: "Viewed checkout").distinct.pluck(:user_id)
637
686
  ```
638
687
 
639
- ### 1.0.0
688
+ The same approach also works with visitor tokens.
689
+
690
+ ### Rollups
640
691
 
641
- Add the following code to the end of `config/intializers/ahoy.rb`.
692
+ Improve query performance by pre-aggregating data with [Rollup](https://github.com/ankane/rollup).
642
693
 
643
694
  ```ruby
644
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
645
- uses_deprecated_subscribers
646
- end
695
+ Ahoy::Event.where(name: "Viewed store").rollup("Store views")
647
696
  ```
648
697
 
649
- If you use `Ahoy::Event` to track events, copy it into your project.
698
+ This is only needed if you have a lot of data.
650
699
 
651
- ```ruby
652
- module Ahoy
653
- class Event < ActiveRecord::Base
654
- self.table_name = "ahoy_events"
700
+ ### Forecasting
655
701
 
656
- belongs_to :visit
657
- belongs_to :user, polymorphic: true
702
+ To forecast future visits and events, check out [Prophet](https://github.com/ankane/prophet).
658
703
 
659
- serialize :properties, JSON
660
- end
661
- end
704
+ ```ruby
705
+ daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
706
+ Prophet.forecast(daily_visits)
662
707
  ```
663
708
 
664
- That’s it! To fix deprecations, keep reading.
709
+ ### Anomaly Detection
665
710
 
666
- #### Visits
667
-
668
- Remove `ahoy_visit` from your visit model and replace it with:
711
+ To detect anomalies in visits and events, check out [AnomalyDetection.rb](https://github.com/ankane/AnomalyDetection.rb).
669
712
 
670
713
  ```ruby
671
- class Visit < ActiveRecord::Base
672
- belongs_to :user, polymorphic: true
673
- end
714
+ daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
715
+ AnomalyDetection.detect(daily_visits, period: 7)
674
716
  ```
675
717
 
676
- #### Subscribers
677
-
678
- Remove `uses_deprecated_subscribers` from `Ahoy::Store`.
718
+ ### Breakout Detection
679
719
 
680
- If you have a custom subscriber, copy the `track` method to `track_event` in `Ahoy::Store`.
720
+ To detect breakouts in visits and events, check out [Breakout](https://github.com/ankane/breakout).
681
721
 
682
722
  ```ruby
683
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
684
- def track_event(name, properties, options)
685
- # code copied from the track method in your subscriber
686
- end
687
- end
723
+ daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
724
+ Breakout.detect(daily_visits)
688
725
  ```
689
726
 
690
- #### Authentication
727
+ ### Recommendations
691
728
 
692
- Ahoy no longer tracks the `$authenticate` event automatically.
729
+ To make recommendations based on events, check out [Disco](https://github.com/ankane/disco#ahoy).
693
730
 
694
- To restore this behavior, use:
731
+ ## Tutorials
695
732
 
696
- ```ruby
697
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
698
- def authenticate(user)
699
- super
700
- ahoy.track "$authenticate"
701
- end
702
- end
703
- ```
733
+ - [Tracking Metrics with Ahoy and Blazer](https://gorails.com/episodes/internal-metrics-with-ahoy-and-blazer)
704
734
 
705
- #### Global Options
735
+ ## API Spec
706
736
 
707
- Replace the `Ahoy.user_method` with `user` method, and replace `Ahoy.track_bots` and `Ahoy.exclude_method` with `exclude?` method.
737
+ ### Visits
708
738
 
709
- Skip this step if you do not use these options.
739
+ 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.
710
740
 
711
- ```ruby
712
- class Ahoy::Store < Ahoy::Stores::ActiveRecordTokenStore
713
- def user
714
- # logic from Ahoy.user_method goes here
715
- controller.true_user
716
- end
741
+ Send a `POST` request to `/ahoy/visits` with `Content-Type: application/json` and a body like:
717
742
 
718
- def exclude?
719
- # logic from Ahoy.track_bots and Ahoy.exclude_method goes here
720
- bot? || request.ip == "192.168.1.1"
721
- end
722
- end
743
+ ```json
744
+ {
745
+ "visit_token": "<visit-token>",
746
+ "visitor_token": "<visitor-token>",
747
+ "platform": "iOS",
748
+ "app_version": "1.0.0",
749
+ "os_version": "11.2.6"
750
+ }
723
751
  ```
724
752
 
725
- You made it! Now, take advantage of Ahoy’s awesome new features, like easy customization and exception reporting.
753
+ After 4 hours of inactivity, create another visit (use the same visitor token).
726
754
 
727
- ### 0.3.0
755
+ ### Events
728
756
 
729
- Starting with `0.3.0`, visit and visitor tokens are now UUIDs.
757
+ Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` and a body like:
758
+
759
+ ```json
760
+ {
761
+ "visit_token": "<visit-token>",
762
+ "visitor_token": "<visitor-token>",
763
+ "events": [
764
+ {
765
+ "id": "<optional-random-id>",
766
+ "name": "Viewed item",
767
+ "properties": {
768
+ "item_id": 123
769
+ },
770
+ "time": "2018-01-01T00:00:00-07:00"
771
+ }
772
+ ]
773
+ }
774
+ ```
730
775
 
731
- ### 0.1.6
776
+ ## Upgrading
732
777
 
733
- In `0.1.6`, a big improvement was made to `browser` and `os`. Update existing visits with:
778
+ ### 4.0
734
779
 
735
- ```ruby
736
- Visit.find_each do |visit|
737
- visit.set_technology
738
- visit.save! if visit.changed?
739
- end
740
- ```
780
+ There are two notable changes to geocoding:
741
781
 
742
- ## TODO
782
+ 1. Geocoding is now disabled by default (this was already the case for new installations with 3.2.0+). Check out the instructions for [how to enable it](#geocoding).
743
783
 
744
- - real-time dashboard of visits and events
745
- - more events for append only stores
746
- - turn off modules
784
+ 2. The `geocoder` gem is now an optional dependency. To use geocoding, add it to your Gemfile:
747
785
 
748
- ## No Ruby?
786
+ ```ruby
787
+ gem "geocoder"
788
+ ```
749
789
 
750
- Check out [Ahoy.js](https://github.com/ankane/ahoy.js).
790
+ Also, check out the [upgrade notes](https://github.com/ankane/ahoy.js#upgrading) for Ahoy.js.
751
791
 
752
792
  ## History
753
793
 
@@ -761,3 +801,20 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
761
801
  - Fix bugs and [submit pull requests](https://github.com/ankane/ahoy/pulls)
762
802
  - Write, clarify, or fix documentation
763
803
  - Suggest or add new features
804
+
805
+ To get started with development:
806
+
807
+ ```sh
808
+ git clone https://github.com/ankane/ahoy.git
809
+ cd ahoy
810
+ bundle install
811
+ bundle exec rake test
812
+ ```
813
+
814
+ To test different adapters, use:
815
+
816
+ ```sh
817
+ ADAPTER=postgresql bundle exec rake test
818
+ ADAPTER=mysql2 bundle exec rake test
819
+ ADAPTER=mongoid bundle exec rake test
820
+ ```