address_concern 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -1,3 +1,4 @@
1
1
  class Address < ApplicationRecord
2
- include AddressConcern::Address
2
+ #normalize_attributes :name
3
+ acts_as_address
3
4
  end
@@ -0,0 +1,11 @@
1
+ class AddressCustomAttrNames < ApplicationRecord
2
+ self.table_name = 'addresses'
3
+
4
+ acts_as_address(
5
+ country: {
6
+ name_attribute: 'country',
7
+ code_attribute: 'country_code',
8
+ carmen_code: 'code', # same as 'alpha_2_code'
9
+ }
10
+ )
11
+ end
@@ -0,0 +1,12 @@
1
+ class AddressWithCodeOnly < ApplicationRecord
2
+ self.table_name = 'address_with_code_or_name_only'
3
+
4
+ acts_as_address(
5
+ state: {
6
+ code_attribute: 'state',
7
+ },
8
+ country: {
9
+ code_attribute: 'country'
10
+ },
11
+ )
12
+ end
@@ -0,0 +1,5 @@
1
+ class AddressWithNameOnly < ApplicationRecord
2
+ self.table_name = 'address_with_code_or_name_only'
3
+
4
+ acts_as_address
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddressWithSeparateAddressColumns < ApplicationRecord
2
+ self.table_name = 'address_with_separate_address_columns'
3
+
4
+ acts_as_address
5
+ end
@@ -1,3 +1,8 @@
1
1
  class User < ApplicationRecord
2
- has_addresses :types => [:physical, :shipping, :billing]
2
+ # Could also do:
3
+ # has_addresses [:physical, :shipping, :billing]
4
+ has_addresses
5
+ has_address :physical
6
+ has_address :shipping
7
+ has_address :billing
3
8
  end
@@ -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.1
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-03-23 00:00:00.000000000 Z
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: :runtime
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.1.6
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