Class: Trackdown::LocationResult
- Inherits:
-
Object
- Object
- Trackdown::LocationResult
- Defined in:
- lib/trackdown/location_result.rb
Overview
Where an IP address probably is — and an honest account of how we know.
Every result carries its own provenance: which provider answered, from which source, when it answered, how precise that provider says the answer is, and whether the host vouched for the path the request arrived through.
Nothing here is guessed. Every field is either what the provider said or
something derived from it by a documented rule — never a plausible-looking
placeholder. A field the answering provider cannot supply is nil, so a
caller can always tell "the provider said no" apart from "it never said".
GeoIP is an estimate. It never proves that a person or a device was in a place. MaxMind documents those limits exactly: https://support.maxmind.com/knowledge-base/articles/maxmind-geolocation-accuracy
Constant Summary collapse
- UNKNOWN =
What providers have always returned for a value they don't have. Kept for backwards compatibility — new code should ask #available? and read the nil-able fields instead of parsing display strings.
'Unknown'- UNKNOWN_FLAG =
'🏳️'- UNAVAILABLE_REASONS =
Why a lookup came back with no location. Stable and machine-readable: these symbols are part of the public API and are never translated.
%i[ no_provider_available address_not_found provider_returned_unknown_country provider_data_incomplete ].freeze
- SOURCE_TRUSTS =
How much the host vouches for the source of a request-backed result. :host_verified only ever comes from the host's own verifier — see Trackdown::Configuration#verify_request_came_through_trusted_cloudflare_path_with and #verify_request_came_through_trusted_cloudfront_path_with.
%i[unverified host_verified].freeze
- LOCATION_FIELDS =
Where the IP is.
%i[ country_code country_name city flag_emoji region region_code continent timezone latitude longitude postal_code metro_code ].freeze
- PROVENANCE_FIELDS =
How we know, and how sure we are.
%i[ provider_name provider_source source_trust resolved_at available estimated unavailable_reason accuracy_radius_in_kilometers accuracy_radius_confidence_percentage database_build_epoch database_built_at database_sha256 ].freeze
- FIELDS =
Every field #to_h can emit, in the order it emits them.
(LOCATION_FIELDS + PROVENANCE_FIELDS + %i[country_info]).freeze
- DEFAULT_FIELDS =
What a no-argument #to_h returns: the exact shape Trackdown returned before provenance existed, kept that way as an API compatibility guarantee. Ask for the rest with
include_provenance: trueor name it inonly:. (LOCATION_FIELDS + %i[country_info]).freeze
Instance Attribute Summary collapse
-
#accuracy_radius_confidence_percentage ⇒ Object
readonly
Returns the value of attribute accuracy_radius_confidence_percentage.
-
#accuracy_radius_in_kilometers ⇒ Object
(also: #accuracy_radius_km)
readonly
Returns the value of attribute accuracy_radius_in_kilometers.
-
#city ⇒ Object
readonly
Returns the value of attribute city.
-
#continent ⇒ Object
readonly
Returns the value of attribute continent.
-
#country_code ⇒ Object
readonly
Returns the value of attribute country_code.
-
#country_name ⇒ Object
(also: #country)
readonly
Returns the value of attribute country_name.
-
#database_build_epoch ⇒ Object
readonly
Returns the value of attribute database_build_epoch.
-
#flag_emoji ⇒ Object
(also: #emoji, #emoji_flag, #country_flag)
readonly
Returns the value of attribute flag_emoji.
-
#latitude ⇒ Object
readonly
Returns the value of attribute latitude.
-
#longitude ⇒ Object
readonly
Returns the value of attribute longitude.
-
#metro_code ⇒ Object
readonly
Returns the value of attribute metro_code.
-
#postal_code ⇒ Object
readonly
Returns the value of attribute postal_code.
-
#provider_name ⇒ Object
(also: #provider)
readonly
Returns the value of attribute provider_name.
-
#provider_source ⇒ Object
readonly
Returns the value of attribute provider_source.
-
#region ⇒ Object
readonly
Returns the value of attribute region.
-
#region_code ⇒ Object
readonly
Returns the value of attribute region_code.
-
#resolved_at ⇒ Object
readonly
Returns the value of attribute resolved_at.
-
#source_trust ⇒ Object
readonly
Returns the value of attribute source_trust.
-
#timezone ⇒ Object
readonly
Returns the value of attribute timezone.
-
#unavailable_reason ⇒ Object
readonly
Returns the value of attribute unavailable_reason.
Class Method Summary collapse
-
.unavailable(reason, **provenance) ⇒ Object
A lookup that resolved nothing, and says why.
Instance Method Summary collapse
-
#available? ⇒ Boolean
Did we actually resolve a location?.
- #country_info ⇒ Object
-
#database_built_at ⇒ Object
When the database that answered was built.
-
#database_sha256 ⇒ Object
The MaxMind database digest, computed the first time it's asked for.
-
#estimated? ⇒ Boolean
True whenever a provider resolved any location data, including a partial result that has a city or coordinates but no country.
-
#initialize(country_code, country_name, city, flag_emoji, region: nil, region_code: nil, continent: nil, timezone: nil, latitude: nil, longitude: nil, postal_code: nil, metro_code: nil, provider_name: nil, provider_source: nil, source_trust: nil, resolved_at: nil, unavailable_reason: nil, accuracy_radius_in_kilometers: nil, accuracy_radius_confidence_percentage: nil, database_build_epoch: nil, database_sha256: nil) ⇒ LocationResult
constructor
Every keyword is optional, so a provider only supplies what it actually knows.
-
#source_was_verified_by_host? ⇒ Boolean
(also: #host_verified?)
True only when the host's own verifier vouched for this request's path.
-
#to_h(only: nil, include_country_info: true, include_provenance: false) ⇒ Object
The whole result as a hash, or exactly the fields you name.
- #unavailable? ⇒ Boolean
Constructor Details
#initialize(country_code, country_name, city, flag_emoji, region: nil, region_code: nil, continent: nil, timezone: nil, latitude: nil, longitude: nil, postal_code: nil, metro_code: nil, provider_name: nil, provider_source: nil, source_trust: nil, resolved_at: nil, unavailable_reason: nil, accuracy_radius_in_kilometers: nil, accuracy_radius_confidence_percentage: nil, database_build_epoch: nil, database_sha256: nil) ⇒ LocationResult
Every keyword is optional, so a provider only supplies what it actually knows.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/trackdown/location_result.rb', line 84 def initialize(country_code, country_name, city, flag_emoji, region: nil, region_code: nil, continent: nil, timezone: nil, latitude: nil, longitude: nil, postal_code: nil, metro_code: nil, provider_name: nil, provider_source: nil, source_trust: nil, resolved_at: nil, unavailable_reason: nil, accuracy_radius_in_kilometers: nil, accuracy_radius_confidence_percentage: nil, database_build_epoch: nil, database_sha256: nil) @country_code = country_code @country_name = country_name @city = city @flag_emoji = flag_emoji @region = region @region_code = region_code @continent = continent @timezone = timezone @latitude = latitude @longitude = longitude @postal_code = postal_code @metro_code = metro_code @provider_name = provider_name @provider_source = provider_source @source_trust = validate!(source_trust, SOURCE_TRUSTS, 'source trust') @resolved_at = resolved_at || Time.now.utc @unavailable_reason = validate!(unavailable_reason, UNAVAILABLE_REASONS, 'unavailable reason') || (:provider_data_incomplete if blank?(country_code)) @accuracy_radius_in_kilometers = accuracy_radius_in_kilometers @accuracy_radius_confidence_percentage = accuracy_radius_confidence_percentage @database_build_epoch = database_build_epoch @database_sha256 = database_sha256 end |
Instance Attribute Details
#accuracy_radius_confidence_percentage ⇒ Object (readonly)
Returns the value of attribute accuracy_radius_confidence_percentage.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def accuracy_radius_confidence_percentage @accuracy_radius_confidence_percentage end |
#accuracy_radius_in_kilometers ⇒ Object (readonly) Also known as: accuracy_radius_km
Returns the value of attribute accuracy_radius_in_kilometers.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def accuracy_radius_in_kilometers @accuracy_radius_in_kilometers end |
#city ⇒ Object (readonly)
Returns the value of attribute city.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def city @city end |
#continent ⇒ Object (readonly)
Returns the value of attribute continent.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def continent @continent end |
#country_code ⇒ Object (readonly)
Returns the value of attribute country_code.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def country_code @country_code end |
#country_name ⇒ Object (readonly) Also known as: country
Returns the value of attribute country_name.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def country_name @country_name end |
#database_build_epoch ⇒ Object (readonly)
Returns the value of attribute database_build_epoch.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def database_build_epoch @database_build_epoch end |
#flag_emoji ⇒ Object (readonly) Also known as: emoji, emoji_flag, country_flag
Returns the value of attribute flag_emoji.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def flag_emoji @flag_emoji end |
#latitude ⇒ Object (readonly)
Returns the value of attribute latitude.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def latitude @latitude end |
#longitude ⇒ Object (readonly)
Returns the value of attribute longitude.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def longitude @longitude end |
#metro_code ⇒ Object (readonly)
Returns the value of attribute metro_code.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def metro_code @metro_code end |
#postal_code ⇒ Object (readonly)
Returns the value of attribute postal_code.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def postal_code @postal_code end |
#provider_name ⇒ Object (readonly) Also known as: provider
Returns the value of attribute provider_name.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def provider_name @provider_name end |
#provider_source ⇒ Object (readonly)
Returns the value of attribute provider_source.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def provider_source @provider_source end |
#region ⇒ Object (readonly)
Returns the value of attribute region.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def region @region end |
#region_code ⇒ Object (readonly)
Returns the value of attribute region_code.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def region_code @region_code end |
#resolved_at ⇒ Object (readonly)
Returns the value of attribute resolved_at.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def resolved_at @resolved_at end |
#source_trust ⇒ Object (readonly)
Returns the value of attribute source_trust.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def source_trust @source_trust end |
#timezone ⇒ Object (readonly)
Returns the value of attribute timezone.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def timezone @timezone end |
#unavailable_reason ⇒ Object (readonly)
Returns the value of attribute unavailable_reason.
70 71 72 |
# File 'lib/trackdown/location_result.rb', line 70 def unavailable_reason @unavailable_reason end |
Class Method Details
.unavailable(reason, **provenance) ⇒ Object
A lookup that resolved nothing, and says why.
LocationResult.unavailable(:address_not_found, provider_name: :maxmind)
121 122 123 124 125 126 127 128 129 130 |
# File 'lib/trackdown/location_result.rb', line 121 def self.unavailable(reason, **provenance) if reason.nil? raise ArgumentError, "An unavailable result has to say why. Must be one of: #{UNAVAILABLE_REASONS.join(', ')}" end if provenance.key?(:unavailable_reason) raise ArgumentError, 'Pass the unavailable reason once, as the first argument to LocationResult.unavailable' end new(nil, UNKNOWN, UNKNOWN, UNKNOWN_FLAG, unavailable_reason: reason, **provenance) end |
Instance Method Details
#available? ⇒ Boolean
Did we actually resolve a location?
140 141 142 |
# File 'lib/trackdown/location_result.rb', line 140 def available? @unavailable_reason.nil? end |
#country_info ⇒ Object
174 175 176 177 178 |
# File 'lib/trackdown/location_result.rb', line 174 def country_info return nil unless country_code ISO3166::Country.new(country_code) end |
#database_built_at ⇒ Object
When the database that answered was built.
170 171 172 |
# File 'lib/trackdown/location_result.rb', line 170 def database_built_at Time.at(@database_build_epoch).utc if @database_build_epoch.is_a?(Numeric) end |
#database_sha256 ⇒ Object
The MaxMind database digest, computed the first time it's asked for.
164 165 166 167 |
# File 'lib/trackdown/location_result.rb', line 164 def database_sha256 @database_sha256 = @database_sha256.call if @database_sha256.respond_to?(:call) @database_sha256 end |
#estimated? ⇒ Boolean
True whenever a provider resolved any location data, including a partial result that has a city or coordinates but no country. Availability answers "could we name a country?"; estimated answers "is any returned location an inference?" Those are deliberately independent questions.
152 153 154 |
# File 'lib/trackdown/location_result.rb', line 152 def estimated? available? || partial_location_estimate? end |
#source_was_verified_by_host? ⇒ Boolean Also known as: host_verified?
True only when the host's own verifier vouched for this request's path. Header presence alone never gets you here.
158 159 160 |
# File 'lib/trackdown/location_result.rb', line 158 def source_was_verified_by_host? @source_trust == :host_verified end |
#to_h(only: nil, include_country_info: true, include_provenance: false) ⇒ Object
The whole result as a hash, or exactly the fields you name.
result.to_h
result.to_h(only: %i[country_code city latitude longitude provider_name])
result.to_h(include_country_info: false)
result.to_h(include_provenance: true)
198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/trackdown/location_result.rb', line 198 def to_h(only: nil, include_country_info: true, include_provenance: false) fields = if only requested_fields(only) elsif include_provenance provenance_fields(include_country_info: include_country_info) elsif include_country_info DEFAULT_FIELDS else DEFAULT_FIELDS - %i[country_info] end fields.to_h { |field| [field, value_of(field)] } end |
#unavailable? ⇒ Boolean
144 145 146 |
# File 'lib/trackdown/location_result.rb', line 144 def unavailable? !available? end |