address_concern 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,81 +1,329 @@
1
- module AddressConcern::Address
1
+ require_relative '../core_extensions/hash/reorder'
2
+ using Hash::Reorder
3
+
4
+ require_relative '../core_extensions/string/cleanlines'
5
+ using String::Cleanlines
6
+
7
+ require_relative 'inspect_base'
8
+ require_relative 'attributes_slice'
9
+
10
+ module AddressConcern
11
+ module Address
12
+ module Base
13
+ extend ActiveSupport::Concern
14
+
15
+ # These (Base) class methods are added to ActiveRecord::Base so that they will be available from _any_
16
+ # model class. Unlike the main AddressConcern::Address methods which are only included _after_
17
+ # you call acts_as_address on a model.
18
+ module ClassMethods
19
+ attr_reader :acts_as_address_config
20
+ def acts_as_address(**options)
21
+ # Have to use yield_self(&not_null) intead of presence because NullColumn.present? => true.
22
+ not_null = ->(column) {
23
+ column.type.nil? ? nil : column
24
+ }
25
+ options = options.deep_symbolize_keys
26
+ default_config = {
27
+ state: {
28
+ #normalize: false,
29
+ #validate: false,
30
+
31
+ code_attribute: column_for_attribute(:state_code).yield_self(&not_null)&.name ||
32
+ (column_for_attribute(:state).yield_self(&not_null)&.name unless options.dig(:state, :name_attribute).to_s == 'state'),
33
+
34
+ name_attribute: column_for_attribute(:state_name).yield_self(&not_null)&.name ||
35
+ (column_for_attribute(:state).yield_self(&not_null)&.name unless options.dig(:state, :code_attribute).to_s == 'state'),
36
+
37
+ on_unknown: ->(value, name_or_code) { },
38
+ },
39
+
40
+ country: {
41
+ #normalize: false,
42
+ #validate: false,
43
+
44
+ # By default, code (same as alpha_2_code) will be used
45
+ carmen_code: :code, # or alpha_2_code, alpha_3_code, :numeric_code
46
+
47
+ code_attribute: column_for_attribute(:country_code).yield_self(&not_null)&.name ||
48
+ (column_for_attribute(:country).yield_self(&not_null)&.name unless options.dig(:country, :name_attribute).to_s == 'country'),
49
+
50
+ name_attribute: column_for_attribute(:country_name).yield_self(&not_null)&.name ||
51
+ (column_for_attribute(:country).yield_self(&not_null)&.name unless options.dig(:country, :code_attribute).to_s == 'country'),
52
+
53
+ on_unknown: ->(value, name_or_code) { },
54
+ },
55
+
56
+ address: {
57
+ #normalize: false,
58
+ #validate: false,
59
+
60
+ # Try to auto-detect address columns
61
+ attributes: column_names.grep(/address$|^address_\d$/),
62
+ }
63
+ }
64
+ @acts_as_address_config = config = {
65
+ **default_config
66
+ }.deep_merge(options)
67
+
68
+ [:state, :country].each do |group|
69
+ # Can't use the same column for code and name, so if it would be the same (by default or
70
+ # otherwise), let it be used for name only instead.
71
+ if config[group][:code_attribute] == config[group][:name_attribute]
72
+ config[group].delete(:code_attribute)
73
+ end
74
+ end
75
+
76
+ include ::AddressConcern::Address
77
+ end
78
+
79
+ def belongs_to_addressable(**options)
80
+ belongs_to :addressable, polymorphic: true, touch: true, optional: true, **options
81
+ end
82
+ end
83
+ end
84
+
85
+ include InspectBase
86
+ include AttributesSlice
87
+
2
88
  extend ActiveSupport::Concern
3
89
  included do
