ahoy_matey 3.0.5 → 4.0.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 +26 -0
- data/LICENSE.txt +1 -1
- data/README.md +104 -92
- data/app/controllers/ahoy/base_controller.rb +12 -4
- data/app/jobs/ahoy/geocode_v2_job.rb +3 -0
- data/lib/ahoy.rb +12 -8
- data/lib/ahoy/base_store.rb +5 -1
- data/lib/ahoy/controller.rb +3 -3
- data/lib/ahoy/database_store.rb +1 -1
- data/lib/ahoy/model.rb +1 -1
- data/lib/ahoy/query_methods.rb +16 -40
- data/lib/ahoy/tracker.rb +6 -13
- data/lib/ahoy/version.rb +1 -1
- data/lib/generators/ahoy/activerecord_generator.rb +17 -4
- data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +1 -1
- data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +5 -0
- data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +5 -0
- data/vendor/assets/javascripts/ahoy.js +50 -35
- metadata +11 -179
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fb8aecda5242614f518d06dbdc9449305f0ed30677d667e01eb1edefc627bc7
|
4
|
+
data.tar.gz: 8f52fbe0bab0a0ac1c5722b06f342add3d31c0c441230b77f03ae566e07f746b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '059927d7fe2344882860ae81ff23e142a09ed955da818da8d3e679b03173b39e64ffc611e4e682a912901678cd9b790b461805f788c423cd2b4331c2a340c226'
|
7
|
+
data.tar.gz: f2d53c35500ee4d30485cfbf6c5d444cbf03c50cec4762783a972411343e19b8baf53b61286cc4aa78bbbe98a69df21da701989422263780447341fdce756db0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
## 4.0.0 (2021-08-14)
|
2
|
+
|
3
|
+
- Disabled geocoding by default
|
4
|
+
- Made the `geocoder` gem an optional dependency
|
5
|
+
- Updated Ahoy.js to 0.4.0
|
6
|
+
- Updated API to return 400 status code when missing required parameters
|
7
|
+
- Dropped support for Ruby < 2.6 and Rails < 5.2
|
8
|
+
|
9
|
+
## 3.3.0 (2021-08-13)
|
10
|
+
|
11
|
+
- Added `country_code` to geocoding
|
12
|
+
- Updated Ahoy.js to 0.3.9
|
13
|
+
- Fixed install generator for MariaDB
|
14
|
+
|
15
|
+
## 3.2.0 (2021-03-01)
|
16
|
+
|
17
|
+
- Disabled geocoding by default for new installations
|
18
|
+
- Fixed deprecation warning with Active Record 6.1
|
19
|
+
|
20
|
+
## 3.1.0 (2020-12-04)
|
21
|
+
|
22
|
+
- Added `instance` method
|
23
|
+
- Added `request` argument to `user_method`
|
24
|
+
- Updated Ahoy.js to 0.3.8
|
25
|
+
- Removed `exclude_method` call when geocoding
|
26
|
+
|
1
27
|
## 3.0.5 (2020-09-09)
|
2
28
|
|
3
29
|
- Added `group_prop` method
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -2,13 +2,15 @@
|
|
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
|
+
|
7
|
+
**Ahoy 4.0 was recently released** - see [how to upgrade](#upgrading)
|
6
8
|
|
7
9
|
:postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for emails and [Field Test](https://github.com/ankane/field_test) for A/B testing
|
8
10
|
|
9
11
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
10
12
|
|
11
|
-
[![Build Status](https://
|
13
|
+
[![Build Status](https://github.com/ankane/ahoy/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy/actions)
|
12
14
|
|
13
15
|
## Installation
|
14
16
|
|
@@ -70,6 +72,14 @@ Track an event with:
|
|
70
72
|
ahoy.track("My second event", {language: "JavaScript"});
|
71
73
|
```
|
72
74
|
|
75
|
+
### Native Apps
|
76
|
+
|
77
|
+
Check out [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
|
78
|
+
|
79
|
+
### Geocoding Setup
|
80
|
+
|
81
|
+
To enable geocoding, see the [Geocoding section](#geocoding).
|
82
|
+
|
73
83
|
### GDPR Compliance
|
74
84
|
|
75
85
|
Ahoy provides a number of options to help with GDPR compliance. See the [GDPR section](#gdpr-compliance-1) for more info.
|
@@ -135,17 +145,11 @@ end
|
|
135
145
|
ahoy.track("Viewed book", {title: "The World is Flat"});
|
136
146
|
```
|
137
147
|
|
138
|
-
Track events automatically with:
|
139
|
-
|
140
|
-
```javascript
|
141
|
-
ahoy.trackAll();
|
142
|
-
```
|
143
|
-
|
144
148
|
See [Ahoy.js](https://github.com/ankane/ahoy.js) for a complete list of features.
|
145
149
|
|
146
150
|
#### Native Apps
|
147
151
|
|
148
|
-
|
152
|
+
See the docs for [Ahoy iOS](https://github.com/namolnad/ahoy-ios) and [Ahoy Android](https://github.com/instacart/ahoy-android).
|
149
153
|
|
150
154
|
#### AMP
|
151
155
|
|
@@ -181,7 +185,7 @@ Order.joins(:ahoy_visit).group("device_type").count
|
|
181
185
|
Here’s what the migration to add the `ahoy_visit_id` column should look like:
|
182
186
|
|
183
187
|
```ruby
|
184
|
-
class AddVisitIdToOrders < ActiveRecord::Migration[6.
|
188
|
+
class AddVisitIdToOrders < ActiveRecord::Migration[6.1]
|
185
189
|
def change
|
186
190
|
add_column :orders, :ahoy_visit_id, :bigint
|
187
191
|
end
|
@@ -196,7 +200,7 @@ visitable :sign_up_visit
|
|
196
200
|
|
197
201
|
### Users
|
198
202
|
|
199
|
-
Ahoy automatically attaches the `current_user` to the visit. With [Devise](https://github.com/plataformatec/devise), it attaches the user even if
|
203
|
+
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.
|
200
204
|
|
201
205
|
With other authentication frameworks, add this to the end of your sign in method:
|
202
206
|
|
@@ -306,71 +310,118 @@ Set other [cookie options](https://api.rubyonrails.org/classes/ActionDispatch/Co
|
|
306
310
|
Ahoy.cookie_options = {same_site: :lax}
|
307
311
|
```
|
308
312
|
|
309
|
-
|
313
|
+
You can also [disable cookies](#anonymity-sets--cookies)
|
310
314
|
|
311
|
-
|
315
|
+
### Token Generation
|
316
|
+
|
317
|
+
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).
|
312
318
|
|
313
319
|
```ruby
|
314
|
-
Ahoy.
|
320
|
+
Ahoy.token_generator = -> { Druuid.gen }
|
315
321
|
```
|
316
322
|
|
317
|
-
|
323
|
+
### Throttling
|
324
|
+
|
325
|
+
You can use [Rack::Attack](https://github.com/kickstarter/rack-attack) to throttle requests to the API.
|
318
326
|
|
319
327
|
```ruby
|
320
|
-
|
328
|
+
class Rack::Attack
|
329
|
+
throttle("ahoy/ip", limit: 20, period: 1.minute) do |req|
|
330
|
+
if req.path.start_with?("/ahoy/")
|
331
|
+
req.ip
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
321
335
|
```
|
322
336
|
|
323
|
-
|
337
|
+
### Exceptions
|
324
338
|
|
325
|
-
|
339
|
+
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:
|
326
340
|
|
327
|
-
|
341
|
+
```ruby
|
342
|
+
Safely.report_exception_method = ->(e) { Rollbar.error(e) }
|
343
|
+
```
|
344
|
+
|
345
|
+
## Geocoding
|
346
|
+
|
347
|
+
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).
|
348
|
+
|
349
|
+
To enable geocoding, add this line to your application’s Gemfile:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
gem 'geocoder'
|
353
|
+
```
|
354
|
+
|
355
|
+
And update `config/initializers/ahoy.rb`:
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
Ahoy.geocode = true
|
359
|
+
```
|
360
|
+
|
361
|
+
Geocoding is performed in a background job so it doesn’t slow down web requests. The default job queue is `:ahoy`. Change this with:
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
Ahoy.job_queue = :low_priority
|
365
|
+
```
|
366
|
+
|
367
|
+
### Local Geocoding
|
368
|
+
|
369
|
+
For privacy and performance, we recommend geocoding locally. Add this line to your application’s Gemfile:
|
328
370
|
|
329
371
|
```ruby
|
330
372
|
gem 'maxminddb'
|
331
373
|
```
|
332
374
|
|
333
|
-
|
375
|
+
For city-level geocoding, download the [GeoLite2 City database](https://dev.maxmind.com/geoip/geoip2/geolite2/) and create `config/initializers/geocoder.rb` with:
|
334
376
|
|
335
377
|
```ruby
|
336
378
|
Geocoder.configure(
|
337
379
|
ip_lookup: :geoip2,
|
338
380
|
geoip2: {
|
339
|
-
file:
|
381
|
+
file: "path/to/GeoLite2-City.mmdb"
|
340
382
|
}
|
341
383
|
)
|
342
384
|
```
|
343
385
|
|
344
|
-
|
386
|
+
For country-level geocoding, install the `geoip-database` package. It’s preinstalled on Heroku. For Ubuntu, use:
|
345
387
|
|
346
|
-
|
388
|
+
```sh
|
389
|
+
sudo apt-get install geoip-database
|
390
|
+
```
|
347
391
|
|
348
|
-
|
392
|
+
And create `config/initializers/geocoder.rb` with:
|
349
393
|
|
350
394
|
```ruby
|
351
|
-
|
395
|
+
Geocoder.configure(
|
396
|
+
ip_lookup: :maxmind_local,
|
397
|
+
maxmind_local: {
|
398
|
+
file: "/usr/share/GeoIP/GeoIP.dat",
|
399
|
+
package: :country
|
400
|
+
}
|
401
|
+
)
|
352
402
|
```
|
353
403
|
|
354
|
-
###
|
404
|
+
### Load Balancer Geocoding
|
355
405
|
|
356
|
-
|
406
|
+
Some load balancers can add geocoding information to request headers.
|
357
407
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
if req.path.start_with?("/ahoy/")
|
362
|
-
req.ip
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
|
-
```
|
408
|
+
- [nginx](https://nginx.org/en/docs/http/ngx_http_geoip_module.html)
|
409
|
+
- [Google Cloud](https://cloud.google.com/load-balancing/docs/custom-headers)
|
410
|
+
- [Cloudflare](https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation)
|
367
411
|
|
368
|
-
|
369
|
-
|
370
|
-
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:
|
412
|
+
Update `config/initializers/ahoy.rb` with:
|
371
413
|
|
372
414
|
```ruby
|
373
|
-
|
415
|
+
Ahoy.geocode = false
|
416
|
+
|
417
|
+
class Ahoy::Store < Ahoy::DatabaseStore
|
418
|
+
def track_visit(data)
|
419
|
+
data[:country] = request.headers["<country-header>"]
|
420
|
+
data[:region] = request.headers["<region-header>"]
|
421
|
+
data[:city] = request.headers["<city-header>"]
|
422
|
+
super(data)
|
423
|
+
end
|
424
|
+
end
|
374
425
|
```
|
375
426
|
|
376
427
|
## GDPR Compliance
|
@@ -431,7 +482,11 @@ Ahoy can switch from cookies to [anonymity sets](https://privacypatterns.org/pat
|
|
431
482
|
Ahoy.cookies = false
|
432
483
|
```
|
433
484
|
|
434
|
-
Previously set cookies are automatically deleted.
|
485
|
+
Previously set cookies are automatically deleted. If you use JavaScript tracking, also set:
|
486
|
+
|
487
|
+
```javascript
|
488
|
+
ahoy.configure({cookies: false});
|
489
|
+
```
|
435
490
|
|
436
491
|
## Data Retention
|
437
492
|
|
@@ -690,62 +745,19 @@ Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` an
|
|
690
745
|
|
691
746
|
## Upgrading
|
692
747
|
|
693
|
-
###
|
694
|
-
|
695
|
-
If you installed Ahoy before 2.1 and want to keep legacy user agent parsing and bot detection, add to your Gemfile:
|
696
|
-
|
697
|
-
```ruby
|
698
|
-
gem "browser", "~> 2.0"
|
699
|
-
gem "user_agent_parser"
|
700
|
-
```
|
701
|
-
|
702
|
-
And add to `config/initializers/ahoy.rb`:
|
703
|
-
|
704
|
-
```ruby
|
705
|
-
Ahoy.user_agent_parser = :legacy
|
706
|
-
```
|
707
|
-
|
708
|
-
### 2.2
|
709
|
-
|
710
|
-
Ahoy now ships with better bot detection if you use Device Detector. This should be more accurate but can significantly reduce the number of visits recorded. For existing installs, it’s opt-in to start. To use it, add to `config/initializers/ahoy.rb`:
|
748
|
+
### 4.0
|
711
749
|
|
712
|
-
|
713
|
-
Ahoy.bot_detection_version = 2
|
714
|
-
```
|
715
|
-
|
716
|
-
### 2.1
|
717
|
-
|
718
|
-
Ahoy recommends [Device Detector](https://github.com/podigee/device_detector) for user agent parsing and makes it the default for new installations. To switch, add to `config/initializers/ahoy.rb`:
|
719
|
-
|
720
|
-
```ruby
|
721
|
-
Ahoy.user_agent_parser = :device_detector
|
722
|
-
```
|
750
|
+
There are two notable changes to geocoding:
|
723
751
|
|
724
|
-
|
752
|
+
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).
|
725
753
|
|
726
|
-
|
727
|
-
Ahoy::Visit.find_each do |visit|
|
728
|
-
client = DeviceDetector.new(visit.user_agent)
|
729
|
-
device_type =
|
730
|
-
case client.device_type
|
731
|
-
when "smartphone"
|
732
|
-
"Mobile"
|
733
|
-
when "tv"
|
734
|
-
"TV"
|
735
|
-
else
|
736
|
-
client.device_type.try(:titleize)
|
737
|
-
end
|
738
|
-
|
739
|
-
visit.browser = client.name
|
740
|
-
visit.os = client.os_name
|
741
|
-
visit.device_type = device_type
|
742
|
-
visit.save(validate: false) if visit.changed?
|
743
|
-
end
|
744
|
-
```
|
754
|
+
2. The `geocoder` gem is now an optional dependency. To use geocoding, add it to your Gemfile:
|
745
755
|
|
746
|
-
|
756
|
+
```ruby
|
757
|
+
gem 'geocoder'
|
758
|
+
```
|
747
759
|
|
748
|
-
|
760
|
+
Also, check out the [upgrade notes](https://github.com/ankane/ahoy.js#upgrading) for Ahoy.js.
|
749
761
|
|
750
762
|
## History
|
751
763
|
|
@@ -5,19 +5,27 @@ module Ahoy
|
|
5
5
|
skip_after_action(*filters, raise: false)
|
6
6
|
skip_around_action(*filters, raise: false)
|
7
7
|
|
8
|
-
before_action :verify_request_size
|
9
|
-
before_action :renew_cookies
|
10
|
-
|
11
8
|
if respond_to?(:protect_from_forgery)
|
12
9
|
protect_from_forgery with: :null_session, if: -> { Ahoy.protect_from_forgery }
|
13
10
|
end
|
14
11
|
|
12
|
+
before_action :verify_request_size
|
13
|
+
before_action :check_params
|
14
|
+
before_action :renew_cookies
|
15
|
+
|
15
16
|
protected
|
16
17
|
|
17
18
|
def ahoy
|
18
19
|
@ahoy ||= Ahoy::Tracker.new(controller: self, api: true)
|
19
20
|
end
|
20
21
|
|
22
|
+
def check_params
|
23
|
+
if ahoy.send(:missing_params?)
|
24
|
+
logger.info "[ahoy] Missing required parameters"
|
25
|
+
render plain: "Missing required parameters\n", status: :bad_request
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
21
29
|
# set proper ttl if cookie generated from JavaScript
|
22
30
|
# approach is not perfect, as user must reload the page
|
23
31
|
# for new cookie settings to take effect
|
@@ -28,7 +36,7 @@ module Ahoy
|
|
28
36
|
def verify_request_size
|
29
37
|
if request.content_length > Ahoy.max_content_length
|
30
38
|
logger.info "[ahoy] Payload too large"
|
31
|
-
render plain: "Payload too large\n", status:
|
39
|
+
render plain: "Payload too large\n", status: :payload_too_large
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -6,6 +6,8 @@ module Ahoy
|
|
6
6
|
location =
|
7
7
|
begin
|
8
8
|
Geocoder.search(ip).first
|
9
|
+
rescue NameError
|
10
|
+
raise "Add the geocoder gem to your Gemfile to use geocoding"
|
9
11
|
rescue => e
|
10
12
|
Ahoy.log "Geocode error: #{e.class.name}: #{e.message}"
|
11
13
|
nil
|
@@ -14,6 +16,7 @@ module Ahoy
|
|
14
16
|
if location && location.country.present?
|
15
17
|
data = {
|
16
18
|
country: location.country,
|
19
|
+
country_code: location.try(:country_code).presence,
|
17
20
|
region: location.try(:state).presence,
|
18
21
|
city: location.try(:city).presence,
|
19
22
|
postal_code: location.try(:postal_code).presence,
|
data/lib/ahoy.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
# stdlib
|
1
2
|
require "ipaddr"
|
2
3
|
|
3
4
|
# dependencies
|
4
5
|
require "active_support"
|
5
6
|
require "active_support/core_ext"
|
6
|
-
require "geocoder"
|
7
7
|
require "safely/core"
|
8
8
|
|
9
9
|
# modules
|
@@ -43,7 +43,7 @@ module Ahoy
|
|
43
43
|
self.quiet = true
|
44
44
|
|
45
45
|
mattr_accessor :geocode
|
46
|
-
self.geocode =
|
46
|
+
self.geocode = false
|
47
47
|
|
48
48
|
mattr_accessor :max_content_length
|
49
49
|
self.max_content_length = 8192
|
@@ -104,6 +104,14 @@ module Ahoy
|
|
104
104
|
addr.mask(48).to_s
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
def self.instance
|
109
|
+
Thread.current[:ahoy]
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.instance=(value)
|
113
|
+
Thread.current[:ahoy] = value
|
114
|
+
end
|
107
115
|
end
|
108
116
|
|
109
117
|
ActiveSupport.on_load(:action_controller) do
|
@@ -119,10 +127,6 @@ ActiveSupport.on_load(:action_view) do
|
|
119
127
|
end
|
120
128
|
|
121
129
|
# Mongoid
|
122
|
-
|
123
|
-
|
124
|
-
# Mongoid::Document::ClassMethods.include(Ahoy::Model)
|
125
|
-
# end
|
126
|
-
if defined?(ActiveModel)
|
127
|
-
ActiveModel::Callbacks.include(Ahoy::Model)
|
130
|
+
ActiveSupport.on_load(:mongoid) do
|
131
|
+
Mongoid::Document::ClassMethods.include(Ahoy::Model)
|
128
132
|
end
|
data/lib/ahoy/base_store.rb
CHANGED
@@ -24,7 +24,11 @@ module Ahoy
|
|
24
24
|
def user
|
25
25
|
@user ||= begin
|
26
26
|
if Ahoy.user_method.respond_to?(:call)
|
27
|
-
Ahoy.user_method.
|
27
|
+
if Ahoy.user_method.arity == 1
|
28
|
+
Ahoy.user_method.call(controller)
|
29
|
+
else
|
30
|
+
Ahoy.user_method.call(controller, request)
|
31
|
+
end
|
28
32
|
else
|
29
33
|
controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
|
30
34
|
end
|
data/lib/ahoy/controller.rb
CHANGED
@@ -39,12 +39,12 @@ module Ahoy
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def set_ahoy_request_store
|
42
|
-
previous_value =
|
42
|
+
previous_value = Ahoy.instance
|
43
43
|
begin
|
44
|
-
|
44
|
+
Ahoy.instance = ahoy
|
45
45
|
yield
|
46
46
|
ensure
|
47
|
-
|
47
|
+
Ahoy.instance = previous_value
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/ahoy/database_store.rb
CHANGED
data/lib/ahoy/model.rb
CHANGED
data/lib/ahoy/query_methods.rb
CHANGED
@@ -19,35 +19,21 @@ module Ahoy
|
|
19
19
|
when "mongoid"
|
20
20
|
relation = where(Hash[properties.map { |k, v| ["properties.#{k}", v] }])
|
21
21
|
when /mysql/
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
relation = relation.where("JSON_UNQUOTE(properties -> ?) = ?", "$.#{k}", v.as_json)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
properties.each do |k, v|
|
34
|
-
# TODO cast to json instead
|
35
|
-
relation = relation.where("properties REGEXP ?", "[{,]#{{k.to_s => v}.to_json.sub(/\A\{/, "").sub(/\}\z/, "").gsub("+", "\\\\+")}[,}]")
|
22
|
+
column = column_type == :json || connection.try(:mariadb?) ? "properties" : "CAST(properties AS JSON)"
|
23
|
+
properties.each do |k, v|
|
24
|
+
if v.nil?
|
25
|
+
v = "null"
|
26
|
+
elsif v == true
|
27
|
+
v = "true"
|
36
28
|
end
|
29
|
+
|
30
|
+
relation = relation.where("JSON_UNQUOTE(JSON_EXTRACT(#{column}, ?)) = ?", "$.#{k}", v.as_json)
|
37
31
|
end
|
38
32
|
when /postgres|postgis/
|
39
|
-
|
33
|
+
case column_type
|
34
|
+
when :jsonb
|
40
35
|
relation = relation.where("properties @> ?", properties.to_json)
|
41
|
-
|
42
|
-
properties.each do |k, v|
|
43
|
-
relation =
|
44
|
-
if v.nil?
|
45
|
-
relation.where("properties ->> ? IS NULL", k.to_s)
|
46
|
-
else
|
47
|
-
relation.where("properties ->> ? = ?", k.to_s, v.as_json.to_s)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
elsif column_type == :hstore
|
36
|
+
when :hstore
|
51
37
|
properties.each do |k, v|
|
52
38
|
relation =
|
53
39
|
if v.nil?
|
@@ -57,10 +43,7 @@ module Ahoy
|
|
57
43
|
end
|
58
44
|
end
|
59
45
|
else
|
60
|
-
properties
|
61
|
-
# TODO cast to jsonb instead
|
62
|
-
relation = relation.where("properties SIMILAR TO ?", "%[{,]#{{k.to_s => v}.to_json.sub(/\A\{/, "").sub(/\}\z/, "").gsub("+", "\\\\+")}[,}]%")
|
63
|
-
end
|
46
|
+
relation = relation.where("properties::jsonb @> ?", properties.to_json)
|
64
47
|
end
|
65
48
|
else
|
66
49
|
raise "Adapter not supported: #{adapter_name}"
|
@@ -84,17 +67,10 @@ module Ahoy
|
|
84
67
|
when "mongoid"
|
85
68
|
raise "Adapter not supported: #{adapter_name}"
|
86
69
|
when /mysql/
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
else
|
93
|
-
column = column_type == :json ? "properties" : "CAST(properties AS JSON)"
|
94
|
-
props.each do |prop|
|
95
|
-
quoted_prop = connection.quote("$.#{prop}")
|
96
|
-
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(#{column}, #{quoted_prop}))")
|
97
|
-
end
|
70
|
+
column = column_type == :json || connection.try(:mariadb?) ? "properties" : "CAST(properties AS JSON)"
|
71
|
+
props.each do |prop|
|
72
|
+
quoted_prop = connection.quote("$.#{prop}")
|
73
|
+
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(#{column}, #{quoted_prop}))")
|
98
74
|
end
|
99
75
|
when /postgres|postgis/
|
100
76
|
# convert to jsonb to fix
|
data/lib/ahoy/tracker.rb
CHANGED
@@ -19,8 +19,6 @@ module Ahoy
|
|
19
19
|
def track(name, properties = {}, options = {})
|
20
20
|
if exclude?
|
21
21
|
debug "Event excluded"
|
22
|
-
elsif missing_params?
|
23
|
-
debug "Missing required parameters"
|
24
22
|
else
|
25
23
|
data = {
|
26
24
|
visit_token: visit_token,
|
@@ -41,8 +39,6 @@ module Ahoy
|
|
41
39
|
def track_visit(defer: false, started_at: nil)
|
42
40
|
if exclude?
|
43
41
|
debug "Visit excluded"
|
44
|
-
elsif missing_params?
|
45
|
-
debug "Missing required parameters"
|
46
42
|
else
|
47
43
|
if defer
|
48
44
|
set_cookie("ahoy_track", true, nil, false)
|
@@ -67,16 +63,12 @@ module Ahoy
|
|
67
63
|
end
|
68
64
|
|
69
65
|
def geocode(data)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
data = {
|
74
|
-
visit_token: visit_token
|
75
|
-
}.merge(data).select { |_, v| v }
|
66
|
+
data = {
|
67
|
+
visit_token: visit_token
|
68
|
+
}.merge(data).select { |_, v| v }
|
76
69
|
|
77
|
-
|
78
|
-
|
79
|
-
end
|
70
|
+
@store.geocode(data)
|
71
|
+
true
|
80
72
|
rescue => e
|
81
73
|
report_exception(e)
|
82
74
|
end
|
@@ -159,6 +151,7 @@ module Ahoy
|
|
159
151
|
@options[:api]
|
160
152
|
end
|
161
153
|
|
154
|
+
# private, but used by API
|
162
155
|
def missing_params?
|
163
156
|
if Ahoy.cookies && api? && Ahoy.protect_from_forgery
|
164
157
|
!(existing_visit_token && existing_visitor_token)
|
data/lib/ahoy/version.rb
CHANGED
@@ -17,9 +17,7 @@ module Ahoy
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def properties_type
|
20
|
-
|
21
|
-
# so database connection isn't needed
|
22
|
-
case ActiveRecord::Base.connection_config[:adapter].to_s
|
20
|
+
case adapter
|
23
21
|
when /postg/i # postgres, postgis
|
24
22
|
"jsonb"
|
25
23
|
when /mysql/i
|
@@ -29,8 +27,23 @@ module Ahoy
|
|
29
27
|
end
|
30
28
|
end
|
31
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
|
+
|
35
|
+
# use connection_config instead of connection.adapter
|
36
|
+
# so database connection isn't needed
|
37
|
+
def adapter
|
38
|
+
if ActiveRecord::VERSION::STRING.to_f >= 6.1
|
39
|
+
ActiveRecord::Base.connection_db_config.adapter.to_s
|
40
|
+
else
|
41
|
+
ActiveRecord::Base.connection_config[:adapter].to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
32
45
|
def rails52?
|
33
|
-
ActiveRecord::VERSION::STRING >=
|
46
|
+
ActiveRecord::VERSION::STRING.to_f >= 5.2
|
34
47
|
end
|
35
48
|
|
36
49
|
def migration_version
|
@@ -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
|
@@ -18,3 +18,8 @@ end
|
|
18
18
|
|
19
19
|
# set to true for JavaScript tracking
|
20
20
|
Ahoy.api = false
|
21
|
+
|
22
|
+
# set to true for geocoding (and add the geocoder gem to your Gemfile)
|
23
|
+
# we recommend configuring local geocoding as well
|
24
|
+
# see https://github.com/ankane/ahoy#geocoding
|
25
|
+
Ahoy.geocode = false
|
@@ -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.
|
5
|
+
* v0.4.0
|
6
6
|
* MIT License
|
7
7
|
*/
|
8
8
|
|
@@ -12,8 +12,6 @@
|
|
12
12
|
(global = global || self, global.ahoy = factory());
|
13
13
|
}(this, (function () { 'use strict';
|
14
14
|
|
15
|
-
var n=function(n){return void 0===n},e=function(n){return Array.isArray(n)},t=function(n){return n&&"number"==typeof n.size&&"string"==typeof n.type&&"function"==typeof n.slice},s=function(o,i,r,f){return (i=i||{}).indices=!n(i.indices)&&i.indices,i.nullsAsUndefineds=!n(i.nullsAsUndefineds)&&i.nullsAsUndefineds,i.booleansAsIntegers=!n(i.booleansAsIntegers)&&i.booleansAsIntegers,r=r||new FormData,n(o)?r:(null===o?i.nullsAsUndefineds||r.append(f,""):"boolean"!=typeof o?e(o)?o.length&&o.forEach(function(n,e){s(n,i,r,f+"["+(i.indices?e:"")+"]");}):o instanceof Date?r.append(f,o.toISOString()):o!==Object(o)||function(n){return t(n)&&"string"==typeof n.name&&("object"==typeof n.lastModifiedDate||"number"==typeof n.lastModified)}(o)||t(o)?r.append(f,o):Object.keys(o).forEach(function(n){var t=o[n];if(e(t)){ for(;n.length>2&&n.lastIndexOf("[]")===n.length-2;){ n=n.substring(0,n.length-2); } }s(t,i,r,f?f+"["+n+"]":n);}):r.append(f,i.booleansAsIntegers?o?1:0:o),r)};
|
16
|
-
|
17
15
|
// https://www.quirksmode.org/js/cookies.html
|
18
16
|
|
19
17
|
var Cookies = {
|
@@ -28,7 +26,7 @@
|
|
28
26
|
if (domain) {
|
29
27
|
cookieDomain = "; domain=" + domain;
|
30
28
|
}
|
31
|
-
document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path
|
29
|
+
document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/; samesite=lax";
|
32
30
|
},
|
33
31
|
get: function (name) {
|
34
32
|
var i, c;
|
@@ -101,6 +99,16 @@
|
|
101
99
|
return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
|
102
100
|
}
|
103
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
|
+
|
104
112
|
// cookies
|
105
113
|
|
106
114
|
function setCookie(name, value, ttl) {
|
@@ -146,17 +154,23 @@
|
|
146
154
|
element.webkitMatchesSelector;
|
147
155
|
|
148
156
|
if (matches) {
|
149
|
-
|
157
|
+
if (matches.apply(element, [selector])) {
|
158
|
+
return element;
|
159
|
+
} else if (element.parentElement) {
|
160
|
+
return matchesSelector(element.parentElement, selector);
|
161
|
+
}
|
162
|
+
return null;
|
150
163
|
} else {
|
151
164
|
log("Unable to match");
|
152
|
-
return
|
165
|
+
return null;
|
153
166
|
}
|
154
167
|
}
|
155
168
|
|
156
169
|
function onEvent(eventName, selector, callback) {
|
157
170
|
document.addEventListener(eventName, function (e) {
|
158
|
-
|
159
|
-
|
171
|
+
var matchedElement = matchesSelector(e.target, selector);
|
172
|
+
if (matchedElement) {
|
173
|
+
callback.call(matchedElement, e);
|
160
174
|
}
|
161
175
|
});
|
162
176
|
}
|
@@ -275,7 +289,7 @@
|
|
275
289
|
// stringify so we keep the type
|
276
290
|
data.events_json = JSON.stringify(data.events);
|
277
291
|
delete data.events;
|
278
|
-
window.navigator.sendBeacon(eventsUrl(),
|
292
|
+
window.navigator.sendBeacon(eventsUrl(), serialize(data));
|
279
293
|
});
|
280
294
|
}
|
281
295
|
|
@@ -298,14 +312,13 @@
|
|
298
312
|
return obj;
|
299
313
|
}
|
300
314
|
|
301
|
-
function eventProperties(
|
302
|
-
var target = e.target;
|
315
|
+
function eventProperties() {
|
303
316
|
return cleanObject({
|
304
|
-
tag:
|
305
|
-
id: presence(
|
306
|
-
"class": presence(
|
317
|
+
tag: this.tagName.toLowerCase(),
|
318
|
+
id: presence(this.id),
|
319
|
+
"class": presence(this.className),
|
307
320
|
page: page(),
|
308
|
-
section: getClosestSection(
|
321
|
+
section: getClosestSection(this)
|
309
322
|
});
|
310
323
|
}
|
311
324
|
|
@@ -463,37 +476,39 @@
|
|
463
476
|
ahoy.track("$view", properties);
|
464
477
|
};
|
465
478
|
|
466
|
-
ahoy.trackClicks = function () {
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
properties
|
479
|
+
ahoy.trackClicks = function (selector) {
|
480
|
+
if (selector === undefined) {
|
481
|
+
throw new Error("Missing selector");
|
482
|
+
}
|
483
|
+
onEvent("click", selector, function (e) {
|
484
|
+
var properties = eventProperties.call(this, e);
|
485
|
+
properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
|
486
|
+
properties.href = this.href;
|
472
487
|
ahoy.track("$click", properties);
|
473
488
|
});
|
474
489
|
};
|
475
490
|
|
476
|
-
ahoy.trackSubmits = function () {
|
477
|
-
|
478
|
-
|
491
|
+
ahoy.trackSubmits = function (selector) {
|
492
|
+
if (selector === undefined) {
|
493
|
+
throw new Error("Missing selector");
|
494
|
+
}
|
495
|
+
onEvent("submit", selector, function (e) {
|
496
|
+
var properties = eventProperties.call(this, e);
|
479
497
|
ahoy.track("$submit", properties);
|
480
498
|
});
|
481
499
|
};
|
482
500
|
|
483
|
-
ahoy.trackChanges = function () {
|
484
|
-
|
485
|
-
|
501
|
+
ahoy.trackChanges = function (selector) {
|
502
|
+
log("trackChanges is deprecated and will be removed in 0.5.0");
|
503
|
+
if (selector === undefined) {
|
504
|
+
throw new Error("Missing selector");
|
505
|
+
}
|
506
|
+
onEvent("change", selector, function (e) {
|
507
|
+
var properties = eventProperties.call(this, e);
|
486
508
|
ahoy.track("$change", properties);
|
487
509
|
});
|
488
510
|
};
|
489
511
|
|
490
|
-
ahoy.trackAll = function() {
|
491
|
-
ahoy.trackView();
|
492
|
-
ahoy.trackClicks();
|
493
|
-
ahoy.trackSubmits();
|
494
|
-
ahoy.trackChanges();
|
495
|
-
};
|
496
|
-
|
497
512
|
// push events from queue
|
498
513
|
try {
|
499
514
|
eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ahoy_matey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,28 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5'
|
19
|
+
version: '5.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: geocoder
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.4.5
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 1.4.5
|
26
|
+
version: '5.2'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: safely_block
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,162 +52,8 @@ dependencies:
|
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '0'
|
69
|
-
|
70
|
-
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rake
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: minitest
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: combustion
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: rails
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: sqlite3
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: pg
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: mysql2
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: mongoid
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - ">="
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0'
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - ">="
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: '0'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: browser
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - "~>"
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '2.0'
|
202
|
-
type: :development
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - "~>"
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '2.0'
|
209
|
-
- !ruby/object:Gem::Dependency
|
210
|
-
name: user_agent_parser
|
211
|
-
requirement: !ruby/object:Gem::Requirement
|
212
|
-
requirements:
|
213
|
-
- - ">="
|
214
|
-
- !ruby/object:Gem::Version
|
215
|
-
version: '0'
|
216
|
-
type: :development
|
217
|
-
prerelease: false
|
218
|
-
version_requirements: !ruby/object:Gem::Requirement
|
219
|
-
requirements:
|
220
|
-
- - ">="
|
221
|
-
- !ruby/object:Gem::Version
|
222
|
-
version: '0'
|
223
|
-
description:
|
224
|
-
email: andrew@chartkick.com
|
55
|
+
description:
|
56
|
+
email: andrew@ankane.org
|
225
57
|
executables: []
|
226
58
|
extensions: []
|
227
59
|
extra_rdoc_files: []
|
@@ -266,7 +98,7 @@ homepage: https://github.com/ankane/ahoy
|
|
266
98
|
licenses:
|
267
99
|
- MIT
|
268
100
|
metadata: {}
|
269
|
-
post_install_message:
|
101
|
+
post_install_message:
|
270
102
|
rdoc_options: []
|
271
103
|
require_paths:
|
272
104
|
- lib
|
@@ -274,15 +106,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
274
106
|
requirements:
|
275
107
|
- - ">="
|
276
108
|
- !ruby/object:Gem::Version
|
277
|
-
version: '2.
|
109
|
+
version: '2.6'
|
278
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
279
111
|
requirements:
|
280
112
|
- - ">="
|
281
113
|
- !ruby/object:Gem::Version
|
282
114
|
version: '0'
|
283
115
|
requirements: []
|
284
|
-
rubygems_version: 3.
|
285
|
-
signing_key:
|
116
|
+
rubygems_version: 3.2.22
|
117
|
+
signing_key:
|
286
118
|
specification_version: 4
|
287
119
|
summary: Simple, powerful, first-party analytics for Rails
|
288
120
|
test_files: []
|