footprinted 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +50 -1
- data/app/jobs/footprinted/track_job.rb +7 -0
- data/gemfiles/rails_7.2.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/footprinted/model.rb +24 -0
- data/lib/footprinted/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20ea0ce295c5fe9031013e323a46e744b92de81e6e433db994dc4df485373e31
|
|
4
|
+
data.tar.gz: '09c5e324348d00dcb53b8fbe7e581e22ee45e9d194f706d48685678cc999359a'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69f4c70cdddadd0bcf208bcecc337ab484ae5ac6991748488e4620a5d712c69ae6b444b26ceb6c0ad10a036c4652185a938509c203348d16b5cac0a28ae9c7a4
|
|
7
|
+
data.tar.gz: 5aaa74017232c3f54cc6db64155d75d762a7f758f04a7e886474e7af9cd579173264c744447f86f0eae56b84b096cb984f462bd40105e35a1ea80e1e8283448f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-02-15
|
|
4
|
+
|
|
5
|
+
- **Async mode now extracts geo data at enqueue time** when `request:` is passed
|
|
6
|
+
- Cloudflare headers are captured before the job is enqueued
|
|
7
|
+
- Background jobs no longer need MaxMind to get geo data
|
|
8
|
+
- Falls back to MaxMind lookup in the job if `request:` wasn't passed
|
|
9
|
+
- No breaking changes — existing code works unchanged
|
|
10
|
+
|
|
3
11
|
## [0.2.1] - 2026-02-09
|
|
4
12
|
|
|
5
13
|
- Fix async mode: change Railtie to Engine so `TrackJob` is autoloaded
|
data/README.md
CHANGED
|
@@ -245,7 +245,41 @@ add_index :footprints, :device_id
|
|
|
245
245
|
add_index :footprints, :app_version
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
Your tracking calls stay the same — just pass everything in `metadata` as before. To auto-promote metadata keys into their dedicated columns, add a `before_save` callback in an initializer:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
# config/initializers/footprinted_extensions.rb
|
|
252
|
+
|
|
253
|
+
# String columns that map 1:1 from metadata
|
|
254
|
+
FOOTPRINT_PROMOTED_STRING_COLUMNS = %w[device_id app_version platform].freeze
|
|
255
|
+
|
|
256
|
+
# Integer columns that need casting
|
|
257
|
+
FOOTPRINT_PROMOTED_INTEGER_COLUMNS = %w[cpu_cores memory_gb].freeze
|
|
258
|
+
|
|
259
|
+
Rails.configuration.to_prepare do
|
|
260
|
+
Footprinted::Footprint.class_eval do
|
|
261
|
+
before_save :promote_metadata_columns
|
|
262
|
+
|
|
263
|
+
private
|
|
264
|
+
|
|
265
|
+
def promote_metadata_columns
|
|
266
|
+
return if metadata.blank?
|
|
267
|
+
|
|
268
|
+
m = metadata.stringify_keys
|
|
269
|
+
|
|
270
|
+
FOOTPRINT_PROMOTED_STRING_COLUMNS.each do |key|
|
|
271
|
+
self[key] = m[key] if self[key].blank? && m[key].present?
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
FOOTPRINT_PROMOTED_INTEGER_COLUMNS.each do |key|
|
|
275
|
+
self[key] = m[key].to_i if self[key].blank? && m[key].present?
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This works regardless of how footprints are created — via `TrackJob` (async), direct `.create!`, or the Rails console. The gem stays generic; your app adds the columns and promotion logic it needs.
|
|
249
283
|
|
|
250
284
|
> [!TIP]
|
|
251
285
|
> Which metadata keys to promote depends on your use case. A licensing SaaS might promote `device_id` + `app_version`. An e-commerce app might promote `product_id` + `session_id`. A CMS might promote `page_url` + `referrer`. Keep the JSONB for everything else.
|
|
@@ -276,6 +310,21 @@ When async is enabled, `track` and `track_<event_type>` calls enqueue a `Footpri
|
|
|
276
310
|
|
|
277
311
|
You need a working ActiveJob backend (Sidekiq, Solid Queue, etc.) for this to work.
|
|
278
312
|
|
|
313
|
+
### Geolocation in async mode
|
|
314
|
+
|
|
315
|
+
When you pass `request:` in async mode, geolocation data is extracted **immediately** (before enqueueing) using Cloudflare headers via [`trackdown`](https://github.com/rameerez/trackdown). This means:
|
|
316
|
+
|
|
317
|
+
- **No MaxMind database needed** in your background workers if you use Cloudflare
|
|
318
|
+
- Geo data is captured at request time, not in the job
|
|
319
|
+
- If `request:` is not passed, the job falls back to MaxMind lookup
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
# In your controller — pass request: for Cloudflare geo extraction
|
|
323
|
+
@product.track(:purchase, ip: request.remote_ip, request: request)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This is the recommended pattern for async mode behind Cloudflare.
|
|
327
|
+
|
|
279
328
|
## Geolocation via Trackdown
|
|
280
329
|
|
|
281
330
|
`footprinted` automatically resolves geolocation data from IP addresses using the [`trackdown`](https://github.com/rameerez/trackdown) gem. For every footprint, the following fields are populated:
|
|
@@ -11,6 +11,13 @@ module Footprinted
|
|
|
11
11
|
attrs = attributes.symbolize_keys
|
|
12
12
|
attrs[:occurred_at] = Time.parse(attrs[:occurred_at]) if attrs[:occurred_at].is_a?(String)
|
|
13
13
|
|
|
14
|
+
# Log geo data status for debugging
|
|
15
|
+
if attrs[:country_code].present?
|
|
16
|
+
Rails.logger.debug { "[Footprinted] TrackJob received pre-extracted geo: #{attrs[:country_code]}/#{attrs[:city]}" }
|
|
17
|
+
else
|
|
18
|
+
Rails.logger.debug { "[Footprinted] TrackJob has no pre-extracted geo, will attempt lookup for #{attrs[:ip]}" }
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
trackable.footprints.create!(attrs)
|
|
15
22
|
end
|
|
16
23
|
end
|
data/lib/footprinted/model.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Footprinted
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if Footprinted.configuration.async
|
|
31
|
+
Footprinted::Model.enrich_with_geo_data!(attrs, ip, request)
|
|
31
32
|
Footprinted::TrackJob.perform_later(
|
|
32
33
|
self.class.name, id,
|
|
33
34
|
attrs.merge(occurred_at: attrs[:occurred_at].iso8601)
|
|
@@ -52,6 +53,7 @@ module Footprinted
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
if Footprinted.configuration.async
|
|
56
|
+
Footprinted::Model.enrich_with_geo_data!(attrs, ip, request)
|
|
55
57
|
Footprinted::TrackJob.perform_later(
|
|
56
58
|
self.class.name, id,
|
|
57
59
|
attrs.merge(occurred_at: attrs[:occurred_at].iso8601)
|
|
@@ -63,5 +65,27 @@ module Footprinted
|
|
|
63
65
|
record
|
|
64
66
|
end
|
|
65
67
|
end
|
|
68
|
+
|
|
69
|
+
# Extract geo data from request (Cloudflare headers) before enqueueing
|
|
70
|
+
# This allows async jobs to have geo data without needing MaxMind
|
|
71
|
+
def self.enrich_with_geo_data!(attrs, ip, request)
|
|
72
|
+
return unless request && defined?(Trackdown)
|
|
73
|
+
|
|
74
|
+
location = Trackdown.locate(ip.to_s, request: request)
|
|
75
|
+
attrs.merge!(
|
|
76
|
+
country_code: location.country_code,
|
|
77
|
+
country_name: location.country_name,
|
|
78
|
+
city: location.city,
|
|
79
|
+
region: location.region,
|
|
80
|
+
continent: location.continent,
|
|
81
|
+
timezone: location.timezone,
|
|
82
|
+
latitude: location.latitude,
|
|
83
|
+
longitude: location.longitude
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if location.country_code.present?
|
|
87
|
+
Rails.logger.debug { "[Footprinted] Extracted geo at enqueue: #{location.country_code}/#{location.city} for #{ip}" }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
66
90
|
end
|
|
67
91
|
end
|
data/lib/footprinted/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: footprinted
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-02-
|
|
10
|
+
date: 2026-02-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|