90
+ #═══════════════════════════════════════════════════════════════════════════════════════════════
91
+ # Config
92
+
93
+ delegate *[
94
+ :acts_as_address_config,
95
+ :country_config,
96
+ :state_config,
97
+ ], to: 'self.class'
98
+
99
+ class << self
100
+ #─────────────────────────────────────────────────────────────────────────────────────────────
101
+ def country_config
102
+ @acts_as_address_config[:country] || {}
103
+ end
104
+
105
+ # usually :code
106
+ def carmen_country_code
107
+ country_config[:carmen_code]
108
+ end
109
+
110
+ # usually :coded
111
+ def carmen_country_code_find_method
112
+ :"#{carmen_country_code}d"
113
+ end
114
+
115
+ # 'country' or similar
116
+ def country_name_attribute
117
+ country_config[:name_attribute]&.to_sym
118
+ end
119
+
120
+ def country_code_attribute
121
+ country_config[:code_attribute]&.to_sym
122
+ end
123
+
124
+ #─────────────────────────────────────────────────────────────────────────────────────────────
125
+
126
+ def state_config
127
+ @acts_as_address_config[:state] || {}
128
+ end
129
+
130
+ def carmen_state_code
131
+ state_config[:carmen_code]
132
+ end
133
+
134
+ def state_name_attribute
135
+ state_config[:name_attribute]&.to_sym
136
+ end
137
+
138
+ def state_code_attribute
139
+ state_config[:code_attribute]&.to_sym
140
+ end
141
+
142
+ #─────────────────────────────────────────────────────────────────────────────────────────────
143
+
144
+ def address_attr_config
145
+ @acts_as_address_config[:address] || {}
146
+ end
147
+
148
+ # TODO: rename to something different than the same name as #address_attributes, like
149
+ # street_address_attr_names
150
+ def address_attributes
151
+ Array(address_attr_config[:attributes]).map(&:to_sym)
152
+ end
153
+
154
+ # Address line 1
155
+ def address_attribute
156
+ address_attributes[0]
157
+ end
158
+
159
+ def multi_line_address?
160
+ address_attributes.size == 1 && (
161
+ column = column_for_attribute(address_attribute)
162
+ column.type == :text
163
+ )
164
+ end
165
+
166
+ #─────────────────────────────────────────────────────────────────────────────────────────────
167
+
168
+ # AKA configured_address_attributes
169
+ def address_attr_names
170
+ [
171
+ *address_attributes,
172
+ :city,
173
+ state_name_attribute,
174
+ state_code_attribute,
175
+ :postal_code,
176
+ country_name_attribute,
177
+ country_code_attribute,
178
+ ].compact.uniq
179
+ end
180
+ end
181
+
182
+ #═════════════════════════════════════════════════════════════════════════════════════════════════
183
+ # Customizable validation (to add?)
4
184
 
5
- #validates_presence_of :name
6
185
  #validates_presence_of :address
7
- #validates_presence_of :state, :if => :state_required?
186
+ #validates_presence_of :state, if: :state_required?
8
187
  #validates_presence_of :country
9
- #validates_format_of :phone, :with => /^[0-9\-\+ ]*$/
10
- #validates_format_of :email, :with => /^[^@]*@.*\.[^\.]*$/, :message => 'is invalid. Please enter an address in the format of you@company.com'
11
- #validates_presence_of :phone, :message => ' is required.'
12
188
 
13
- #-------------------------------------------------------------------------------------------------
14
- normalize_attributes :name, :city, :state, :postal_code, :country
15
- normalize_attribute :address, :with => [:cleanlines, :strip]
189
+ #═════════════════════════════════════════════════════════════════════════════════════════════════
190
+ # Attributes
16
191
 
17
- #-------------------------------------------------------------------------------------------------
18
- # Country code
192
+ def _assign_attributes(attributes)
193
+ attributes = attributes.symbolize_keys
194
+ attributes = reorder_language_attributes(attributes)
195
+ super(attributes)
196
+ end
19
197
 
20
- def country_alpha2=(code)
21
- if code.blank?
22
- write_attribute(:country, nil)
23
- write_attribute(:country_alpha2, nil)
24
- write_attribute(:country_alpha3, nil)
198
+ def self.country_aliases ; [:country_name, :country_code] ; end
199
+ def self.state_aliases ; [:state_name, :state_code] ; end
25
200
 
26
- elsif (country = Carmen::Country.alpha_2_coded(code))
27
- # Only set it if it's a recognized country code
28
- write_attribute(:country, country.name)
29
- write_attribute(:country_alpha2, code)
30
- end
201
+ # country needs to be assigned _before_ state for things to work as intended (can't look up
202
+ # state in state= unless we know which country it is for)
203
+ def reorder_language_attributes(attributes)
204
+ attributes.reorder(self.class.country_name_attribute, self.class.country_code_attribute, *self.class.country_aliases,
205
+ self.class. state_name_attribute, self.class. state_code_attribute, *self.class.state_aliases)
206
+ end
207
+
208
+ def address_attributes
209
+ attributes_slice(
210
+ *self.class.address_attr_names
211
+ )
31
212
  end
