Module: ConcernsOnRails::Models::Addressable

Extended by:
ActiveSupport::Concern
Defined in:
lib/concerns_on_rails/models/addressable.rb

Overview

Declarative normalization + format validation for a postal address spread across several columns. One macro wires up whitespace cleanup, postal-code and ISO country-code checks, required-part presence, and a ‘full_address` helper — no external geocoding service required.

class Location < ApplicationRecord
  include ConcernsOnRails::Addressable

  addressable_by                      # standard line1/line2/city/state/postal_code/country columns
  # addressable_by line1: :street, postal_code: :zip, country: :country_code,
  #                required: %i[line1 city postal_code], default_country: "GB",
  #                validate_state: true, verify_with: ->(rec) { Usps.verify(rec) }
end

Scope is format/structure only — it checks shape, not real-world deliverability. Layer a real verifier on via ‘verify_with:`. Relates to the per-field Normalizable concern.

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

DEFAULT_FIELDS =

Canonical address part => default column name.

{
  line1: :line1, line2: :line2, city: :city,
  state: :state, postal_code: :postal_code, country: :country
}.freeze
DEFAULT_REQUIRED =

Parts required by default (each must map to an existing column).

%i[line1 city postal_code country].freeze

Instance Method Summary collapse

Instance Method Details

#address_attributesObject

‘{ part => value }` for the present parts (handy for serializers / verifiers).



131
132
133
134
135
136
# File 'lib/concerns_on_rails/models/addressable.rb', line 131

def address_attributes
  self.class.addressable_fields.each_with_object({}) do |(part, column), acc|
    value = self[column]
    acc[part] = value if value.present?
  end
end

#address_complete?Boolean

True when every required part has a value (presence only, no format check).

Returns:

  • (Boolean)


124
125
126
127
128
# File 'lib/concerns_on_rails/models/addressable.rb', line 124

def address_complete?
  self.class.addressable_required.all? do |part|
    (column = self.class.addressable_fields[part]) && self[column].present?
  end
end

#address_linesObject

The present parts as an ordered array (handy for multi-line rendering).



114
115
116
# File 'lib/concerns_on_rails/models/addressable.rb', line 114

def address_lines
  ordered_parts.filter_map { |part| self[self.class.addressable_fields[part]].presence }
end

#address_present?Boolean

True when any configured part has a value.

Returns:

  • (Boolean)


119
120
121
# File 'lib/concerns_on_rails/models/addressable.rb', line 119

def address_present?
  ordered_parts.any? { |part| self[self.class.addressable_fields[part]].present? }
end

#full_address(separator: ", ") ⇒ Object

The present parts joined into a single line, in canonical order.



109
110
111
# File 'lib/concerns_on_rails/models/addressable.rb', line 109

def full_address(separator: ", ")
  address_lines.join(separator)
end

#normalize_addressObject

— Normalization (before_validation) ————————————



86
87
88
89
90
91
92
93
94
# File 'lib/concerns_on_rails/models/addressable.rb', line 86

def normalize_address
  country = resolved_country
  self.class.addressable_fields.each do |part, column|
    value = self[column]
    next unless value.is_a?(String)

    self[column] = normalize_part(part, country, value)
  end
end

#validate_addressObject

— Validation ———————————————————–



98
99
100
101
102
103
104
# File 'lib/concerns_on_rails/models/addressable.rb', line 98

def validate_address
  validate_required_parts
  validate_country_code
  validate_postal_code
  validate_state_code if self.class.addressable_validate_state
  run_address_verifier if self.class.addressable_verifier && errors.empty?
end