trackdown 0.2.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ac258f067b151ee511c897d3460694466ed9ea36e2ff96010cccb5acf106b16
4
- data.tar.gz: 1120ed37341b3fe876ed38997440537968e9014a1918b0c2c12acd3d10c36615
3
+ metadata.gz: 675d77c9715d2b1de76e4d385b95960b82f45355edb57689d2cf243197f19324
4
+ data.tar.gz: 061e61803019b00dd99e2d186f3c3ccbe73cf5ee9a750b513e1dfc11c46c0859
5
5
  SHA512:
6
- metadata.gz: fd9029fb452c671613dcadb3081fc8e036634506f564b354997798d4b59617ac316c19976ae0ea2d9f4cfbc6a6d16a9a7729fc70d60d6a47354a808205bb6106
7
- data.tar.gz: 94a789bbd321cb389590c466da2de5c7887812c9362d086a3d35446a428bd2a2a6a398f485d7629f86fec24e7e51a5b26d9871e512a4bb4e49f10edf9de45f7b
6
+ metadata.gz: 5394b49ff9c641ae941675b2bc18da7e92d934effab2f771707ded19179ea4f019a5c18533dc059e5e8f12e7cf6ff0847d0e3dd91586e1e218bb7f0f3d96c139
7
+ data.tar.gz: bddbc1ea728846d08dd1ca75033c3a9b3eb95144fdbec641089593c8f1e177e3ac52eb9cb495b2a14bbc118d99d23a504f2ecbe37578017629f9344e13faf0d7
data/.simplecov ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ SimpleCov.start do
4
+ formatter SimpleCov::Formatter::SimpleFormatter
5
+
6
+ add_filter "/test/"
7
+
8
+ track_files "{lib,app}/**/*.rb"
9
+
10
+ enable_coverage :branch
11
+
12
+ minimum_coverage line: 80, branch: 65
13
+
14
+ command_name "Job #{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
15
+ end
16
+
17
+ SimpleCov.at_exit do
18
+ SimpleCov.result.format!
19
+ puts "\n" + "=" * 60
20
+ puts "COVERAGE SUMMARY"
21
+ puts "=" * 60
22
+ puts "Line Coverage: #{SimpleCov.result.covered_percent.round(2)}%"
23
+ puts "Branch Coverage: #{SimpleCov.result.coverage_statistics[:branch]&.percent&.round(2) || 'N/A'}%"
24
+ puts "=" * 60
25
+ end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.3.0] - 2026-02-08
2
+
3
+ - Add 8 new geolocation fields: `region`, `region_code`, `continent`, `timezone`, `latitude`, `longitude`, `postal_code`, `metro_code`
4
+ - All new fields available from both Cloudflare and MaxMind providers
5
+ - All 10 Cloudflare "Add visitor location headers" now fully supported
6
+ - Backward compatible — all new fields are optional, existing API unchanged
7
+ - `to_h` now includes all new fields
8
+
1
9
  ## [0.2.0] - 2026-01-02
2
10
 
3
11
  - Completely decouple Maxmind from the gem, making it optional
data/README.md CHANGED
@@ -1,23 +1,36 @@
1
1
  # 📍 `trackdown` - Ruby gem to geolocate IPs
2
2
 
3
- `trackdown` is a Ruby gem that allows you to geolocate IP addresses easily. It works out-of-the-box with **Cloudflare** (zero config!); and it's also a simple, convenient wrapper on top of **MaxMind** (just bring your own MaxMind key, and you're good to go!). `trackdown` offers a clean API for Rails applications to fetch country, city, and emoji flag information for any IP address.
3
+ > [!TIP]
4
+ > **🚀 Ship your next Rails app 10x faster!** I've built **[RailsFast](https://railsfast.com/?ref=trackdown)**, a production-ready Rails boilerplate template that comes with everything you need to launch a software business in days, not weeks. Go [check it out](https://railsfast.com/?ref=trackdown)!
5
+
6
+ `trackdown` is a Ruby gem that allows you to geolocate IP addresses easily.
7
+
8
+ It works out-of-the-box with **Cloudflare** (zero config!); and it's also a simple, convenient wrapper on top of **MaxMind** (just bring your own MaxMind key, and you're good to go!).
9
+
10
+ `trackdown` offers a clean API for Rails applications to fetch country, city, region, continent, timezone, coordinates, and emoji flag information for any IP address.
4
11
 