32
213
 
33
- # Aliases
34
- def country_code
35
- country_alpha2
214
+ #═════════════════════════════════════════════════════════════════════════════════════════════════
215
+
216
+ # TODO: automatically normalize if attribute_normalizer/normalizy gem is loaded? add a config option to opt out?
217
+ #normalize_attributes :city, :state, :postal_code, :country
218
+ #normalize_attribute *address_attributes, with: [:cleanlines, :strip]
219
+
220
+ #═════════════════════════════════════════════════════════════════════════════════════════════════
221
+ # Country & State (Carmen + custom)
222
+
223
+ # Some of these methods look up by either name or code
224
+
225
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
226
+ # find country
227
+
228
+ # Finds by name, falling back to finding by code.
229
+ def self.find_carmen_country(name)
230
+ return name if name.is_a? Carmen::Country
231
+
232
+ (
233
+ find_carmen_country_by_name(name) ||
234
+ find_carmen_country_by_code(name)
235
+ )
36
236
  end
37
- def country_code=(code)
38
- self.country_alpha2 = code
237
+ def self.find_carmen_country!(name)
238
+ find_carmen_country(name) or
239
+ raise "country #{name} not found"
39
240
  end
40
- def state_code
41
- state
241
+
242
+ def self.find_carmen_country_by_name(name)
243
+ name = recognize_country_name_alias(name)
244
+ Carmen::Country.named(name)
42
245
  end
43
246
 
44
- def carmen_country
45
- Carmen::Country.alpha_2_coded(country_alpha2)
247
+ def self.find_carmen_country_by_code(code)
248
+ # Carmen::Country.coded(code)
249
+ Carmen::Country.send(carmen_country_code_find_method, code)
46
250
  end
47
251
 
48
- def carmen_state
49
- if (country = carmen_country)
50
- Address.states_for_country(country).coded(state_code)
252
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
253
+ # find state
254
+
255
+ # Finds by name, falling back to finding by code.
256
+ def self.find_carmen_state(country_name, name)
257
+ return name if name.is_a? Carmen::Region
258
+
259
+ country = find_carmen_country!(country_name)
260
+ states = states_for_country(country)
261
+ (
262
+ states.named(name) ||
263
+ states.coded(name)
264
+ )
265
+ end
266
+ def self.find_carmen_state!(country_name, name)
267
+ find_carmen_state(country_name, name) or
268
+ raise "state #{name} not found for country #{country_name}"
269
+ end
270
+
271
+ def self.find_carmen_state_by_name(country_name, name)
272
+ country = find_carmen_country!(country_name)
273
+ states = states_for_country(country)
274
+ states.named(name)
275
+ end
276
+
277
+ def self.find_carmen_state_by_code(country_name, code)
278
+ country = find_carmen_country!(country_name)
279
+ states = states_for_country(country)
280
+ states.coded(code)
281
+ end
282
+
283
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
284
+ # country
285
+
286
+ # Calls country.code
287
+ _ = def self.carmen_country_code_for(country)
288
+ country.send(carmen_country_code)
289
+ end
290
+ delegate _, to: 'self.class'
291
+
292
+ # If you are storing both a country_name and country_code...
293
+ # This _should_ be the same as the value stored in the country attribute, but allows you to
294
+ # look it up just to make sure they match (or to update country field to match this).
295
+ def country_name_from_code
296
+ if (country = self.class.find_carmen_country_by_code(country_code))
297
+ country.name
298
+ end
299
+ end
300
+ def country_code_from_name
301
+ if (country = self.class.find_carmen_country_by_name(country_name))
302
+ self.class.carmen_country_code_for(country)
51
303
  end
52
304
  end
53
305
 
54
- #-------------------------------------------------------------------------------------------------
55
- # Country name
306
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
307
+ # state
56
308
 
