Class: PostalCode
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- PostalCode
- Defined in:
- app/models/postal_code.rb
Overview
== Schema Information
Table name: postal_codes
Database name: primary
id :integer not null, primary key
area_codes :string(200)
code :string(10) not null
dst :boolean
latitude :decimal(15, 10)
longitude :decimal(15, 10)
msa :string(50)
msa_fip :integer
postal_type :string(1)
state_code :string(2)
utc :integer
Indexes
idx_postal_codes_trgm (code) USING gist
index_postal_codes_on_code (code) UNIQUE
index_postal_codes_on_state_code (state_code)
Constant Summary collapse
- CANADA_POSTAL_CODE_FIRST_LETTER_TO_PROVINCE_CODE =
Canada postal code first letter to province code.
{ "A" => "NL", "B" => "NS", "C" => "PE", "E" => "NB", "G" => "QC", "H" => "QC", "J" => "QC", "K" => "ON", "L" => "ON", "M" => "ON", "P" => "ON", "R" => "MB", "S" => "SK", "T" => "AB", "V" => "BC", "X" => "NT", "Y" => "YT" }.freeze
- STALE_AFTER =
A cached row is trusted for this long before a self-healing refresh. Rows
with a nilupdated_at(legacy 2011 import, not yet GeoNames-refreshed) are
treated as usable — ZIP→state is stable — so we don't stampede the provider
before the bulk refresh has stamped them. 1.year
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#code ⇒ Object
readonly
Validates the postal code is present and unique.
Has many collapse
-
#addresses ⇒ ActiveRecord::Relation<Address>
Addresses carrying this postal code.
-
#cities ⇒ ActiveRecord::Relation<City>
Cities this postal code covers.
-
#postal_code_statistics ⇒ ActiveRecord::Relation<PostalCodeStatistic>
Usage statistics gathered for this postal code.
Belongs to collapse
-
#state ⇒ State
State/province this postal code belongs to.
Class Method Summary collapse
-
.fresh_record(code, country_iso3: 'USA') ⇒ PostalCode?
Returns the cached row for +code+, transparently refreshing it from the configured address-validation provider on a miss or when it has aged past STALE_AFTER.
-
.get_state_code_from_postal_code(post_code) ⇒ String?
deprecated
Deprecated.
Prefer PostalCode.state_for. Kept for existing callers (Opportunity
installation-ZIP derivation), which opt into self-heal so stale/missing
ZIPs are refreshed as they're encountered. -
.refresh_from_provider(code, country_iso3: 'USA') ⇒ PostalCode?
Upserts +code+'s state from a live provider lookup.
-
.self_heal_enabled? ⇒ Boolean
Whether on-read self-heal may call the address-validation provider.
-
.state_for(raw_code, country_iso3: 'USA', refresh: false) ⇒ String?
Canonical state/province code for a postal code.
Instance Method Summary collapse
-
#address_attributes ⇒ Hash
Attribute hash for building an Address in this postal code's city/state.
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#code ⇒ Object (readonly)
Validates the postal code is present and unique.
Validations:
28 |
# File 'app/models/postal_code.rb', line 28 validates :code, presence: true, uniqueness: true |
Class Method Details
.fresh_record(code, country_iso3: 'USA') ⇒ PostalCode?
Returns the cached row for +code+, transparently refreshing it from the
configured address-validation provider on a miss or when it has aged past
STALE_AFTER. A present-but-untimestamped row is returned as-is. Self-heal
is skipped entirely when disabled (see self_heal_enabled?).
113 114 115 116 117 118 119 |
# File 'app/models/postal_code.rb', line 113 def self.fresh_record(code, country_iso3: 'USA') rec = find_by(code: code) return rec if rec && (rec.updated_at.nil? || rec.updated_at > STALE_AFTER.ago) return rec unless self_heal_enabled? refresh_from_provider(code, country_iso3: country_iso3) || rec end |
.get_state_code_from_postal_code(post_code) ⇒ String?
Prefer state_for. Kept for existing callers (Opportunity
installation-ZIP derivation), which opt into self-heal so stale/missing
ZIPs are refreshed as they're encountered.
Returns state/province code.
155 156 157 158 159 160 161 162 163 |
# File 'app/models/postal_code.rb', line 155 def self.get_state_code_from_postal_code(post_code) return if post_code.blank? if post_code.to_s.strip.match?(/\A\d/) state_for(post_code, country_iso3: 'USA', refresh: true) else state_for(post_code, country_iso3: 'CAN') end end |
.refresh_from_provider(code, country_iso3: 'USA') ⇒ PostalCode?
Upserts +code+'s state from a live provider lookup. Best-effort: returns nil
(leaving any stale row in place) when the provider can't resolve it.
136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/models/postal_code.rb', line 136 def self.refresh_from_provider(code, country_iso3: 'USA') candidate = AddressValidation.lookup_zip(code, country_iso3: country_iso3) return if candidate.nil? || candidate.state_code.blank? rec = find_or_initialize_by(code: code) rec.state_code = candidate.state_code rec.updated_at = Time.current # advance the TTL even when the state is unchanged rec.save! rec rescue StandardError => e ErrorReporting.warning(e, context: 'PostalCode.refresh_from_provider', code: code) nil end |
.self_heal_enabled? ⇒ Boolean
Whether on-read self-heal may call the address-validation provider. On by
default; disabled in test (config.x.address_validation.self_heal = false)
so suites stay hermetic and network-free.
126 127 128 |
# File 'app/models/postal_code.rb', line 126 def self.self_heal_enabled? Rails.application.config.x.address_validation.self_heal != false end |
.state_for(raw_code, country_iso3: 'USA', refresh: false) ⇒ String?
Canonical state/province code for a postal code.
US: PostalCode cache. With +refresh: true+ it self-heals a cache miss or a
stale (timestamped + aged) row via AddressValidation.lookup_zip; the
default is a pure local lookup so hot/cheap callers (e.g.
Address#zip_state_consistent?, the TaxJar guard) never trigger a provider
call. CA: cache when present, else the FSA first-letter→province fallback.
Any other country returns nil — only US/CA have a ZIP↔state mapping here, so
a numeric foreign postal code must not fall through and match a US ZIP row.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'app/models/postal_code.rb', line 89 def self.state_for(raw_code, country_iso3: 'USA', refresh: false) return if raw_code.blank? country = country_iso3.to_s.upcase if country == 'CAN' code = raw_code.to_s.strip.upcase.delete(' ') # Full code, then FSA (first 3 — the free GeoNames CA grain), then the # first-letter→province fallback. (find_by(code: code) || find_by(code: code[0, 3]))&.state_code.presence || CANADA_POSTAL_CODE_FIRST_LETTER_TO_PROVINCE_CODE[code[0]] elsif country == 'USA' code = raw_code.to_s.strip[0, 5] (refresh ? fresh_record(code, country_iso3: country) : find_by(code: code))&.state_code end end |
Instance Method Details
#address_attributes ⇒ Hash
Attribute hash for building an Address in this postal code's city/state.
61 62 63 64 65 66 67 |
# File 'app/models/postal_code.rb', line 61 def address_attributes { street1: 'Unknown', zip: code, state_code: state_code, city: begin cities.first.city_name rescue StandardError nil end, country_iso3: state.country_iso3 } end |
#addresses ⇒ ActiveRecord::Relation<Address>
Addresses carrying this postal code.
30 |
# File 'app/models/postal_code.rb', line 30 has_many :addresses, foreign_key: :zip, primary_key: :code |
#cities ⇒ ActiveRecord::Relation<City>
Cities this postal code covers.
32 |
# File 'app/models/postal_code.rb', line 32 has_many :cities, foreign_key: :code, primary_key: :code |
#postal_code_statistics ⇒ ActiveRecord::Relation<PostalCodeStatistic>
Usage statistics gathered for this postal code.
34 |
# File 'app/models/postal_code.rb', line 34 has_many :postal_code_statistics, foreign_key: :code, primary_key: :code |
#state ⇒ State
State/province this postal code belongs to.
36 |
# File 'app/models/postal_code.rb', line 36 belongs_to :state, foreign_key: :state_code, primary_key: :code, optional: true |