5
12
  Given an IP, it gives you the corresponding:
6
13
  - 🗺️ Country (two-letter country code + country name)
7
14
  - 📍 City
15
+ - 🏔️ Region / state (e.g. "California") and region code (e.g. "CA")
16
+ - 🌍 Continent (e.g. "NA", "EU")
17
+ - 🕐 Timezone (e.g. "America/Los_Angeles")
18
+ - 📌 Latitude and longitude coordinates
19
+ - 📮 Postal code (e.g. "94107")
20
+ - 📺 Metro code (e.g. "807")
8
21
  - 🇺🇸 Emoji flag of the country
9
22
 
10
- ## Two ways to use `trackdown`
23
+ ## First, choose your `trackdown` Geo IP provider
11
24
 
12
25
  ### Option 1: Cloudflare (recommended, zero config)
13
26
 
14
- If your app is behind Cloudflare, you can use `trackdown` with **zero configuration**:
27
+ If your Rails app is behind Cloudflare, you can use `trackdown` with **zero configuration**:
15
28
  - No API keys needed
16
29
  - No database downloads
17
30
  - No external dependencies
18
31
  - Instant lookups from Cloudflare headers
19
32
 
20
- Just enable "IP Geolocation" in your Cloudflare dashboard and you're done! We automatically check for the Cloudflare headers in the context of a `request` and provide you with the IP geo data.
33
+ Just enable "IP Geolocation" in your Cloudflare dashboard and you're done! For the full set of location fields (city, region, coordinates, etc.), enable ["Add visitor location headers"](https://developers.cloudflare.com/rules/transform/managed-transforms/reference/) in Managed Transforms. We automatically read these headers from the `request` and provide you with the IP geo data.
21
34
 
22
35
  ### Option 2: MaxMind (BYOK - Bring Your Own Key)
23
36
 
@@ -162,12 +175,23 @@ result.country_code # => 'US'
162
175
  result.country_name # => 'United States'
163
176
  result.country # => 'United States' (alias for country_name)
164
177
  result.city # => 'Mountain View' (from MaxMind or Cloudflare's "Add visitor location headers")
178
+ result.region # => 'California'
179
+ result.region_code # => 'CA'
180
+ result.continent # => 'NA'
181
+ result.timezone # => 'America/Los_Angeles'
182
+ result.latitude # => 37.7749
183
+ result.longitude # => -122.4194
184
+ result.postal_code # => '94107'
185
+ result.metro_code # => '807'
165
186
  result.flag_emoji # => '🇺🇸'
166
187
  result.emoji # => '🇺🇸' (alias for flag_emoji)
167
188
  result.country_flag # => '🇺🇸' (alias for flag_emoji)
168
189
  result.country_info # => # Rich country data from the `countries` gem