57
- def country=(name)
58
- if name.blank?
59
- write_attribute(:country, nil)
60
- write_attribute(:country_alpha2, nil)
61
- write_attribute(:country_alpha3, nil)
62
- else
63
- name = recognize_country_name_alias(name)
64
- if (country = Carmen::Country.named(name))
65
- write_attribute(:country, country.name)
66
- write_attribute(:country_alpha2, country.alpha_2_code)
67
- write_attribute(:country_alpha3, country.alpha_3_code)
68
- else
69
- write_attribute(:country, nil)
70
- write_attribute(:country_alpha2, nil)
71
- write_attribute(:country_alpha3, nil)
72
- end
309
+ def state_name_from_code
310
+ if carmen_country && (state = self.class.find_carmen_state_by_code(carmen_country, state_code))
311
+ state.name
312
+ end
313
+ end
314
+ def state_code_from_name
315
+ if carmen_country && (state = self.class.find_carmen_state_by_name(carmen_country, state_name))
316
+ state.code
73
317
  end
74
318
  end
75
319
 
76
- def recognize_country_name_alias(name)
320
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
321
+ # country
322
+
323
+ def self.recognize_country_name_alias(name)
77
324
  name = case name
78
325
  when 'USA'
326
+ 'United States'
79
327
  when 'The Democratic Republic of the Congo', 'Democratic Republic of the Congo'
80
328
  'Congo, the Democratic Republic of the'
81
329
  when 'Republic of Macedonia', 'Macedonia, Republic of', 'Macedonia'
@@ -85,20 +333,178 @@ module AddressConcern::Address
85
333
  end
86
334
  end
87
335
 
88
- # This should not be different from the value stored in the country attribute, but allows you to
89
- # look it up just to make sure they match (or to update country field to match this).
90
- def country_name_from_code
91
- if (country = Carmen::Country.alpha_2_coded(country_alpha2))
92
- country.name
336
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
337
+
338
+ scope :in_country, ->(country_name) {
339
+ country = find_carmen_country!(country_name)
340
+ where(addresses: { country_code: country&.code })
341
+ }
342
+ scope :in_state, ->(country_name, name) {
343
+ country = find_carmen_country!(country_name)
344
+ state = find_carmen_state!(country_name, name)
345
+ where(addresses: { country_code: country&.code, state_code: state&.code })
346
+ }
347
+
348
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
349
+
350
+ def carmen_country
351
+ self.class.find_carmen_country_by_code(country_code)
352
+ end
353
+
354
+ def carmen_state
355
+ if (country = carmen_country)
356
+ # country.subregions.coded(state_code)
357
+ self.class.states_for_country(country).coded(state_code)
358
+ end
359
+ end
360
+
361
+ #═════════════════════════════════════════════════════════════════════════════════════════════════
362
+ # country attribute(s)
363
+
364
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
365
+ # setters
366
+
367
+
368
+ def clear_country
369
+ write_attribute(self.class.country_name_attribute, nil) if self.class.country_name_attribute
370
+ write_attribute(self.class.country_code_attribute, nil) if self.class.country_code_attribute
371
+ end
372
+
373
+ def set_country_from_carmen_country(country)
374
+ write_attribute(self.class.country_name_attribute, country.name ) if self.class.country_name_attribute
375
+ write_attribute(self.class.country_code_attribute, carmen_country_code_for(country)) if self.class.country_code_attribute
376
+ end
377
+
378
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
379
+ # code=
380
+
381
+ # def country_code=(code)
382
+ define_method :"#{country_code_attribute || 'country_code'}=" do |value|
383
+ if value.blank?
384
+ clear_country
385
+ else
386
+ if (country = self.class.find_carmen_country_by_code(value))
387
+ set_country_from_carmen_country(country)
388
+ else
389
+ country_config[:on_unknown].(value, :code)
390
+ write_attribute(self.class.country_code_attribute, value) if self.class.country_code_attribute
391
+ end
392
+ end
393
+ end
394
+
395
+ # Attribute alias
396
+ if country_code_attribute
397
+ unless :country_code == country_code_attribute
398
+ alias_attribute :country_code, :"#{country_code_attribute}"
399
+ #alias_method :country_code=, :"#{country_code_attribute}="
400
+ end
401
+ else
402
+ alias_method :country_code, :country_code_from_name
403
+ end
404
+
405
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
406
+ # name=
407
+
408
+ # def country_name=(name)
409
+ define_method :"#{country_name_attribute || 'country_name'}=" do |value|
410
+ if value.blank?
411
+ clear_country
412
+ else
413
+ if (country = self.class.find_carmen_country_by_name(value))
414
+ set_country_from_carmen_country(country)
415
+ else
416
+ country_config[:on_unknown].(value, :name)
417
+ write_attribute(self.class.country_name_attribute, value) if self.class.country_name_attribute
418
+ end
419
+ end
420
+ end
421
+
422
+ # Attribute alias
423
+ if country_name_attribute
424
+ unless :country_name == country_name_attribute
425
+ alias_attribute :country_name, country_name_attribute
426
+ #alias_method :country_name=, :"#{country_name_attribute}="
427
+ end
428
+ else
429
+ alias_method :country_name, :country_name_from_code
430
+ end
431
+
432
+ #════════════════════════════════════════════════════════════════════════════════════════════════════
433
+ # state attribute(s)
434
+ # (This is nearly identical to country section above with s/country/state/)
435
+
436
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
437
+ # setters
438
+
439
+
440
+ def clear_state
441
+ write_attribute(self.class.state_name_attribute, nil) if self.class.state_name_attribute
442
+ write_attribute(self.class.state_code_attribute, nil) if self.class.state_code_attribute
443
+ end
444
+
445
+ def set_state_from_carmen_state(state)
446
+ write_attribute(self.class.state_name_attribute, state.name) if self.class.state_name_attribute
447
+ write_attribute(self.class.state_code_attribute, state.code) if self.class.state_code_attribute
448
+ end
449
+
450
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
451
+ # code=
452
+
453
+ # def state_code=(code)
454
+ define_method :"#{state_code_attribute || 'state_code'}=" do |value|
455
+ if value.blank?
456
+ clear_state
457
+ else
458
+ if carmen_country && (state = self.class.find_carmen_state_by_code(carmen_country, value))
459
+ set_state_from_carmen_state(state)
460
+ else
461
+ #puts carmen_country ? "unknown state code '#{value}'" : "can't find state without country"
462
+ state_config[:on_unknown].(value, :code)
463
+ write_attribute(self.class.state_code_attribute, value) if self.class.state_code_attribute
464
+ end
93
465
  end
