Class: PostalCode

Inherits:
ApplicationRecord show all
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 nil updated_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

Has many collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#codeObject (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?).

Parameters:

  • code (String)

    postal code to look up

  • country_iso3 (String) (defaults to: 'USA')

    ISO3 country code for the provider lookup

Returns:



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?

Deprecated.

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.

Parameters:

  • post_code (String)

    postal/ZIP code (US numeric-prefixed or Canadian)

Returns:

  • (String, nil)

    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.

Parameters:

  • code (String)

    postal code to refresh

  • country_iso3 (String) (defaults to: 'USA')

    ISO3 country code for the provider lookup

Returns:



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.

Returns:

  • (Boolean)


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.

Parameters:

  • raw_code (String)
  • country_iso3 (String) (defaults to: 'USA')

    "USA" or "CAN"

  • refresh (Boolean) (defaults to: false)

    allow a live provider self-heal on miss/stale (US)

Returns:

  • (String, nil)


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_attributesHash

Attribute hash for building an Address in this postal code's city/state.

Returns:

  • (Hash)


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

#addressesActiveRecord::Relation<Address>

Addresses carrying this postal code.

Returns:

  • (ActiveRecord::Relation<Address>)

See Also:



30
# File 'app/models/postal_code.rb', line 30

has_many :addresses, foreign_key: :zip, primary_key: :code

#citiesActiveRecord::Relation<City>

Cities this postal code covers.

Returns:

  • (ActiveRecord::Relation<City>)

See Also:



32
# File 'app/models/postal_code.rb', line 32

has_many :cities, foreign_key: :code, primary_key: :code

#postal_code_statisticsActiveRecord::Relation<PostalCodeStatistic>

Usage statistics gathered for this postal code.

Returns:

See Also:



34
# File 'app/models/postal_code.rb', line 34

has_many :postal_code_statistics, foreign_key: :code, primary_key: :code

#stateState

State/province this postal code belongs to.

Returns:

See Also:



36
# File 'app/models/postal_code.rb', line 36

belongs_to :state, foreign_key: :state_code, primary_key: :code, optional: true