ahoy_matey 3.0.0 → 3.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -43
- data/LICENSE.txt +1 -1
- data/README.md +89 -7
- data/lib/ahoy.rb +8 -0
- data/lib/ahoy/base_store.rb +1 -1
- data/lib/ahoy/database_store.rb +1 -1
- data/lib/ahoy/query_methods.rb +45 -0
- data/lib/ahoy/tracker.rb +15 -11
- data/lib/ahoy/version.rb +1 -1
- data/lib/generators/ahoy/activerecord_generator.rb +2 -16
- data/lib/generators/ahoy/base_generator.rb +1 -1
- data/lib/generators/ahoy/install_generator.rb +1 -1
- data/lib/generators/ahoy/mongoid_generator.rb +1 -1
- data/lib/generators/ahoy/templates/active_record_migration.rb.tt +3 -3
- data/vendor/assets/javascripts/ahoy.js +21 -112
- metadata +61 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17572400fdaab440040c753cf23084f2a47868e5c1ed12229e85fd1904e9c937
|
4
|
+
data.tar.gz: cc917af046906e308830fa1ffa9543db1bc1fe0f79488ccaddef090e6366433d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 325caa06934a20bf7a58f573fff7359b7058cf63e3e7576c48faf1bd5d08ac2564810a28e5a775c3ac324170754db4de35d46f43c0b421f8c4977760af19d153
|
7
|
+
data.tar.gz: e7af674522e09e3fb472b52546f30d10b743e2b2814efd97162d05cb1d4f9b13615dce211cd83e291da16e370a86cf7d8d384b16ad60d8af07c8ced8fbd17f59
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,28 @@
|
|
1
|
-
## 3.0.
|
1
|
+
## 3.0.5 (2020-09-09)
|
2
|
+
|
3
|
+
- Added `group_prop` method
|
4
|
+
- Use `datetime` type in migration
|
5
|
+
|
6
|
+
## 3.0.4 (2020-06-07)
|
7
|
+
|
8
|
+
- Updated Ahoy.js to 0.3.6
|
9
|
+
|
10
|
+
## 3.0.3 (2020-04-17)
|
11
|
+
|
12
|
+
- Updated Ahoy.js to 0.3.5
|
13
|
+
|
14
|
+
## 3.0.2 (2020-04-03)
|
15
|
+
|
16
|
+
- Added `cookie_options`
|
17
|
+
|
18
|
+
## 3.0.1 (2019-09-21)
|
19
|
+
|
20
|
+
- Made `Ahoy::Tracker` work outside of requests
|
21
|
+
- Fixed storage of `false` values with customized store
|
22
|
+
- Fixed error with `user_method` and `Rails::InfoController`
|
23
|
+
- Gracefully handle `ActionDispatch::RemoteIp::IpSpoofAttackError`
|
24
|
+
|
25
|
+
## 3.0.0 (2019-05-29)
|
2
26
|
|
3
27
|
- Made Device Detector the default user agent parser
|
4
28
|
- Made v2 the default bot detection version
|
@@ -6,18 +30,18 @@
|
|
6
30
|
- Removed search keyword detection (most search engines today prevent this)
|
7
31
|
- Removed support for Rails < 5
|
8
32
|
|
9
|
-
## 2.2.1
|
33
|
+
## 2.2.1 (2019-05-26)
|
10
34
|
|
11
35
|
- Updated Ahoy.js to 0.3.4
|
12
36
|
- Fixed v2 bot detection
|
13
37
|
- Added latitude and longitude to installation
|
14
38
|
|
15
|
-
## 2.2.0
|
39
|
+
## 2.2.0 (2019-01-04)
|
16
40
|
|
17
41
|
- Added `amp_event` helper
|
18
42
|
- Improved bot detection for Device Detector
|
19
43
|
|
20
|
-
## 2.1.0
|
44
|
+
## 2.1.0 (2018-05-18)
|
21
45
|
|
22
46
|
- Added option for IP masking
|
23
47
|
- Added option to use anonymity sets instead of cookies
|
@@ -25,19 +49,19 @@
|
|
25
49
|
- Fixed `visitable` for Rails 4.2
|
26
50
|
- Removed `search_keyword` from new installs
|
27
51
|
|
28
|
-
## 2.0.2
|
52
|
+
## 2.0.2 (2018-03-14)
|
29
53
|
|
30
54
|
- Fixed error on duplicate records
|
31
55
|
- Fixed message when visit not found for geocoding
|
32
56
|
- Better compatibility with GeoLite2
|
33
57
|
- Better browser compatibility for Ahoy.js
|
34
58
|
|
35
|
-
## 2.0.1
|
59
|
+
## 2.0.1 (2018-02-26)
|
36
60
|
|
37
61
|
- Added `Ahoy.server_side_visits = :when_needed` to automatically create visits server-side when needed for events and `visitable`
|
38
62
|
- Better handling of visit duration and expiration in JavaScript
|
39
63
|
|
40
|
-
## 2.0.0
|
64
|
+
## 2.0.0 (2018-02-25)
|
41
65
|
|
42
66
|
- Removed dependency on jQuery
|
43
67
|
- Use `navigator.sendBeacon` by default in supported browsers
|
@@ -59,58 +83,58 @@ Breaking changes
|
|
59
83
|
- Removed most built-in stores
|
60
84
|
- Removed support for Rails < 4.2
|
61
85
|
|
62
|
-
## 1.6.1
|
86
|
+
## 1.6.1 (2018-02-02)
|
63
87
|
|
64
88
|
- Added `gin` index on properties for events
|
65
89
|
- Fixed `visitable` options when name not provided
|
66
90
|
|
67
|
-
## 1.6.0
|
91
|
+
## 1.6.0 (2017-05-01)
|
68
92
|
|
69
93
|
- Added support for Rails 5.1
|
70
94
|
|
71
|
-
## 1.5.5
|
95
|
+
## 1.5.5 (2017-03-23)
|
72
96
|
|
73
97
|
- Added support for Rails API
|
74
98
|
- Added NATS and NSQ stores
|
75
99
|
|
76
|
-
## 1.5.4
|
100
|
+
## 1.5.4 (2017-01-22)
|
77
101
|
|
78
102
|
- Fixed issue with duplicate events
|
79
103
|
- Added support for PostGIS for `where_properties`
|
80
104
|
|
81
|
-
## 1.5.3
|
105
|
+
## 1.5.3 (2016-10-31)
|
82
106
|
|
83
107
|
- Fixed error with Rails 5 and Mongoid 6
|
84
108
|
- Fixed regression with server not generating visit and visitor tokens
|
85
109
|
- Accept UTM parameters as request parameters (for native apps)
|
86
110
|
|
87
|
-
## 1.5.2
|
111
|
+
## 1.5.2 (2016-08-26)
|
88
112
|
|
89
113
|
- Better support for Rails 5
|
90
114
|
|
91
|
-
## 1.5.1
|
115
|
+
## 1.5.1 (2016-08-19)
|
92
116
|
|
93
117
|
- Restored throttling after removing side effects
|
94
118
|
|
95
|
-
## 1.5.0
|
119
|
+
## 1.5.0 (2016-08-19)
|
96
120
|
|
97
121
|
- Removed throttling due to unintended side effects with its implementation
|
98
122
|
- Ensure basic token requirements
|
99
123
|
- Fixed visit recreation on cookie expiration
|
100
124
|
- Fixed issue where `/ahoy/visits` is called indefinitely when `Ahoy.cookie_domain = :all`
|
101
125
|
|
102
|
-
## 1.4.2
|
126
|
+
## 1.4.2 (2016-06-21)
|
103
127
|
|
104
128
|
- Fixed issues with `where_properties`
|
105
129
|
|
106
|
-
## 1.4.1
|
130
|
+
## 1.4.1 (2016-06-20)
|
107
131
|
|
108
132
|
- Added `where_properties` method
|
109
133
|
- Added Kafka store
|
110
134
|
- Added `mount` option
|
111
135
|
- Use less intrusive version of `safely`
|
112
136
|
|
113
|
-
## 1.4.0
|
137
|
+
## 1.4.0 (2016-03-23)
|
114
138
|
|
115
139
|
- Use `ActiveRecordTokenStore` by default (integer instead of uuid for id)
|
116
140
|
- Detect database for `rails g ahoy:stores:active_record` for easier installation
|
@@ -118,55 +142,55 @@ Breaking changes
|
|
118
142
|
- Fixed issue with log silencer
|
119
143
|
- Use multi-column indexes on `ahoy_events` table creation
|
120
144
|
|
121
|
-
## 1.3.1
|
145
|
+
## 1.3.1 (2016-03-22)
|
122
146
|
|
123
147
|
- Raise errors in test environment
|
124
148
|
|
125
|
-
## 1.3.0
|
149
|
+
## 1.3.0 (2016-03-06)
|
126
150
|
|
127
151
|
- Added throttling
|
128
152
|
- Added `max_content_length` and `max_events_per_request`
|
129
153
|
|
130
|
-
## 1.2.2
|
154
|
+
## 1.2.2 (2016-03-05)
|
131
155
|
|
132
156
|
- Fixed issue with latest version of `browser` gem
|
133
157
|
- Added support for RabbitMQ
|
134
158
|
- Added support for Amazon Kinesis Firehose
|
135
159
|
- Fixed deprecation warnings in Rails 5
|
136
160
|
|
137
|
-
## 1.2.1
|
161
|
+
## 1.2.1 (2015-08-14)
|
138
162
|
|
139
163
|
- Fixed `SystemStackError: stack level too deep` when used with `activerecord-session_store`
|
140
164
|
|
141
|
-
## 1.2.0
|
165
|
+
## 1.2.0 (2015-06-07)
|
142
166
|
|
143
167
|
- Added support for PostgreSQL `jsonb` column type
|
144
168
|
- Added Fluentd store
|
145
169
|
- Added latitude, longitude, and postal_code to visits
|
146
170
|
- Log exclusions
|
147
171
|
|
148
|
-
## 1.1.1
|
172
|
+
## 1.1.1 (2015-01-05)
|
149
173
|
|
150
174
|
- Better support for Authlogic
|
151
175
|
- Added `screen_height` and `screen_width`
|
152
176
|
|
153
|
-
## 1.1.0
|
177
|
+
## 1.1.0 (2014-11-02)
|
154
178
|
|
155
179
|
- Added `geocode` option
|
156
180
|
- Report errors to service by default
|
157
181
|
- Fixed association mismatch
|
158
182
|
|
159
|
-
## 1.0.2
|
183
|
+
## 1.0.2 (2014-07-10)
|
160
184
|
|
161
185
|
- Fixed BSON for Mongoid 3
|
162
186
|
- Fixed Doorkeeper integration
|
163
187
|
- Fixed user tracking in overridden authenticate method
|
164
188
|
|
165
|
-
## 1.0.1
|
189
|
+
## 1.0.1 (2014-06-27)
|
166
190
|
|
167
191
|
- Fixed `visitable` outside of requests
|
168
192
|
|
169
|
-
## 1.0.0
|
193
|
+
## 1.0.0 (2014-06-18)
|
170
194
|
|
171
195
|
- Added support for any data store, and Mongoid out of the box
|
172
196
|
- Added `track_visits_immediately` option
|
@@ -174,17 +198,17 @@ Breaking changes
|
|
174
198
|
- Visits expire after inactivity, not fixed interval
|
175
199
|
- Added `visit_duration` and `visitor_duration` options
|
176
200
|
|
177
|
-
## 0.3.2
|
201
|
+
## 0.3.2 (2014-06-15)
|
178
202
|
|
179
203
|
- Fixed bot exclusion for visits
|
180
204
|
- Fixed user method
|
181
205
|
|
182
|
-
## 0.3.1
|
206
|
+
## 0.3.1 (2014-06-12)
|
183
207
|
|
184
208
|
- Fixed visitor cookies when set on server
|
185
209
|
- Added `domain` option for server cookies
|
186
210
|
|
187
|
-
## 0.3.0
|
211
|
+
## 0.3.0 (2014-06-11)
|
188
212
|
|
189
213
|
- Added `current_visit_token` and `current_visitor_token` method
|
190
214
|
- Switched to UUIDs
|
@@ -192,47 +216,47 @@ Breaking changes
|
|
192
216
|
- Skip server-side bot events
|
193
217
|
- Added `request` argument to `exclude_method`
|
194
218
|
|
195
|
-
## 0.2.2
|
219
|
+
## 0.2.2 (2014-05-26)
|
196
220
|
|
197
221
|
- Added `exclude_method` option
|
198
222
|
- Added support for batch events
|
199
223
|
- Fixed cookie encoding
|
200
224
|
- Fixed `options` variable from being modified
|
201
225
|
|
202
|
-
## 0.2.1
|
226
|
+
## 0.2.1 (2014-05-16)
|
203
227
|
|
204
228
|
- Fixed IE 8 error
|
205
229
|
- Added `track_bots` option
|
206
230
|
- Added `$authenticate` event
|
207
231
|
|
208
|
-
## 0.2.0
|
232
|
+
## 0.2.0 (2014-05-13)
|
209
233
|
|
210
234
|
- Added event tracking (merged ahoy_events)
|
211
235
|
- Added ahoy.js
|
212
236
|
|
213
|
-
## 0.1.8
|
237
|
+
## 0.1.8 (2014-05-11)
|
214
238
|
|
215
239
|
- Fixed bug with `user_type` set to `false` instead of `nil`
|
216
240
|
|
217
|
-
## 0.1.7
|
241
|
+
## 0.1.7 (2014-05-11)
|
218
242
|
|
219
243
|
- Made cookie functions public for ahoy_events
|
220
244
|
|
221
|
-
## 0.1.6
|
245
|
+
## 0.1.6 (2014-05-07)
|
222
246
|
|
223
247
|
- Better user agent parser
|
224
248
|
|
225
|
-
## 0.1.5
|
249
|
+
## 0.1.5 (2014-05-01)
|
226
250
|
|
227
251
|
- Added support for Doorkeeper
|
228
252
|
- Added options to `visitable`
|
229
253
|
- Added `landing_params` method
|
230
254
|
|
231
|
-
## 0.1.4
|
255
|
+
## 0.1.4 (2014-04-27)
|
232
256
|
|
233
257
|
- Added `ahoy.ready()` and `ahoy.log()` for events
|
234
258
|
|
235
|
-
## 0.1.3
|
259
|
+
## 0.1.3 (2014-04-24)
|
236
260
|
|
237
261
|
- Supports `current_user` from `ApplicationController`
|
238
262
|
- Added `ahoy.reset()`
|
@@ -240,16 +264,16 @@ Breaking changes
|
|
240
264
|
- Added experimental support for native apps
|
241
265
|
- Prefer `ahoy` over `Ahoy`
|
242
266
|
|
243
|
-
## 0.1.2
|
267
|
+
## 0.1.2 (2014-04-15)
|
244
268
|
|
245
269
|
- Attach user on Devise sign up
|
246
270
|
- Ability to specify visit model
|
247
271
|
|
248
|
-
## 0.1.1
|
272
|
+
## 0.1.1 (2014-03-20)
|
249
273
|
|
250
274
|
- Made most database columns optional
|
251
275
|
- Performance hack for referer-parser
|
252
276
|
|
253
|
-
## 0.1.0
|
277
|
+
## 0.1.0 (2014-03-19)
|
254
278
|
|
255
279
|
- First major release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Ahoy
|
2
2
|
|
3
|
-
:fire: Simple, powerful analytics for Rails
|
3
|
+
:fire: Simple, powerful, first-party analytics for Rails
|
4
4
|
|
5
5
|
Track visits and events in Ruby, JavaScript, and native apps. Data is stored in your database by default so you can easily combine it with other data.
|
6
6
|
|
7
|
-
:postbox:
|
7
|
+
:postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for emails and [Field Test](https://github.com/ankane/field_test) for A/B testing
|
8
8
|
|
9
9
|
:tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
|
10
10
|
|
@@ -181,7 +181,7 @@ Order.joins(:ahoy_visit).group("device_type").count
|
|
181
181
|
Here’s what the migration to add the `ahoy_visit_id` column should look like:
|
182
182
|
|
183
183
|
```ruby
|
184
|
-
class AddVisitIdToOrders < ActiveRecord::Migration[
|
184
|
+
class AddVisitIdToOrders < ActiveRecord::Migration[6.0]
|
185
185
|
def change
|
186
186
|
add_column :orders, :ahoy_visit_id, :bigint
|
187
187
|
end
|
@@ -292,7 +292,7 @@ By default, a new visit is created after 4 hours of inactivity. Change this with
|
|
292
292
|
Ahoy.visit_duration = 30.minutes
|
293
293
|
```
|
294
294
|
|
295
|
-
###
|
295
|
+
### Cookies
|
296
296
|
|
297
297
|
To track visits across multiple subdomains, use:
|
298
298
|
|
@@ -300,6 +300,12 @@ To track visits across multiple subdomains, use:
|
|
300
300
|
Ahoy.cookie_domain = :all
|
301
301
|
```
|
302
302
|
|
303
|
+
Set other [cookie options](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) with:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
Ahoy.cookie_options = {same_site: :lax}
|
307
|
+
```
|
308
|
+
|
303
309
|
### Geocoding
|
304
310
|
|
305
311
|
Disable geocoding with:
|
@@ -308,7 +314,7 @@ Disable geocoding with:
|
|
308
314
|
Ahoy.geocode = false
|
309
315
|
```
|
310
316
|
|
311
|
-
|
317
|
+
The default job queue is `:ahoy`. Change this with:
|
312
318
|
|
313
319
|
```ruby
|
314
320
|
Ahoy.job_queue = :low_priority
|
@@ -427,6 +433,34 @@ Ahoy.cookies = false
|
|
427
433
|
|
428
434
|
Previously set cookies are automatically deleted.
|
429
435
|
|
436
|
+
## Data Retention
|
437
|
+
|
438
|
+
Data should only be retained for as long as it’s needed. Delete older data with:
|
439
|
+
|
440
|
+
```ruby
|
441
|
+
Ahoy::Visit.where("started_at < ?", 2.years.ago).find_in_batches do |visits|
|
442
|
+
visit_ids = visits.map(&:id)
|
443
|
+
Ahoy::Event.where(visit_id: visit_ids).delete_all
|
444
|
+
Ahoy::Visit.where(id: visit_ids).delete_all
|
445
|
+
end
|
446
|
+
```
|
447
|
+
|
448
|
+
You can use [Rollup](https://github.com/ankane/rollup) to aggregate important data before you do.
|
449
|
+
|
450
|
+
```ruby
|
451
|
+
Ahoy::Visit.rollup("Visits", interval: "hour")
|
452
|
+
```
|
453
|
+
|
454
|
+
Delete data for a specific user with:
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
user_id = 123
|
458
|
+
visit_ids = Ahoy::Visit.where(user_id: user_id).pluck(:id)
|
459
|
+
Ahoy::Event.where(visit_id: visit_ids).delete_all
|
460
|
+
Ahoy::Visit.where(id: visit_ids).delete_all
|
461
|
+
Ahoy::Event.where(user_id: user_id).delete_all
|
462
|
+
```
|
463
|
+
|
430
464
|
## Development
|
431
465
|
|
432
466
|
Ahoy is built with developers in mind. You can run the following code in your browser’s console.
|
@@ -552,7 +586,7 @@ Ahoy::Visit.group(:referring_domain).count
|
|
552
586
|
|
553
587
|
### Querying Events
|
554
588
|
|
555
|
-
Ahoy provides
|
589
|
+
Ahoy provides a few methods on the event model to make querying easier.
|
556
590
|
|
557
591
|
To query on both name and properties, you can use:
|
558
592
|
|
@@ -563,9 +597,17 @@ Ahoy::Event.where_event("Viewed product", product_id: 123).count
|
|
563
597
|
Or just query properties with:
|
564
598
|
|
565
599
|
```ruby
|
566
|
-
Ahoy::Event.where_props(product_id: 123).count
|
600
|
+
Ahoy::Event.where_props(product_id: 123, category: "Books").count
|
567
601
|
```
|
568
602
|
|
603
|
+
Group by properties with:
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
Ahoy::Event.group_prop(:product_id, :category).count
|
607
|
+
```
|
608
|
+
|
609
|
+
Note: MySQL and MariaDB always return string keys (include `"null"` for `nil`) for `group_prop`.
|
610
|
+
|
569
611
|
### Funnels
|
570
612
|
|
571
613
|
It’s easy to create funnels.
|
@@ -578,6 +620,29 @@ viewed_checkout_ids = Ahoy::Event.where(user_id: added_item_ids, name: "Viewed c
|
|
578
620
|
|
579
621
|
The same approach also works with visitor tokens.
|
580
622
|
|
623
|
+
### Rollups
|
624
|
+
|
625
|
+
Improve query performance by pre-aggregating data with [Rollup](https://github.com/ankane/rollup).
|
626
|
+
|
627
|
+
```ruby
|
628
|
+
Ahoy::Event.where(name: "Viewed store").rollup("Store views")
|
629
|
+
```
|
630
|
+
|
631
|
+
This is only needed if you have a lot of data.
|
632
|
+
|
633
|
+
### Forecasting
|
634
|
+
|
635
|
+
To forecast future visits and events, check out [Prophet](https://github.com/ankane/prophet).
|
636
|
+
|
637
|
+
```ruby
|
638
|
+
daily_visits = Ahoy::Visit.group_by_day(:started_at).count # uses Groupdate
|
639
|
+
Prophet.forecast(daily_visits)
|
640
|
+
```
|
641
|
+
|
642
|
+
### Recommendations
|
643
|
+
|
644
|
+
To make recommendations based on events, check out [Disco](https://github.com/ankane/disco#ahoy).
|
645
|
+
|
581
646
|
## Tutorials
|
582
647
|
|
583
648
|
- [Tracking Metrics with Ahoy and Blazer](https://gorails.com/episodes/internal-metrics-with-ahoy-and-blazer)
|
@@ -694,3 +759,20 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
694
759
|
- Fix bugs and [submit pull requests](https://github.com/ankane/ahoy/pulls)
|
695
760
|
- Write, clarify, or fix documentation
|
696
761
|
- Suggest or add new features
|
762
|
+
|
763
|
+
To get started with development:
|
764
|
+
|
765
|
+
```sh
|
766
|
+
git clone https://github.com/ankane/ahoy.git
|
767
|
+
cd ahoy
|
768
|
+
bundle install
|
769
|
+
bundle exec rake test
|
770
|
+
```
|
771
|
+
|
772
|
+
To test query methods, start PostgreSQL, MySQL, and MongoDB and use:
|
773
|
+
|
774
|
+
```sh
|
775
|
+
createdb ahoy_test
|
776
|
+
mysqladmin create ahoy_test
|
777
|
+
bundle exec rake test:query_methods
|
778
|
+
```
|
data/lib/ahoy.rb
CHANGED
@@ -30,8 +30,12 @@ module Ahoy
|
|
30
30
|
mattr_accessor :cookies
|
31
31
|
self.cookies = true
|
32
32
|
|
33
|
+
# TODO deprecate in favor of cookie_options
|
33
34
|
mattr_accessor :cookie_domain
|
34
35
|
|
36
|
+
mattr_accessor :cookie_options
|
37
|
+
self.cookie_options = {}
|
38
|
+
|
35
39
|
mattr_accessor :server_side_visits
|
36
40
|
self.server_side_visits = true
|
37
41
|
|
@@ -115,6 +119,10 @@ ActiveSupport.on_load(:action_view) do
|
|
115
119
|
end
|
116
120
|
|
117
121
|
# Mongoid
|
122
|
+
# TODO use
|
123
|
+
# ActiveSupport.on_load(:mongoid) do
|
124
|
+
# Mongoid::Document::ClassMethods.include(Ahoy::Model)
|
125
|
+
# end
|
118
126
|
if defined?(ActiveModel)
|
119
127
|
ActiveModel::Callbacks.include(Ahoy::Model)
|
120
128
|
end
|
data/lib/ahoy/base_store.rb
CHANGED
data/lib/ahoy/database_store.rb
CHANGED
@@ -76,7 +76,7 @@ module Ahoy
|
|
76
76
|
|
77
77
|
def slice_data(model, data)
|
78
78
|
column_names = model.try(:column_names) || model.attribute_names
|
79
|
-
data.slice(*column_names.map(&:to_sym)).select { |_, v| v }
|
79
|
+
data.slice(*column_names.map(&:to_sym)).select { |_, v| !v.nil? }
|
80
80
|
end
|
81
81
|
|
82
82
|
def unique_exception?(e)
|
data/lib/ahoy/query_methods.rb
CHANGED
@@ -31,6 +31,7 @@ module Ahoy
|
|
31
31
|
end
|
32
32
|
else
|
33
33
|
properties.each do |k, v|
|
34
|
+
# TODO cast to json instead
|
34
35
|
relation = relation.where("properties REGEXP ?", "[{,]#{{k.to_s => v}.to_json.sub(/\A\{/, "").sub(/\}\z/, "").gsub("+", "\\\\+")}[,}]")
|
35
36
|
end
|
36
37
|
end
|
@@ -57,6 +58,7 @@ module Ahoy
|
|
57
58
|
end
|
58
59
|
else
|
59
60
|
properties.each do |k, v|
|
61
|
+
# TODO cast to jsonb instead
|
60
62
|
relation = relation.where("properties SIMILAR TO ?", "%[{,]#{{k.to_s => v}.to_json.sub(/\A\{/, "").sub(/\}\z/, "").gsub("+", "\\\\+")}[,}]%")
|
61
63
|
end
|
62
64
|
end
|
@@ -66,6 +68,49 @@ module Ahoy
|
|
66
68
|
relation
|
67
69
|
end
|
68
70
|
alias_method :where_properties, :where_props
|
71
|
+
|
72
|
+
def group_prop(*props)
|
73
|
+
# like with group
|
74
|
+
props.flatten!
|
75
|
+
|
76
|
+
relation = self
|
77
|
+
if respond_to?(:columns_hash)
|
78
|
+
column_type = columns_hash["properties"].type
|
79
|
+
adapter_name = connection.adapter_name.downcase
|
80
|
+
else
|
81
|
+
adapter_name = "mongoid"
|
82
|
+
end
|
83
|
+
case adapter_name
|
84
|
+
when "mongoid"
|
85
|
+
raise "Adapter not supported: #{adapter_name}"
|
86
|
+
when /mysql/
|
87
|
+
if connection.try(:mariadb?)
|
88
|
+
props.each do |prop|
|
89
|
+
quoted_prop = connection.quote("$.#{prop}")
|
90
|
+
relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(properties, #{quoted_prop}))")
|
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
|
98
|
+
end
|
99
|
+
when /postgres|postgis/
|
100
|
+
# convert to jsonb to fix
|
101
|
+
# could not identify an equality operator for type json
|
102
|
+
# and for text columns
|
103
|
+
cast = [:jsonb, :hstore].include?(column_type) ? "" : "::jsonb"
|
104
|
+
|
105
|
+
props.each do |prop|
|
106
|
+
quoted_prop = connection.quote(prop)
|
107
|
+
relation = relation.group("properties#{cast} -> #{quoted_prop}")
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise "Adapter not supported: #{adapter_name}"
|
111
|
+
end
|
112
|
+
relation
|
113
|
+
end
|
69
114
|
end
|
70
115
|
end
|
71
116
|
end
|
data/lib/ahoy/tracker.rb
CHANGED
@@ -11,6 +11,7 @@ module Ahoy
|
|
11
11
|
@controller = options[:controller]
|
12
12
|
@request = options[:request] || @controller.try(:request)
|
13
13
|
@visit_token = options[:visit_token]
|
14
|
+
@user = options[:user]
|
14
15
|
@options = options
|
15
16
|
end
|
16
17
|
|
@@ -57,7 +58,7 @@ module Ahoy
|
|
57
58
|
|
58
59
|
@store.track_visit(data)
|
59
60
|
|
60
|
-
Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode
|
61
|
+
Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode && data[:ip]
|
61
62
|
end
|
62
63
|
end
|
63
64
|
true
|
@@ -128,7 +129,7 @@ module Ahoy
|
|
128
129
|
end
|
129
130
|
|
130
131
|
def visit_properties
|
131
|
-
@visit_properties ||= Ahoy::VisitProperties.new(request, api: api?).generate
|
132
|
+
@visit_properties ||= request ? Ahoy::VisitProperties.new(request, api: api?).generate : {}
|
132
133
|
end
|
133
134
|
|
134
135
|
def visit_token
|
@@ -168,19 +169,18 @@ module Ahoy
|
|
168
169
|
|
169
170
|
def set_cookie(name, value, duration = nil, use_domain = true)
|
170
171
|
# safety net
|
171
|
-
return unless Ahoy.cookies
|
172
|
+
return unless Ahoy.cookies && request
|
172
173
|
|
173
|
-
cookie =
|
174
|
-
value: value
|
175
|
-
}
|
174
|
+
cookie = Ahoy.cookie_options.merge(value: value)
|
176
175
|
cookie[:expires] = duration.from_now if duration
|
177
|
-
domain
|
178
|
-
cookie[:domain]
|
176
|
+
# prefer cookie_options[:domain] over cookie_domain
|
177
|
+
cookie[:domain] ||= Ahoy.cookie_domain if Ahoy.cookie_domain
|
178
|
+
cookie.delete(:domain) unless use_domain
|
179
179
|
request.cookie_jar[name] = cookie
|
180
180
|
end
|
181
181
|
|
182
182
|
def delete_cookie(name)
|
183
|
-
request.cookie_jar.delete(name) if request.cookie_jar[name]
|
183
|
+
request.cookie_jar.delete(name) if request && request.cookie_jar[name]
|
184
184
|
end
|
185
185
|
|
186
186
|
def trusted_time(time = nil)
|
@@ -196,8 +196,12 @@ module Ahoy
|
|
196
196
|
end
|
197
197
|
|
198
198
|
def report_exception(e)
|
199
|
-
|
200
|
-
|
199
|
+
if defined?(ActionDispatch::RemoteIp::IpSpoofAttackError) && e.is_a?(ActionDispatch::RemoteIp::IpSpoofAttackError)
|
200
|
+
debug "Tracking excluded due to IP spoofing"
|
201
|
+
else
|
202
|
+
raise e if !defined?(Rails) || Rails.env.development? || Rails.env.test?
|
203
|
+
Safely.report_exception(e)
|
204
|
+
end
|
201
205
|
end
|
202
206
|
|
203
207
|
def generate_id
|
data/lib/ahoy/version.rb
CHANGED
@@ -1,27 +1,13 @@
|
|
1
|
-
# taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
|
2
|
-
require "rails/generators"
|
3
|
-
require "rails/generators/migration"
|
4
|
-
require "active_record"
|
5
1
|
require "rails/generators/active_record"
|
6
2
|
|
7
3
|
module Ahoy
|
8
4
|
module Generators
|
9
5
|
class ActiverecordGenerator < Rails::Generators::Base
|
10
|
-
include
|
11
|
-
source_root File.
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
12
8
|
|
13
9
|
class_option :database, type: :string, aliases: "-d"
|
14
10
|
|
15
|
-
# Implement the required interface for Rails::Generators::Migration.
|
16
|
-
def self.next_migration_number(dirname) #:nodoc:
|
17
|
-
next_migration_number = current_migration_number(dirname) + 1
|
18
|
-
if ::ActiveRecord::Base.timestamped_migrations
|
19
|
-
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
20
|
-
else
|
21
|
-
"%.3d" % next_migration_number
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
11
|
def copy_templates
|
26
12
|
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
27
13
|
template "active_record_visit_model.rb", "app/models/ahoy/visit.rb"
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class BaseGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
template "base_store_initializer.rb", "config/initializers/ahoy.rb"
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
activerecord = defined?(ActiveRecord)
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class MongoidGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
@@ -41,10 +41,10 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
41
41
|
t.string :os_version
|
42
42
|
t.string :platform
|
43
43
|
|
44
|
-
t.
|
44
|
+
t.datetime :started_at
|
45
45
|
end
|
46
46
|
|
47
|
-
add_index :ahoy_visits,
|
47
|
+
add_index :ahoy_visits, :visit_token, unique: true
|
48
48
|
|
49
49
|
create_table :ahoy_events do |t|
|
50
50
|
t.references :visit
|
@@ -52,7 +52,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
52
52
|
|
53
53
|
t.string :name
|
54
54
|
t.<%= properties_type %> :properties
|
55
|
-
t.
|
55
|
+
t.datetime :time
|
56
56
|
end
|
57
57
|
|
58
58
|
add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" %><% if rails52? %>
|
@@ -2,112 +2,17 @@
|
|
2
2
|
* Ahoy.js
|
3
3
|
* Simple, powerful JavaScript analytics
|
4
4
|
* https://github.com/ankane/ahoy.js
|
5
|
-
* v0.3.
|
5
|
+
* v0.3.6
|
6
6
|
* MIT License
|
7
7
|
*/
|
8
8
|
|
9
9
|
(function (global, factory) {
|
10
10
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
11
11
|
typeof define === 'function' && define.amd ? define(factory) :
|
12
|
-
(global.ahoy = factory());
|
12
|
+
(global = global || self, global.ahoy = factory());
|
13
13
|
}(this, (function () { 'use strict';
|
14
14
|
|
15
|
-
function
|
16
|
-
return value === undefined;
|
17
|
-
}
|
18
|
-
|
19
|
-
function isNull(value) {
|
20
|
-
return value === null;
|
21
|
-
}
|
22
|
-
|
23
|
-
function isObject(value) {
|
24
|
-
return value === Object(value);
|
25
|
-
}
|
26
|
-
|
27
|
-
function isArray(value) {
|
28
|
-
return Array.isArray(value);
|
29
|
-
}
|
30
|
-
|
31
|
-
function isDate(value) {
|
32
|
-
return value instanceof Date;
|
33
|
-
}
|
34
|
-
|
35
|
-
function isBlob(value) {
|
36
|
-
return (
|
37
|
-
value &&
|
38
|
-
typeof value.size === 'number' &&
|
39
|
-
typeof value.type === 'string' &&
|
40
|
-
typeof value.slice === 'function'
|
41
|
-
);
|
42
|
-
}
|
43
|
-
|
44
|
-
function isFile(value) {
|
45
|
-
return (
|
46
|
-
isBlob(value) &&
|
47
|
-
(typeof value.lastModifiedDate === 'object' ||
|
48
|
-
typeof value.lastModified === 'number') &&
|
49
|
-
typeof value.name === 'string'
|
50
|
-
);
|
51
|
-
}
|
52
|
-
|
53
|
-
function isFormData(value) {
|
54
|
-
return value instanceof FormData;
|
55
|
-
}
|
56
|
-
|
57
|
-
function objectToFormData(obj, cfg, fd, pre) {
|
58
|
-
if (isFormData(cfg)) {
|
59
|
-
pre = fd;
|
60
|
-
fd = cfg;
|
61
|
-
cfg = null;
|
62
|
-
}
|
63
|
-
|
64
|
-
cfg = cfg || {};
|
65
|
-
cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;
|
66
|
-
cfg.nulls = isUndefined(cfg.nulls) ? true : cfg.nulls;
|
67
|
-
fd = fd || new FormData();
|
68
|
-
|
69
|
-
if (isUndefined(obj)) {
|
70
|
-
return fd;
|
71
|
-
} else if (isNull(obj)) {
|
72
|
-
if (cfg.nulls) {
|
73
|
-
fd.append(pre, '');
|
74
|
-
}
|
75
|
-
} else if (isArray(obj)) {
|
76
|
-
if (!obj.length) {
|
77
|
-
var key = pre + '[]';
|
78
|
-
|
79
|
-
fd.append(key, '');
|
80
|
-
} else {
|
81
|
-
obj.forEach(function(value, index) {
|
82
|
-
var key = pre + '[' + (cfg.indices ? index : '') + ']';
|
83
|
-
|
84
|
-
objectToFormData(value, cfg, fd, key);
|
85
|
-
});
|
86
|
-
}
|
87
|
-
} else if (isDate(obj)) {
|
88
|
-
fd.append(pre, obj.toISOString());
|
89
|
-
} else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
|
90
|
-
Object.keys(obj).forEach(function(prop) {
|
91
|
-
var value = obj[prop];
|
92
|
-
|
93
|
-
if (isArray(value)) {
|
94
|
-
while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
|
95
|
-
prop = prop.substring(0, prop.length - 2);
|
96
|
-
}
|
97
|
-
}
|
98
|
-
|
99
|
-
var key = pre ? pre + '[' + prop + ']' : prop;
|
100
|
-
|
101
|
-
objectToFormData(value, cfg, fd, key);
|
102
|
-
});
|
103
|
-
} else {
|
104
|
-
fd.append(pre, obj);
|
105
|
-
}
|
106
|
-
|
107
|
-
return fd;
|
108
|
-
}
|
109
|
-
|
110
|
-
var objectToFormdata = objectToFormData;
|
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)};
|
111
16
|
|
112
17
|
// https://www.quirksmode.org/js/cookies.html
|
113
18
|
|
@@ -155,7 +60,9 @@
|
|
155
60
|
cookieDomain: null,
|
156
61
|
headers: {},
|
157
62
|
visitParams: {},
|
158
|
-
withCredentials: false
|
63
|
+
withCredentials: false,
|
64
|
+
visitDuration: 4 * 60, // default 4 hours
|
65
|
+
visitorDuration: 2 * 365 * 24 * 60 // default 2 years
|
159
66
|
};
|
160
67
|
|
161
68
|
var ahoy = window.ahoy || window.Ahoy || {};
|
@@ -173,8 +80,6 @@
|
|
173
80
|
|
174
81
|
var $ = window.jQuery || window.Zepto || window.$;
|
175
82
|
var visitId, visitorId, track;
|
176
|
-
var visitTtl = 4 * 60; // 4 hours
|
177
|
-
var visitorTtl = 2 * 365 * 24 * 60; // 2 years
|
178
83
|
var isReady = false;
|
179
84
|
var queue = [];
|
180
85
|
var canStringify = typeof(JSON) !== "undefined" && typeof(JSON.stringify) !== "undefined";
|
@@ -224,13 +129,13 @@
|
|
224
129
|
isReady = true;
|
225
130
|
}
|
226
131
|
|
227
|
-
function
|
132
|
+
ahoy.ready = function (callback) {
|
228
133
|
if (isReady) {
|
229
134
|
callback();
|
230
135
|
} else {
|
231
136
|
queue.push(callback);
|
232
137
|
}
|
233
|
-
}
|
138
|
+
};
|
234
139
|
|
235
140
|
function matchesSelector(element, selector) {
|
236
141
|
var matches = element.matches ||
|
@@ -258,7 +163,11 @@
|
|
258
163
|
|
259
164
|
// http://beeker.io/jquery-document-ready-equivalent-vanilla-javascript
|
260
165
|
function documentReady(callback) {
|
261
|
-
document.readyState === "interactive" || document.readyState === "complete"
|
166
|
+
if (document.readyState === "interactive" || document.readyState === "complete") {
|
167
|
+
setTimeout(callback, 0);
|
168
|
+
} else {
|
169
|
+
document.addEventListener("DOMContentLoaded", callback);
|
170
|
+
}
|
262
171
|
}
|
263
172
|
|
264
173
|
// https://stackoverflow.com/a/2117523/1177228
|
@@ -294,7 +203,7 @@
|
|
294
203
|
|
295
204
|
function sendRequest(url, data, success) {
|
296
205
|
if (canStringify) {
|
297
|
-
if ($) {
|
206
|
+
if ($ && $.ajax) {
|
298
207
|
$.ajax({
|
299
208
|
type: "POST",
|
300
209
|
url: url,
|
@@ -343,7 +252,7 @@
|
|
343
252
|
}
|
344
253
|
|
345
254
|
function trackEvent(event) {
|
346
|
-
ready( function () {
|
255
|
+
ahoy.ready( function () {
|
347
256
|
sendRequest(eventsUrl(), eventData(event), function() {
|
348
257
|
// remove from queue
|
349
258
|
for (var i = 0; i < eventQueue.length; i++) {
|
@@ -358,7 +267,7 @@
|
|
358
267
|
}
|
359
268
|
|
360
269
|
function trackEventNow(event) {
|
361
|
-
ready( function () {
|
270
|
+
ahoy.ready( function () {
|
362
271
|
var data = eventData(event);
|
363
272
|
var param = csrfParam();
|
364
273
|
var token = csrfToken();
|
@@ -366,7 +275,7 @@
|
|
366
275
|
// stringify so we keep the type
|
367
276
|
data.events_json = JSON.stringify(data.events);
|
368
277
|
delete data.events;
|
369
|
-
window.navigator.sendBeacon(eventsUrl(),
|
278
|
+
window.navigator.sendBeacon(eventsUrl(), s(data));
|
370
279
|
});
|
371
280
|
}
|
372
281
|
|
@@ -427,7 +336,7 @@
|
|
427
336
|
} else {
|
428
337
|
if (!visitId) {
|
429
338
|
visitId = generateId();
|
430
|
-
setCookie("ahoy_visit", visitId,
|
339
|
+
setCookie("ahoy_visit", visitId, config.visitDuration);
|
431
340
|
}
|
432
341
|
|
433
342
|
// make sure cookies are enabled
|
@@ -436,7 +345,7 @@
|
|
436
345
|
|
437
346
|
if (!visitorId) {
|
438
347
|
visitorId = generateId();
|
439
|
-
setCookie("ahoy_visitor", visitorId,
|
348
|
+
setCookie("ahoy_visitor", visitorId, config.visitorDuration);
|
440
349
|
}
|
441
350
|
|
442
351
|
var data = {
|
@@ -509,12 +418,12 @@
|
|
509
418
|
js: true
|
510
419
|
};
|
511
420
|
|
512
|
-
ready( function () {
|
421
|
+
ahoy.ready( function () {
|
513
422
|
if (config.cookies && !ahoy.getVisitId()) {
|
514
423
|
createVisit();
|
515
424
|
}
|
516
425
|
|
517
|
-
ready( function () {
|
426
|
+
ahoy.ready( function () {
|
518
427
|
log(event);
|
519
428
|
|
520
429
|
event.visit_token = ahoy.getVisitId();
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ahoy_matey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -109,7 +109,35 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
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
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
143
|
- - ">="
|
@@ -164,6 +192,34 @@ dependencies:
|
|
164
192
|
- - ">="
|
165
193
|
- !ruby/object:Gem::Version
|
166
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'
|
167
223
|
description:
|
168
224
|
email: andrew@chartkick.com
|
169
225
|
executables: []
|
@@ -225,8 +281,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
225
281
|
- !ruby/object:Gem::Version
|
226
282
|
version: '0'
|
227
283
|
requirements: []
|
228
|
-
rubygems_version: 3.
|
284
|
+
rubygems_version: 3.1.2
|
229
285
|
signing_key:
|
230
286
|
specification_version: 4
|
231
|
-
summary: Simple, powerful analytics for Rails
|
287
|
+
summary: Simple, powerful, first-party analytics for Rails
|
232
288
|
test_files: []
|