94
466
  end
95
467
 
96
- # Aliases
97
- def country_name
98
- country
468
+ # Attribute alias
469
+ if state_code_attribute
470
+ unless :state_code == state_code_attribute
471
+ alias_attribute :state_code, :"#{state_code_attribute}"
472
+ #alias_method :state_code=, :"#{state_code_attribute}="
473
+ end
474
+ else
475
+ alias_method :state_code, :state_code_from_name
476
+ end
477
+
478
+ # alias_method :province, :state
479
+
480
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
481
+ # name=
482
+
483
+ # def state_name=(name)
484
+ # Uses find_carmen_state so if your column was named 'state', you could actually do state = name
485
+ # or code.
486
+ define_method :"#{state_name_attribute || 'state_name'}=" do |value|
487
+ if value.blank?
488
+ clear_state
489
+ else
490
+ if carmen_country && (state = self.class.find_carmen_state(carmen_country, value))
491
+ set_state_from_carmen_state(state)
492
+ else
493
+ #puts carmen_country ? "unknown state name '#{name}'" : "can't find state without country"
494
+ state_config[:on_unknown].(value, :name)
495
+ write_attribute(self.class.state_name_attribute, value) if self.class.state_name_attribute
496
+ end
497
+ end
99
498
  end
100
- def country_name=(name)
101
- self.country = name
499
+
500
+ # Attribute alias
501
+ if state_name_attribute
502
+ unless :state_name == state_name_attribute
503
+ alias_attribute :state_name, state_name_attribute
504
+ #alias_method :state_name=, :"#{state_name_attribute}="
505
+ end
506
+ else
507
+ alias_method :state_name, :state_name_from_code
102
508
  end
103
509
 
104
510
  #════════════════════════════════════════════════════════════════════════════════════════════════════
@@ -111,27 +517,40 @@ module AddressConcern::Address
111
517
  # state_included_in_postal_address?.
112
518
  def self.states_for_country(country)
113
519
  return [] unless country
