address_concern 2.0.1 → 2.1.0
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 -6
- data/address_concern.gemspec +2 -3
- data/app/models/concerns/address.rb +784 -0
- data/{lib/address_concern → app/models/concerns}/address_associations.rb +33 -12
- data/app/models/concerns/attributes_slice.rb +54 -0
- data/app/models/concerns/inspect_base.rb +32 -0
- data/config/address_concern.rb +3 -0
- data/lib/address_concern/attribute_normalizer.rb +10 -6
- data/lib/address_concern/engine.rb +24 -0
- data/lib/address_concern/version.rb +1 -1
- data/lib/address_concern.rb +4 -3
- 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 +17 -34
- data/lib/address_concern/address.rb +0 -306
data/spec/spec_helper.rb
CHANGED
@@ -30,12 +30,8 @@ CreateAddresses.up
|
|
30
30
|
|
31
31
|
require 'rspec'
|
32
32
|
|
33
|
-
require 'active_record_ignored_attributes/matchers'
|
34
|
-
|
35
|
-
|
36
33
|
RSpec.configure do |config|
|
37
|
-
config.include AttributeNormalizer::RSpecMatcher #, :type => :models
|
38
|
-
config.expect_with(:rspec) { |c| c.syntax = :should }
|
34
|
+
#config.include AttributeNormalizer::RSpecMatcher #, :type => :models
|
39
35
|
config.example_status_persistence_file_path = "tmp/rspec_status.txt"
|
40
36
|
end
|
41
37
|
|
data/spec/support/models/user.rb
CHANGED
data/spec/support/schema.rb
CHANGED
@@ -1,4 +1,26 @@
|
|
1
1
|
ActiveRecord::Schema.define do
|
2
|
+
# In order to avoid duplication, and to ensure that the template migration is valid, the schema for :addresses table can be found in lib/generators/address_concern/templates/migration.rb
|
3
|
+
|
4
|
+
create_table :address_with_code_or_name_only do |t|
|
5
|
+
t.text :address
|
6
|
+
t.string :city
|
7
|
+
t.string :state
|
8
|
+
t.string :postal_code
|
9
|
+
t.string :country
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :address_with_separate_address_columns do |t|
|
14
|
+
t.string :address_1
|
15
|
+
t.string :address_2
|
16
|
+
t.string :address_3
|
17
|
+
t.string :city
|
18
|
+
t.string :state
|
19
|
+
t.string :postal_code
|
20
|
+
t.string :country
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
|
2
24
|
create_table :users, :force => true do |t|
|
3
25
|
t.string :name
|
4
26
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: address_concern
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Campbell
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-04-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -81,20 +81,6 @@ dependencies:
|
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: 1.1.1
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: attribute_normalizer
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
91
|
-
type: :runtime
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - ">="
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
98
84
|
- !ruby/object:Gem::Dependency
|
99
85
|
name: active_record_ignored_attributes
|
100
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,21 +88,7 @@ dependencies:
|
|
102
88
|
- - ">="
|
103
89
|
- !ruby/object:Gem::Version
|
104
90
|
version: '0'
|
105
|
-
type: :
|
106
|
-
prerelease: false
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
108
|
-
requirements:
|
109
|
-
- - ">="
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version: '0'
|
112
|
-
- !ruby/object:Gem::Dependency
|
113
|
-
name: facets
|
114
|
-
requirement: !ruby/object:Gem::Requirement
|
115
|
-
requirements:
|
116
|
-
- - ">="
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
version: '0'
|
119
|
-
type: :runtime
|
91
|
+
type: :development
|
120
92
|
prerelease: false
|
121
93
|
version_requirements: !ruby/object:Gem::Requirement
|
122
94
|
requirements:
|
@@ -170,21 +142,32 @@ files:
|
|
170
142
|
- Readme.md
|
171
143
|
- VERSION
|
172
144
|
- address_concern.gemspec
|
145
|
+
- app/models/concerns/address.rb
|
146
|
+
- app/models/concerns/address_associations.rb
|
147
|
+
- app/models/concerns/attributes_slice.rb
|
148
|
+
- app/models/concerns/inspect_base.rb
|
173
149
|
- bin/console
|
150
|
+
- config/address_concern.rb
|
174
151
|
- config/locale/overlay/en/id.yml
|
175
152
|
- lib/address_concern.rb
|
176
|
-
- lib/address_concern/address.rb
|
177
|
-
- lib/address_concern/address_associations.rb
|
178
153
|
- lib/address_concern/attribute_normalizer.rb
|
154
|
+
- lib/address_concern/engine.rb
|
179
155
|
- lib/address_concern/version.rb
|
156
|
+
- lib/core_extensions/hash/reorder.rb
|
157
|
+
- lib/core_extensions/string/cleanlines.rb
|
180
158
|
- lib/generators/address_concern/install_generator.rb
|
181
159
|
- lib/generators/address_concern/templates/configurable.yml
|
182
160
|
- lib/generators/address_concern/templates/migration.rb
|
161
|
+
- spec/models/acts_as_address_spec.rb
|
183
162
|
- spec/models/address_spec.rb
|
184
163
|
- spec/spec_helper.rb
|
185
164
|
- spec/support/database.mysql2.yml
|
186
165
|
- spec/support/database.sqlite3.yml
|
187
166
|
- spec/support/models/address.rb
|
167
|
+
- spec/support/models/address_custom_attr_names.rb
|
168
|
+
- spec/support/models/address_with_code_only.rb
|
169
|
+
- spec/support/models/address_with_name_only.rb
|
170
|
+
- spec/support/models/address_with_separate_address_columns.rb
|
188
171
|
- spec/support/models/application_record.rb
|
189
172
|
- spec/support/models/child.rb
|
190
173
|
- spec/support/models/company.rb
|
@@ -211,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
194
|
- !ruby/object:Gem::Version
|
212
195
|
version: '0'
|
213
196
|
requirements: []
|
214
|
-
rubygems_version: 3.
|
197
|
+
rubygems_version: 3.3.3
|
215
198
|
signing_key:
|
216
199
|
specification_version: 4
|
217
200
|
summary: A reusable Address model for your Rails apps
|
@@ -1,306 +0,0 @@
|
|
1
|
-
module AddressConcern::Address
|
2
|
-
extend ActiveSupport::Concern
|
3
|
-
included do
|
4
|
-
|
5
|
-
#validates_presence_of :name
|
6
|
-
#validates_presence_of :address
|
7
|
-
#validates_presence_of :state, :if => :state_required?
|
8
|
-
#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
|
-
|
13
|
-
#-------------------------------------------------------------------------------------------------
|
14
|
-
normalize_attributes :name, :city, :state, :postal_code, :country
|
15
|
-
normalize_attribute :address, :with => [:cleanlines, :strip]
|
16
|
-
|
17
|
-
#-------------------------------------------------------------------------------------------------
|
18
|
-
# Country code
|
19
|
-
|
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)
|
25
|
-
|
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
|
31
|
-
end
|
32
|
-
|
33
|
-
# Aliases
|
34
|
-
def country_code
|
35
|
-
country_alpha2
|
36
|
-
end
|
37
|
-
def country_code=(code)
|
38
|
-
self.country_alpha2 = code
|
39
|
-
end
|
40
|
-
def state_code
|
41
|
-
state
|
42
|
-
end
|
43
|
-
|
44
|
-
def carmen_country
|
45
|
-
Carmen::Country.alpha_2_coded(country_alpha2)
|
46
|
-
end
|
47
|
-
|
48
|
-
def carmen_state
|
49
|
-
if (country = carmen_country)
|
50
|
-
Address.states_for_country(country).coded(state_code)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
#-------------------------------------------------------------------------------------------------
|
55
|
-
# Country name
|
56
|
-
|
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
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def recognize_country_name_alias(name)
|
77
|
-
name = case name
|
78
|
-
when 'USA'
|
79
|
-
when 'The Democratic Republic of the Congo', 'Democratic Republic of the Congo'
|
80
|
-
'Congo, the Democratic Republic of the'
|
81
|
-
when 'Republic of Macedonia', 'Macedonia, Republic of', 'Macedonia'
|
82
|
-
'Macedonia, Republic of'
|
83
|
-
else
|
84
|
-
name
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
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
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# Aliases
|
97
|
-
def country_name
|
98
|
-
country
|
99
|
-
end
|
100
|
-
def country_name=(name)
|
101
|
-
self.country = name
|
102
|
-
end
|
103
|
-
|
104
|
-
#════════════════════════════════════════════════════════════════════════════════════════════════════
|
105
|
-
# State/province options for country
|
106
|
-
|
107
|
-
# This is useful if want to list the state options allowed for a country in a select box and
|
108
|
-
# restrict entry to only officially listed state options.
|
109
|
-
# It is not required in the postal address for all countries, however. If you only want to show it
|
110
|
-
# if it's required in the postal address, you can make it conditional based on
|
111
|
-
# state_included_in_postal_address?.
|
112
|
-
def self.states_for_country(country)
|
113
|
-
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
|
-
)
|
135
|
-
end
|
136
|
-
def states_for_country
|
137
|
-
self.class.states_for_country(carmen_country)
|
138
|
-
end
|
139
|
-
alias_method :state_options, :states_for_country
|
140
|
-
|
141
|
-
def country_with_states?
|
142
|
-
states_for_country.any?
|
143
|
-
end
|
144
|
-
|
145
|
-
# Is the state/province required in a postal address?
|
146
|
-
# If no, perhaps you want to collect it for other reasons (like seeing which people/things are in
|
147
|
-
# the same region). Or for countries where it *may* be included in a postal address but is not
|
148
|
-
# required to be included.
|
149
|
-
def state_required_in_postal_address?
|
150
|
-
[
|
151
|
-
'Australia',
|
152
|
-
'Brazil',
|
153
|
-
'Canada',
|
154
|
-
'Mexico',
|
155
|
-
'United States',
|
156
|
-
'Italy',
|
157
|
-
'Venezuela',
|
158
|
-
].include? country_name
|
159
|
-
end
|
160
|
-
def state_possibly_included_in_postal_address?
|
161
|
-
# https://ux.stackexchange.com/questions/64665/address-form-field-for-region
|
162
|
-
# http://www.bitboost.com/ref/international-address-formats/denmark/
|
163
|
-
# http://www.bitboost.com/ref/international-address-formats/poland/
|
164
|
-
return true if state_required_in_postal_address?
|
165
|
-
return false if [
|
166
|
-
'Algeria',
|
167
|
-
'Argentina',
|
168
|
-
'Austria',
|
169
|
-
'Denmark',
|
170
|
-
'France',
|
171
|
-
'Germany',
|
172
|
-
'Indonesia',
|
173
|
-
'Ireland',
|
174
|
-
'Israel',
|
175
|
-
'Netherlands',
|
176
|
-
'New Zealand',
|
177
|
-
'Poland',
|
178
|
-
'Sweden',
|
179
|
-
'United Kingdom',
|
180
|
-
].include? country_name
|
181
|
-
# Default:
|
182
|
-
country_with_states?
|
183
|
-
end
|
184
|
-
|
185
|
-
# It's not called a "State" in all countries.
|
186
|
-
# In some countries, it could technically be multiple different types of regions:
|
187
|
-
# - In United States, it could be a state or an outlying region or a district or an APO
|
188
|
-
# - In Canada, it could be a province or a territory.
|
189
|
-
# This attempts to return the most common, expected name for this field.
|
190
|
-
# See also: https://ux.stackexchange.com/questions/64665/address-form-field-for-region
|
191
|
-
#
|
192
|
-
# To see what it should be called in all countries known to Carmen:
|
193
|
-
# Country.countries_with_states.map {|country| [country.name, Address.new(country_name: country.name).state_label] }.to_h
|
194
|
-
# => {"Afghanistan"=>"Province",
|
195
|
-
# "Armenia"=>"Province",
|
196
|
-
# "Angola"=>"Province",
|
197
|
-
# "Argentina"=>"Province",
|
198
|
-
# "Austria"=>"State",
|
199
|
-
# "Australia"=>"State",
|
200
|
-
# ...
|
201
|
-
def state_label
|
202
|
-
# In UK, it looks like they (optionally) include the *county* in their addresses. They don't actually have "states" per se.
|
203
|
-
# Reference: http://bitboost.com/ref/international-address-formats/united-kingdom/
|
204
|
-
# Could also limit to Countries (England, Scotland, Wales) and Provinces (Northern Ireland).
|
205
|
-
# Who knows. The UK's subregions are a mess.
|
206
|
-
# If allowing the full list of subregions from https://en.wikipedia.org/wiki/ISO_3166-2:GB,
|
207
|
-
# perhaps Region is a better, more inclusive term.
|
208
|
-
if country_name.in? ['United Kingdom']
|
209
|
-
'Region'
|
210
|
-
elsif state_options.any?
|
211
|
-
state_options[0].type.capitalize
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def state_name
|
216
|
-
carmen_state ? carmen_state.name : state
|
217
|
-
end
|
218
|
-
|
219
|
-
#════════════════════════════════════════════════════════════════════════════════════════════════════
|
220
|
-
|
221
|
-
def empty?
|
222
|
-
[:address, :city, :state, :postal_code, :country].all? {|_|
|
223
|
-
!self[_].present?
|
224
|
-
}
|
225
|
-
end
|
226
|
-
|
227
|
-
def started_filling_out?
|
228
|
-
[:address, :city, :state, :postal_code, :country].any? {|_|
|
229
|
-
self[_].present?
|
230
|
-
}
|
231
|
-
end
|
232
|
-
|
233
|
-
#════════════════════════════════════════════════════════════════════════════════════════════════════
|
234
|
-
# Formatting for humans
|
235
|
-
|
236
|
-
# Lines of a postal address
|
237
|
-
def lines
|
238
|
-
[
|
239
|
-
name,
|
240
|
-
address.to_s.lines.to_a,
|
241
|
-
city_line,
|
242
|
-
country_name,
|
243
|
-
].flatten.reject(&:blank?)
|
244
|
-
end
|
245
|
-
|
246
|
-
# Used by #lines
|
247
|
-
#
|
248
|
-
# Instead of using `state` method (which is really state_code). That's fine for some countries
|
249
|
-
# like US, Canada, Australia but not other countries (presumably).
|
250
|
-
#
|
251
|
-
# TODO: Put postal code and city in a different order, as that countries conventions dictate.
|
252
|
-
# See http://bitboost.com/ref/international-address-formats/new-zealand/
|
253
|
-
#
|
254
|
-
def city_line
|
255
|
-
[
|
256
|
-
#[city, state].reject(&:blank?).join(', '),
|
257
|
-
[city, state_for_postal_address].reject(&:blank?).join(', '),
|
258
|
-
postal_code,
|
259
|
-
].reject(&:blank?).join(' ')
|
260
|
-
end
|
261
|
-
|
262
|
-
def city_state_code
|
263
|
-
[city, state].reject(&:blank?).join(', ')
|
264
|
-
end
|
265
|
-
|
266
|
-
def city_state_name
|
267
|
-
[city, state_name].reject(&:blank?).join(', ')
|
268
|
-
end
|
269
|
-
|
270
|
-
def city_state_country
|
271
|
-
[city_state_name, country_name].join(', ')
|
272
|
-
end
|
273
|
-
|
274
|
-
def state_for_postal_address
|
275
|
-
# Possibly others use a code? But seems safer to default to a name until confirmed that they use
|
276
|
-
# a code.
|
277
|
-
if country_name.in? ['United States', 'Canada', 'Australia']
|
278
|
-
state_code
|
279
|
-
elsif state_possibly_included_in_postal_address?
|
280
|
-
state_name
|
281
|
-
else
|
282
|
-
''
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
#════════════════════════════════════════════════════════════════════════════════════════════════════
|
287
|
-
# Misc. output
|
288
|
-
|
289
|
-
def parts
|
290
|
-
[
|
291
|
-
name,
|
292
|
-
address.to_s.lines.to_a,
|
293
|
-
city,
|
294
|
-
state_name,
|
295
|
-
postal_code,
|
296
|
-
country_name,
|
297
|
-
].flatten.reject(&:blank?)
|
298
|
-
end
|
299
|
-
|
300
|
-
def inspect
|
301
|
-
inspect_with([:id, :name, :address, :city, :state, :postal_code, :country], ['{', '}'])
|
302
|
-
end
|
303
|
-
|
304
|
-
#-------------------------------------------------------------------------------------------------
|
305
|
-
end
|
306
|
-
end
|