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.
- checksums.yaml +4 -4
- data/Readme.md +73 -8
- data/address_concern.gemspec +3 -4
- data/config/address_concern.rb +3 -0
- data/lib/address_concern/address.rb +582 -104
- data/lib/address_concern/address_associations.rb +33 -12
- data/lib/address_concern/attribute_normalizer.rb +10 -6
- data/lib/address_concern/attributes_slice.rb +54 -0
- data/lib/address_concern/engine.rb +18 -0
- data/lib/address_concern/inspect_base.rb +33 -0
- data/lib/address_concern/version.rb +1 -1
- data/lib/address_concern.rb +17 -4
- data/lib/core_extensions/hash/reorder.rb +11 -0
- data/lib/core_extensions/string/cleanlines.rb +28 -0
- data/lib/generators/address_concern/templates/migration.rb +14 -10
- data/spec/models/acts_as_address_spec.rb +66 -0
- data/spec/models/address_spec.rb +345 -117
- data/spec/spec_helper.rb +1 -5
- data/spec/support/models/address.rb +2 -1
- data/spec/support/models/address_custom_attr_names.rb +11 -0
- data/spec/support/models/address_with_code_only.rb +12 -0
- data/spec/support/models/address_with_name_only.rb +5 -0
- data/spec/support/models/address_with_separate_address_columns.rb +5 -0
- data/spec/support/models/user.rb +6 -1
- data/spec/support/schema.rb +22 -0
- metadata +16 -33
@@ -1,81 +1,329 @@
|
|
1
|
-
|
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(¬_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(¬_null)&.name ||
|
32
|
+
(column_for_attribute(:state).yield_self(¬_null)&.name unless options.dig(:state, :name_attribute).to_s == 'state'),
|
33
|
+
|
34
|
+
name_attribute: column_for_attribute(:state_name).yield_self(¬_null)&.name ||
|
35
|
+
(column_for_attribute(:state).yield_self(¬_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(¬_null)&.name ||
|
48
|
+
(column_for_attribute(:country).yield_self(¬_null)&.name unless options.dig(:country, :name_attribute).to_s == 'country'),
|
49
|
+
|
50
|
+
name_attribute: column_for_attribute(:country_name).yield_self(¬_null)&.name ||
|
51
|
+
(column_for_attribute(:country).yield_self(¬_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, :
|
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
|
-
|
15
|
-
normalize_attribute :address, :with => [:cleanlines, :strip]
|
189
|
+
#═════════════════════════════════════════════════════════════════════════════════════════════════
|
190
|
+
# Attributes
|
16
191
|
|
17
|
-
|
18
|
-
|
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
|
21
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
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
|
38
|
-
|
237
|
+
def self.find_carmen_country!(name)
|
238
|
+
find_carmen_country(name) or
|
239
|
+
raise "country #{name} not found"
|
39
240
|
end
|
40
|
-
|
41
|
-
|
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
|
45
|
-
Carmen::Country.
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
#
|
306
|
+
#─────────────────────────────────────────────────────────────────────────────────────────────────
|
307
|
+
# state
|
56
308
|
|
57
|
-
def
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
#
|
97
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
658
|
+
address_attributes.all? do |key, value|
|
659
|
+
value.blank?
|
660
|
+
end
|
225
661
|
end
|
226
662
|
|
227
|
-
def
|
228
|
-
|
229
|
-
|
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
|
-
|
695
|
+
#name,
|
696
|
+
*address_lines,
|
241
697
|
city_line,
|
242
698
|
country_name,
|
243
|
-
].
|
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
|
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,
|
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
|
-
|
748
|
+
#name,
|
749
|
+
*address_lines,
|
293
750
|
city,
|
294
751
|
state_name,
|
295
752
|
postal_code,
|
296
753
|
country_name,
|
297
|
-
].
|
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
|
-
|
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
|