114
- raise ArgumentError.new('expected a Carmen::Country') unless country.is_a? Carmen::Country
115
- Carmen::RegionCollection.new(
116
- if country.name == 'Kenya'
117
- # https://github.com/jim/carmen/issues/227
118
- # https://en.wikipedia.org/wiki/Provinces_of_Kenya
119
- # Kenya's provinces were replaced by a system of counties in 2013.
120
- # https://en.wikipedia.org/wiki/ISO_3166-2:KE confirms that they are "former" provinces.
121
- # At the time of this writing, however, it doesn't look like Carmen has been updated to
122
- # include the 47 counties listed under https://en.wikipedia.org/wiki/ISO_3166-2:KE.
123
- country.subregions.typed('county')
124
- elsif country.name == 'France'
125
- # https://github.com/jim/carmen/issues/228
126
- # https://en.wikipedia.org/wiki/Regions_of_France
127
- # In 2016 what had been 27 regions was reduced to 18.
128
- # France is divided into 18 administrative regions, including 13 metropolitan regions and 5 overseas regions.
129
- # https://en.wikipedia.org/wiki/ISO_3166-2:FR
130
- []
131
- else # Needed for New Zealand, Philippines, Indonesia, and possibly others
132
- country.subregions.map {|_| _.subregions.any? ? _.subregions : _ }.flatten
133
- end
134
- )
520
+ country = find_carmen_country!(country)
521
+
522
+ has_states_at_level_1 = country.subregions.any? { |region|
523
+ region.type == 'state' ||
524
+ region.type == 'province' ||
525
+ region.type == 'metropolitan region'
526
+ }
527
+ has_states_at_level_1 = false if country.name == 'United Kingdom'
528
+
529
+ if country.name == 'Kenya'
530
+ # https://github.com/jim/carmen/issues/227
531
+ # https://en.wikipedia.org/wiki/Provinces_of_Kenya
532
+ # Kenya's provinces were replaced by a system of counties in 2013.
533
+ # https://en.wikipedia.org/wiki/ISO_3166-2:KE confirms that they are "former" provinces.
534
+ # At the time of this writing, however, it doesn't look like Carmen has been updated to
535
+ # include the 47 counties listed under https://en.wikipedia.org/wiki/ISO_3166-2:KE.
536
+ country.subregions.typed('county')
537
+ #elsif country.name == 'France'
538
+ # # https://github.com/jim/carmen/issues/228
539
+ # # https://en.wikipedia.org/wiki/Regions_of_France
540
+ # # In 2016 what had been 27 regions was reduced to 18.
541
+ # # France is divided into 18 administrative regions, including 13 metropolitan regions and 5 overseas regions.
542
+ # # https://en.wikipedia.org/wiki/ISO_3166-2:FR
543
+ # []
544
+ elsif has_states_at_level_1
545
+ country.subregions
546
+ else
547
+ # Going below level-1 subregions is needed for Philippines, Indonesia, and possibly others
548
+ Carmen::RegionCollection.new(
549
+ country.subregions.
550
+ map { |_| _.subregions.any? ? _.subregions : _ }.
551
+ flatten
552
+ )
553
+ end
135
554
  end
136
555
  def states_for_country
137
556
  self.class.states_for_country(carmen_country)
@@ -142,6 +561,27 @@ module AddressConcern::Address
142
561
  states_for_country.any?
143
562
  end
144
563
 