169
190
  ```
170
191
 
192
+ > [!NOTE]
193
+ > The `region`, `region_code`, `continent`, `timezone`, `latitude`, `longitude`, `postal_code`, and `metro_code` fields require Cloudflare's ["Add visitor location headers"](https://developers.cloudflare.com/rules/transform/managed-transforms/reference/) Managed Transform to be enabled, or a MaxMind GeoLite2-City database. These fields return `nil` when not available.
194
+
171
195
  ### Rich country information
172
196
 
173
197
  For `country_info` we're leveraging the [`countries`](https://github.com/countries/countries) gem, so you get a lot of information about the country, like the continent, the region, the languages spoken, the currency, and more:
@@ -191,6 +215,14 @@ result.to_h
191
215
  # country_name: 'United States',
192
216
  # city: 'Mountain View',
193
217
  # flag_emoji: '🇺🇸',
218
+ # region: 'California',
219
+ # region_code: 'CA',
220
+ # continent: 'NA',
221
+ # timezone: 'America/Los_Angeles',
222
+ # latitude: 37.7749,
223
+ # longitude: -122.4194,
224
+ # postal_code: '94107',
225
+ # metro_code: '807',
194
226
  # country_info: { ... }
195
227
  # }
196
228
  ```
@@ -243,13 +275,26 @@ Trackdown.update_database
243
275
 
244
276
  ### Cloudflare Provider
245
277
 
