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) },
  #                lengths: { line1: 100, postal_code: 5..10 }, allow_blank: %i[state],
  #                normalize_country: true,  # "Canada"/"CAN" -> "CA"
  #                if: :on_addresses?        # Rails-style condition gating the validations
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
LABEL =

Prefix for the ArgumentErrors raised during configuration.

"ConcernsOnRails::Models::Addressable".freeze

Instance Method Summary collapse

Instance Method Details

#address_attributesObject

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



226
227
228
229
230
231
# File 'lib/concerns_on_rails/models/addressable.rb', line 226

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)


219
220
221
222
223
# File 'lib/concerns_on_rails/models/addressable.rb', line 219

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).



209
210
211
# File 'lib/concerns_on_rails/models/addressable.rb', line 209

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)


214
215
216
# File 'lib/concerns_on_rails/models/addressable.rb', line 214

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.



204
205
206
# File 'lib/concerns_on_rails/models/addressable.rb', line 204

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

#normalize_addressObject

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



180
181
182
183
184
185
186
187
188
# File 'lib/concerns_on_rails/models/addressable.rb', line 180

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 ———————————————————–



192
193
194
195
196
197
198
199
# File 'lib/concerns_on_rails/models/addressable.rb', line 192

def validate_address
  validate_required_parts
  validate_lengths
  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