564
+ #───────────────────────────────────────────────────────────────────────────────────────────────
565
+
566
+ # Used for checking/testing states_for_country.
567
+ # Example:
568
+ # Address.compare_subregions_and_states_for_country('France');
569
+ def self.compare_subregions_and_states_for_country(country)
570
+ country = find_carmen_country!(country)
571
+ states_for_country = states_for_country(country)
572
+ if country.subregions == states_for_country
573
+ puts '(Same:)'
574
+ pp country.subregions
575
+ else
576
+ puts %(country.subregions (#{country.subregions.size}):\n#{country.subregions.pretty_inspect})
577
+ puts
578
+ puts %(states_for_country(country) (#{states_for_country.size}):\n#{states_for_country})
579
+ states_for_country
580
+ end
581
+ end
582
+
583
+ #───────────────────────────────────────────────────────────────────────────────────────────────
584
+
145
585
  # Is the state/province required in a postal address?
146
586
  # If no, perhaps you want to collect it for other reasons (like seeing which people/things are in
147
587
  # the same region). Or for countries where it *may* be included in a postal address but is not
@@ -212,22 +652,38 @@ module AddressConcern::Address
212
652
  end
213
653
  end
214
654
 
215
- def state_name
216
- carmen_state ? carmen_state.name : state
217
- end
218
-
219
655
  #════════════════════════════════════════════════════════════════════════════════════════════════════
220
656
 
221
657
  def empty?
222
- [:address, :city, :state, :postal_code, :country].all? {|_|
223
- !self[_].present?
224
- }
658
+ address_attributes.all? do |key, value|
659
+ value.blank?
660
+ end
225
661
  end
226
662
 
227
- def started_filling_out?
228
- [:address, :city, :state, :postal_code, :country].any? {|_|
229
- self[_].present?
230
- }
663
+ def present?
664
+ address_attributes.any? do |key, value|
665
+ value.present?
666
+ end
667
+ end
668
+
669
+ #════════════════════════════════════════════════════════════════════════════════════════════════════
670
+ # Street address / Address lines
671
+
672
+ # Attribute alias for street address line 1
673
+ #if address_attribute
674
+ # unless :address == address_attribute
675
+ # alias_attribute :address, :"#{address_attribute}"
676
+ # end
677
+ #end
678
+
679
+ def address_lines
680
+ if self.class.multi_line_address?
681
+ address.to_s.cleanlines.to_a
682
+ else
683
+ self.class.address_attributes.map do |attr_name|
684
+ send attr_name
685
+ end
686
+ end
231
687
  end
232
688
 
233
689
  #════════════════════════════════════════════════════════════════════════════════════════════════════
@@ -236,11 +692,11 @@ module AddressConcern::Address
236
692
  # Lines of a postal address
237
693
  def lines
238
694
  [
239
- name,
240
- address.to_s.lines.to_a,
695
+ #name,
696
+ *address_lines,
241
697
  city_line,
242
698
  country_name,
243
- ].flatten.reject(&:blank?)
699
+ ].reject(&:blank?)
244
700
  end
245
701
 
246
702
  # Used by #lines
@@ -248,7 +704,7 @@ module AddressConcern::Address
248
704
  # Instead of using `state` method (which is really state_code). That's fine for some countries
249
705
  # like US, Canada, Australia but not other countries (presumably).
250
706
  #
251
- # TODO: Put postal code and city in a different order, as that countries conventions dictate.
707
+ # TODO: Put postal code and city in a different order, as that country's conventions dictate.
252
708
  # See http://bitboost.com/ref/international-address-formats/new-zealand/
253
709
  #
254
710
  def city_line
@@ -260,7 +716,7 @@ module AddressConcern::Address
260
716
  end
261
717
 
262
718
  def city_state_code
263
- [city, state].reject(&:blank?).join(', ')
719
+ [city, state_code].reject(&:blank?).join(', ')
264
720
  end
265
721
 
266
722
  def city_state_name
@@ -286,21 +742,43 @@ module AddressConcern::Address
286
742
  #════════════════════════════════════════════════════════════════════════════════════════════════════
287
743
  # Misc. output
288
744
 
745
+ # TODO: remove?
289
746
  def parts
290
747
  [
291
- name,
292
- address.to_s.lines.to_a,
748
+ #name,
749
+ *address_lines,
293
750
  city,
294
751
  state_name,
295
752
  postal_code,
296
753
  country_name,
297
- ].flatten.reject(&:blank?)
754
+ ].reject(&:blank?)
298
755
  end
299
756
 
757
+ # def inspect
758
+ # inspect_base(
759
+ # :id,
760
+ # #:name,
761
+ # :address,
762
+ # # address_2 ...
763
+ # :city,
764
+ # :state,
765
+ # :postal_code,
766
+ # :country,
767
+ # )
768
+ # end
769
+
300
770
  def inspect
301
- inspect_with([:id, :name, :address, :city, :state, :postal_code, :country], ['{', '}'])
771
+ inspect_base(
772
+ :id,
773
+ address_attributes
774
+ )
302
775
  end
303
776
 
304
- #-------------------------------------------------------------------------------------------------
777
+ #─────────────────────────────────────────────────────────────────────────────────────────────────
305
778
  end
306
779
  end
780
+ end
781
+
782
+ ActiveRecord::Base.class_eval do
783
+ include AddressConcern::Address::Base
784
+ end