246
- When you enable "IP Geolocation" in Cloudflare, they add the `CF-IPCountry` header to every request. If you enable "Add visitor location headers" (via Managed Transforms), you also get `CF-IPCity`.
278
+ When you enable "IP Geolocation" in Cloudflare, they add the `CF-IPCountry` header to every request. If you also enable ["Add visitor location headers"](https://developers.cloudflare.com/rules/transform/managed-transforms/reference/) (via Managed Transforms), you get all 10 location headers:
279
+
280
+ | Cloudflare header | `trackdown` field |
281
+ |---|---|
282
+ | `cf-ipcountry` | `country_code` |
283
+ | `cf-ipcity` | `city` |
284
+ | `cf-ipcontinent` | `continent` |
285
+ | `cf-iplatitude` | `latitude` |
286
+ | `cf-iplongitude` | `longitude` |
287
+ | `cf-region` | `region` |
288
+ | `cf-region-code` | `region_code` |
289
+ | `cf-metro-code` | `metro_code` |
290
+ | `cf-postal-code` | `postal_code` |
291
+ | `cf-timezone` | `timezone` |
247
292
 
248
- Trackdown reads these headers directly from the request with zero overhead, and no database lookups.
293
+ Trackdown reads these headers directly from the request with zero overhead no database lookups, no external API calls.
249
294
 
250
295
  ### MaxMind Provider
251
296
 
252
- Downloads the GeoLite2-City database to your server and performs local lookups using connection pooling for performance.
297
+ Downloads the [GeoLite2-City](https://dev.maxmind.com/geoip/docs/databases/city-and-country/) database to your server and performs local lookups using connection pooling for performance. All fields (`country`, `city`, `region`, `continent`, `timezone`, `latitude`, `longitude`, `postal_code`, `metro_code`) are extracted from the database record.
253
298
 
254
299
 
255
300
  ## Development
data/Rakefile CHANGED
@@ -7,7 +7,6 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
9
  t.test_files = FileList["test/**/*_test.rb"]
10
- t.verbose = true
11
10
  end
12
11
 
13
12
  task default: :test
@@ -4,13 +4,26 @@ require 'countries'
4
4
 
5
5
  module Trackdown
6
6
  class LocationResult
7
- attr_reader :country_code, :country_name, :city, :flag_emoji
7
+ attr_reader :country_code, :country_name, :city, :flag_emoji,
8
+ :region, :region_code, :continent, :timezone, :latitude, :longitude,
9
+ :postal_code, :metro_code
8
10
 
9
- def initialize(country_code, country_name, city, flag_emoji)
11
+ def initialize(country_code, country_name, city, flag_emoji,
12
+ region: nil, region_code: nil, continent: nil,
13
+ timezone: nil, latitude: nil, longitude: nil,
14
+ postal_code: nil, metro_code: nil)
10
15
  @country_code = country_code
11
16
  @country_name = country_name
12
17
  @city = city
13
18
  @flag_emoji = flag_emoji
19
+ @region = region
20
+ @region_code = region_code
21
+ @continent = continent
22
+ @timezone = timezone
23
+ @latitude = latitude
24
+ @longitude = longitude
25
+ @postal_code = postal_code
26
+ @metro_code = metro_code
14
27
  end
15
28
 
16
29
  alias_method :country, :country_name
@@ -29,6 +42,14 @@ module Trackdown
29
42
  country_name: @country_name,
30
43
  city: @city,
31
44
  flag_emoji: @flag_emoji,
45
+ region: @region,
46
+ region_code: @region_code,
47
+ continent: @continent,
48
+ timezone: @timezone,
49
+ latitude: @latitude,
50
+ longitude: @longitude,
51
+ postal_code: @postal_code,
52
+ metro_code: @metro_code,
32
53
  country_info: country_info&.data || {}
33
54
  }
34
55
  end
@@ -13,6 +13,14 @@ module Trackdown
13
13
  class CloudflareProvider < BaseProvider
14
14
  COUNTRY_HEADER = 'HTTP_CF_IPCOUNTRY'
15
15
  CITY_HEADER = 'HTTP_CF_IPCITY'
16
+ REGION_HEADER = 'HTTP_CF_REGION'
17
+ REGION_CODE_HEADER = 'HTTP_CF_REGION_CODE'
18
+ LATITUDE_HEADER = 'HTTP_CF_IPLATITUDE'
19
+ LONGITUDE_HEADER = 'HTTP_CF_IPLONGITUDE'
20
+ TIMEZONE_HEADER = 'HTTP_CF_TIMEZONE'
21
+ CONTINENT_HEADER = 'HTTP_CF_IPCONTINENT'
22
+ METRO_CODE_HEADER = 'HTTP_CF_METRO_CODE'
23
+ POSTAL_CODE_HEADER = 'HTTP_CF_POSTAL_CODE'
16
24
 
17
25
  # Special Cloudflare country codes
18
26
  UNKNOWN_CODE = 'XX'
@@ -43,7 +51,17 @@ module Trackdown
43
51
  city = extract_city(request)
44
52
  flag_emoji = get_emoji_flag(country_code)
45
53
 
46
- LocationResult.new(country_code, country_name, city, flag_emoji)
54
+ LocationResult.new(
55
+ country_code, country_name, city, flag_emoji,
56
+ region: extract_header(request, REGION_HEADER),
57
+ region_code: extract_header(request, REGION_CODE_HEADER),
58
+ continent: extract_header(request, CONTINENT_HEADER),
59
+ timezone: extract_header(request, TIMEZONE_HEADER),
60
+ latitude: parse_coordinate(request.env[LATITUDE_HEADER]),
61
+ longitude: parse_coordinate(request.env[LONGITUDE_HEADER]),
62
+ postal_code: extract_header(request, POSTAL_CODE_HEADER),
63
+ metro_code: extract_header(request, METRO_CODE_HEADER)
64
+ )
47
65
  end
48
66
 
49
67
  private
@@ -64,6 +82,21 @@ module Trackdown
64
82
 
65
83
  city
66
84
  end
85
+
86
+ def extract_header(request, header)
87
+ value = request.env[header]
88
+ return nil if value.nil? || value.empty?
89
+
90
+ value
91
+ end
92
+
93
+ def parse_coordinate(value)
94
+ return nil if value.nil? || value.empty?
95
+
96
+ Float(value)
97
+ rescue ArgumentError, TypeError
98
+ nil
99
+ end
67
100
  end
68
101
  end
69
102
  end
@@ -48,7 +48,17 @@ module Trackdown
48
48
  city = extract_city(record)
49
49
  flag_emoji = get_emoji_flag(country_code)
50
50
 
51
- LocationResult.new(country_code, country_name, city, flag_emoji)
51
+ LocationResult.new(
52
+ country_code, country_name, city, flag_emoji,
53
+ region: extract_region(record),
54
+ region_code: record&.dig('subdivisions', 0, 'iso_code'),
55
+ continent: record&.dig('continent', 'code'),
56
+ timezone: record&.dig('location', 'time_zone'),
57
+ latitude: record&.dig('location', 'latitude'),
58
+ longitude: record&.dig('location', 'longitude'),
59
+ postal_code: record&.dig('postal', 'code'),
60
+ metro_code: record&.dig('location', 'metro_code')&.to_s
61
+ )
52
62
  end
53
63
 
54
64
  private
@@ -103,6 +113,11 @@ module Trackdown
103
113
  (record&.dig('city', 'names')&.values&.first) ||
104
114
  'Unknown'
105
115
  end
116
+
117
+ def extract_region(record)
118
+ record&.dig('subdivisions', 0, 'names', 'en') ||
119
+ record&.dig('subdivisions', 0, 'names')&.values&.first
120
+ end
106
121
  end
107
122
  end
108
123
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Trackdown
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trackdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.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-01-02 00:00:00.000000000 Z
10
+ date: 2026-02-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: countries
@@ -23,129 +23,19 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '7.0'
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '13.0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '13.0'
40
- - !ruby/object:Gem::Dependency
41
- name: rubocop
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '1.21'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.21'
54
- - !ruby/object:Gem::Dependency
55
- name: minitest
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '5.25'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '5.25'
68
- - !ruby/object:Gem::Dependency
69
- name: mocha
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '2.0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '2.0'
82
- - !ruby/object:Gem::Dependency
83
- name: simplecov
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '0.22'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '0.22'
96
- - !ruby/object:Gem::Dependency
97
- name: webmock
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '3.0'
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '3.0'
110
- - !ruby/object:Gem::Dependency
111
- name: maxmind-db
112
- requirement: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - "~>"
115
- - !ruby/object:Gem::Version
116
- version: '1.2'
117
- type: :development
118
- prerelease: false
119
- version_requirements: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '1.2'
124
- - !ruby/object:Gem::Dependency
125
- name: connection_pool
126
- requirement: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '2.4'
131
- type: :development
132
- prerelease: false
133
- version_requirements: !ruby/object:Gem::Requirement
134
- requirements:
135
- - - "~>"
136
- - !ruby/object:Gem::Version
137
- version: '2.4'
138
- description: Trackdown is a Ruby gem that easily allows you to geolocate IP addresses.
139
- It works out of the box with Cloudflare headers if you're using it, or you can use
140
- MaxMind (BYOK). The gem offers a clean API for Rails applications to fetch country,
141
- city, and emoji flag information for any IP address. Supports Cloudflare headers
142
- (instant, zero overhead) and MaxMind GeoLite2 database (offline capable).
26
+ description: Trackdown is a Ruby gem that allows you to geolocate IP addresses easily.
27
+ It works out of the box with Cloudflare headers if your Rails app is behind it;
28
+ or you can also use MaxMind databases (BYOK). The gem offers a clean API for Rails
29
+ applications to fetch country, city, emoji flag, region, continent, postal code,
30
+ latitude, longitude and other GeoIP information for any IP address. Supports Cloudflare
31
+ headers, and MaxMind GeoLite2 databases (offline capable).
143
32
  email:
144
33
  - rubygems@rameerez.com
145
34
  executables: []
146
35
  extensions: []
147
36
  extra_rdoc_files: []
148
37
  files:
38
+ - ".simplecov"
149
39
  - AGENTS.md
150
40
  - CHANGELOG.md
151
41
  - CLAUDE.md
@@ -173,6 +63,7 @@ licenses:
173
63
  - MIT
174
64
  metadata:
175
65
  allowed_push_host: https://rubygems.org
66
+ rubygems_mfa_required: 'true'
176
67
  homepage_uri: https://github.com/rameerez/trackdown
177
68
  source_code_uri: https://github.com/rameerez/trackdown
178
69
  changelog_uri: https://github.com/rameerez/trackdown/blob/main/CHANGELOG.md