addressing 0.1.0 → 0.3.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/CHANGELOG.md +15 -1
- data/README.md +18 -2
- data/data/address_formats.dump +0 -0
- data/data/address_formats.json +1 -1
- data/data/country/zh.json +1 -1
- data/data/library_customizations.rb +1 -1
- data/lib/addressing/address_field.rb +0 -2
- data/lib/addressing/address_format.rb +21 -0
- data/lib/addressing/administrative_area_type.rb +0 -2
- data/lib/addressing/dependent_locality_type.rb +0 -2
- data/lib/addressing/enum.rb +2 -0
- data/lib/addressing/field_override.rb +33 -0
- data/lib/addressing/lazy_subdivisions.rb +7 -0
- data/lib/addressing/locality_type.rb +0 -2
- data/lib/addressing/model.rb +111 -0
- data/lib/addressing/pattern_type.rb +0 -2
- data/lib/addressing/postal_code_type.rb +0 -2
- data/lib/addressing/subdivision.rb +1 -1
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +10 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b46d807aa97c2e089222447115d7862105246f37f850e65f824f8599af5d714d
|
4
|
+
data.tar.gz: b2f82f834eb10ead8e14e9b63083093b94a27bdec7a157a7102ee6c01eb15e6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb86d0f70a24db611837a597b3f8846a545f9c16d0df2099d6baab0235cc27ff0730b97f55b138b1d946715e3a9a671b6af23544ba59851d6f10b1cde428d9e5
|
7
|
+
data.tar.gz: 4b4c5ce9982c8faaf53a6eab5117b8250d17fc3e11ffb34e24e3765ad931b5ad1fbd4031633a132c23c7c4d489131ff5364af22a0d695acad02e349b23414d25
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
All notable changes to `addressing` will be documented in this file.
|
4
4
|
|
5
|
-
## 0.
|
5
|
+
## 0.3.0 (04-04-2022)
|
6
|
+
|
7
|
+
- Allow field validation to be overridden
|
8
|
+
- Only verify address when one of the field changes
|
9
|
+
- Allow used address fields to be configured
|
10
|
+
|
11
|
+
## 0.2.0 (25-02-2022)
|
12
|
+
|
13
|
+
- Add ActiveRecord validations
|
14
|
+
|
15
|
+
## 0.1.1 (21-02-2022)
|
16
|
+
|
17
|
+
- Fix typo in DE custom format
|
18
|
+
|
19
|
+
## 0.1.0 (07-02-2022)
|
6
20
|
|
7
21
|
- Initial release
|
data/README.md
CHANGED
@@ -172,6 +172,24 @@ p formatter.format(address, origin_country: "FR")
|
|
172
172
|
# ÉTATS-UNIS - UNITED STATES
|
173
173
|
```
|
174
174
|
|
175
|
+
### Validating addresses
|
176
|
+
|
177
|
+
For Active Record models, use:
|
178
|
+
|
179
|
+
```rb
|
180
|
+
class User < ApplicationRecord
|
181
|
+
validates_address_format
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
For performance, the address is only verified if at least one of the fields changes. Set your own condition with:
|
186
|
+
|
187
|
+
```rb
|
188
|
+
class User < ApplicationRecord
|
189
|
+
validates_address if: -> { something_changed? }, ...
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
175
193
|
## Changelog
|
176
194
|
|
177
195
|
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
|
@@ -203,5 +221,3 @@ Feel free to open an issue to get feedback on your idea before spending too much
|
|
203
221
|
## License
|
204
222
|
|
205
223
|
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
206
|
-
|
207
|
-
https://github.com/countries/countries/blob/eddb4af3889e5c8b66d897f9789d43c4a1ee0598/spec/thread_safety_spec.rb
|
data/data/address_formats.dump
CHANGED
Binary file
|
data/data/address_formats.json
CHANGED
@@ -42,7 +42,7 @@
|
|
42
42
|
{"country_code": "CX","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%locality %administrative_area %postal_code","uppercase_fields": ["locality","administrative_area"],"postal_code_pattern": "6798"}
|
43
43
|
{"country_code": "CY","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{4}"}
|
44
44
|
{"country_code": "CZ","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{3} ?\\d{2}"}
|
45
|
-
{"country_code": "DE","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%
|
45
|
+
{"country_code": "DE","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{5}"}
|
46
46
|
{"country_code": "DK","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{4}"}
|
47
47
|
{"country_code": "DO","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{5}"}
|
48
48
|
{"country_code": "DZ","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{5}"}
|
data/data/country/zh.json
CHANGED
@@ -35,7 +35,6 @@
|
|
35
35
|
"MK": "北马其顿",
|
36
36
|
"BJ": "贝宁",
|
37
37
|
"BE": "比利时",
|
38
|
-
"PE": "秘鲁",
|
39
38
|
"IS": "冰岛",
|
40
39
|
"PR": "波多黎各",
|
41
40
|
"PL": "波兰",
|
@@ -149,6 +148,7 @@
|
|
149
148
|
"MN": "蒙古",
|
150
149
|
"MS": "蒙特塞拉特",
|
151
150
|
"BD": "孟加拉国",
|
151
|
+
"PE": "秘鲁",
|
152
152
|
"FM": "密克罗尼西亚",
|
153
153
|
"MM": "缅甸",
|
154
154
|
"MD": "摩尔多瓦",
|
@@ -24,7 +24,7 @@ def address_format_customizations(country_code)
|
|
24
24
|
# Switch %organization and %recipient.
|
25
25
|
# https://github.com/googlei18n/libaddressinput/issues/83
|
26
26
|
format_customizations["DE"] = {
|
27
|
-
"format" => "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%
|
27
|
+
"format" => "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%postal_code %locality"
|
28
28
|
}
|
29
29
|
|
30
30
|
# Revert the removal of %sortingCode.
|
@@ -141,4 +141,25 @@ module Addressing
|
|
141
141
|
fields & used_fields
|
142
142
|
end
|
143
143
|
end
|
144
|
+
|
145
|
+
class AddressFormatHelper
|
146
|
+
class << self
|
147
|
+
# Gets the required fields.
|
148
|
+
#
|
149
|
+
# Applies field overrides to the required fields
|
150
|
+
# specified by the address format.
|
151
|
+
def required_fields(address_format, field_overrides)
|
152
|
+
required_fields = address_format.required_fields
|
153
|
+
required_fields = required_fields - field_overrides.optional_fields
|
154
|
+
required_fields = required_fields - field_overrides.hidden_fields
|
155
|
+
|
156
|
+
if field_overrides.required_fields
|
157
|
+
required_fields = required_fields + field_overrides.required_fields
|
158
|
+
required_fields = required_fields.uniq
|
159
|
+
end
|
160
|
+
|
161
|
+
required_fields
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
144
165
|
end
|
data/lib/addressing/enum.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Addressing
|
4
|
+
class FieldOverride < Enum
|
5
|
+
HIDDEN = "hidden"
|
6
|
+
OPTIONAL = "optional"
|
7
|
+
REQUIRED = "required"
|
8
|
+
end
|
9
|
+
|
10
|
+
class FieldOverrides
|
11
|
+
attr_reader :hidden_fields, :optional_fields, :required_fields
|
12
|
+
|
13
|
+
def initialize(definition)
|
14
|
+
AddressField.assert_all_exist(definition.keys)
|
15
|
+
FieldOverride.assert_all_exist(definition.values)
|
16
|
+
|
17
|
+
@hidden_fields = []
|
18
|
+
@optional_fields = []
|
19
|
+
@required_fields = []
|
20
|
+
|
21
|
+
definition.each do |field, override|
|
22
|
+
case override
|
23
|
+
when FieldOverride::HIDDEN
|
24
|
+
@hidden_fields << field
|
25
|
+
when FieldOverride::OPTIONAL
|
26
|
+
@optional_fields << field
|
27
|
+
when FieldOverride::REQUIRED
|
28
|
+
@required_fields << field
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Addressing
|
2
4
|
class LazySubdivisions
|
3
5
|
def initialize(parents)
|
@@ -16,6 +18,11 @@ module Addressing
|
|
16
18
|
@subdivisions.any?
|
17
19
|
end
|
18
20
|
|
21
|
+
def empty?
|
22
|
+
do_initialize unless @initialized
|
23
|
+
@subdivisions.empty?
|
24
|
+
end
|
25
|
+
|
19
26
|
private
|
20
27
|
|
21
28
|
def do_initialize
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Addressing
|
4
|
+
module Model
|
5
|
+
def validates_address_format(
|
6
|
+
fields: [:country_code, :administrative_area, :locality, :dependent_locality, :postal_code, :sorting_code, :address_line1, :address_line2, :organization, :given_name, :additional_name, :family_name, :locale], field_overrides: nil, **options)
|
7
|
+
fields = Array(fields)
|
8
|
+
field_overrides ||= FieldOverrides.new({})
|
9
|
+
|
10
|
+
options[:if] ||= -> { fields.any? { |f| changes.key?(f.to_s) } } unless options[:unless]
|
11
|
+
|
12
|
+
class_eval do
|
13
|
+
validate :verify_address_format, **options
|
14
|
+
|
15
|
+
define_method :verify_address_format do
|
16
|
+
values = fields.each_with_object({}) { |f, v| v[f] = send(f) }
|
17
|
+
address = Address.new(**values)
|
18
|
+
|
19
|
+
return unless address.country_code.present?
|
20
|
+
|
21
|
+
address_format = AddressFormat.get(address.country_code)
|
22
|
+
address_format.used_fields
|
23
|
+
|
24
|
+
return unless address.country_code.present?
|
25
|
+
|
26
|
+
address_format = AddressFormat.get(address.country_code)
|
27
|
+
address_format.used_fields
|
28
|
+
|
29
|
+
# Validate the presence of required fields.
|
30
|
+
AddressFormatHelper::required_fields(address_format, field_overrides).each do |required_field|
|
31
|
+
next unless address.send(required_field).blank?
|
32
|
+
|
33
|
+
errors.add(required_field, "should not be blank")
|
34
|
+
end
|
35
|
+
|
36
|
+
used_fields = address_format.used_fields - field_overrides.hidden_fields
|
37
|
+
|
38
|
+
# Validate the absence of unused fields.
|
39
|
+
unused_fields = AddressField.all.values - used_fields
|
40
|
+
unused_fields.each do |unused_field|
|
41
|
+
next if address.send(unused_field).blank?
|
42
|
+
|
43
|
+
errors.add(unused_field, "should be blank")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validate subdivisions.
|
47
|
+
subdivisions = verify_subdivisions(address, address_format)
|
48
|
+
|
49
|
+
# Validate postal code.
|
50
|
+
verify_postal_code(address.postal_code, subdivisions, address_format) if used_fields.include?(AddressField::POSTAL_CODE)
|
51
|
+
end
|
52
|
+
|
53
|
+
define_method :verify_subdivisions do |address, address_format|
|
54
|
+
# No predefined subdivisions exist, nothing to validate against.
|
55
|
+
return [] if address_format.subdivision_depth < 1
|
56
|
+
|
57
|
+
subdivisions, _parents = address_format.used_subdivision_fields.each_with_index.inject([[], []]) do |(subdivisions, parents), (field, index)|
|
58
|
+
# The field is empty or validation is disabled.
|
59
|
+
# break subdivisions if address.send(field).blank? || address_format.hidden_fields.include?(field)
|
60
|
+
break subdivisions if address.send(field).blank?
|
61
|
+
|
62
|
+
parents << (index > 0 ? address.send(address_format.used_subdivision_fields[index - 1]) : address_format.country_code)
|
63
|
+
subdivision = Subdivision.get(address.send(field), parents)
|
64
|
+
if subdivision.nil?
|
65
|
+
errors.add(field, "should be valid")
|
66
|
+
break [subdivisions, parents]
|
67
|
+
end
|
68
|
+
|
69
|
+
subdivisions << subdivision
|
70
|
+
# No predefined subdivisions below this level, stop here.
|
71
|
+
break [subdivisions, parents] if subdivision.children.empty?
|
72
|
+
|
73
|
+
[subdivisions, parents]
|
74
|
+
end
|
75
|
+
|
76
|
+
subdivisions
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method :verify_postal_code do |postal_code, subdivisions, address_format|
|
80
|
+
# Nothing to validate.
|
81
|
+
return if postal_code.blank?
|
82
|
+
|
83
|
+
full_pattern, start_pattern = subdivisions.inject([address_format.postal_code_pattern, nil]) do |(full_pattern, start_pattern), subdivision|
|
84
|
+
pattern = subdivision.postal_code_pattern
|
85
|
+
next [full_pattern, start_pattern] if pattern.blank?
|
86
|
+
next [pattern, start_pattern] if subdivision.postal_code_pattern_type == PatternType::FULL
|
87
|
+
|
88
|
+
[full_pattern, pattern]
|
89
|
+
end
|
90
|
+
|
91
|
+
if full_pattern
|
92
|
+
# The pattern must match the provided value completely.
|
93
|
+
match = postal_code.match(Regexp.new(full_pattern.gsub("\\\\", "\\").to_s, "i"))
|
94
|
+
if match.nil? || match[0] != postal_code
|
95
|
+
errors.add(AddressField::POSTAL_CODE, "should be valid")
|
96
|
+
return
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if start_pattern
|
101
|
+
# The pattern must match the start of the provided value.
|
102
|
+
match = postal_code.match(Regexp.new(start_pattern.gsub("\\\\", "\\").to_s, "i"))
|
103
|
+
if match.nil? || postal_code.index(match[0]) != 0
|
104
|
+
errors.add(AddressField::POSTAL_CODE, "should be valid")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/addressing/version.rb
CHANGED
data/lib/addressing.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "cgi"
|
4
|
+
require "digest"
|
4
5
|
require "json"
|
5
6
|
|
7
|
+
require "addressing/enum"
|
6
8
|
require "addressing/address"
|
7
9
|
require "addressing/address_field"
|
8
10
|
require "addressing/address_format"
|
@@ -10,6 +12,7 @@ require "addressing/administrative_area_type"
|
|
10
12
|
require "addressing/country"
|
11
13
|
require "addressing/default_formatter"
|
12
14
|
require "addressing/dependent_locality_type"
|
15
|
+
require "addressing/field_override"
|
13
16
|
require "addressing/lazy_subdivisions"
|
14
17
|
require "addressing/locale"
|
15
18
|
require "addressing/locality_type"
|
@@ -26,3 +29,10 @@ module Addressing
|
|
26
29
|
|
27
30
|
class UnknownLocaleError < Error; end
|
28
31
|
end
|
32
|
+
|
33
|
+
if defined?(ActiveSupport.on_load)
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
35
|
+
require "addressing/model"
|
36
|
+
extend Addressing::Model
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: addressing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robin van der Vleuten
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: robinvdvleuten@gmail.com
|
@@ -689,9 +689,11 @@ files:
|
|
689
689
|
- lib/addressing/default_formatter.rb
|
690
690
|
- lib/addressing/dependent_locality_type.rb
|
691
691
|
- lib/addressing/enum.rb
|
692
|
+
- lib/addressing/field_override.rb
|
692
693
|
- lib/addressing/lazy_subdivisions.rb
|
693
694
|
- lib/addressing/locale.rb
|
694
695
|
- lib/addressing/locality_type.rb
|
696
|
+
- lib/addressing/model.rb
|
695
697
|
- lib/addressing/pattern_type.rb
|
696
698
|
- lib/addressing/postal_code_type.rb
|
697
699
|
- lib/addressing/postal_label_formatter.rb
|