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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0bf8936e1edf97987fbcca8e6b47a9ca45e07fa8c2e3a120360cd6730c422a7
4
- data.tar.gz: 9b7aa33a67b425cf514d8c182bdc0583de2a9ade137afcb6d682c641d50612bb
3
+ metadata.gz: b46d807aa97c2e089222447115d7862105246f37f850e65f824f8599af5d714d
4
+ data.tar.gz: b2f82f834eb10ead8e14e9b63083093b94a27bdec7a157a7102ee6c01eb15e6a
5
5
  SHA512:
6
- metadata.gz: 4e8efd58bed3c59c920f66a4f144196338214d9f91a7af7185147f459752686c831f2a947326d1bedc8fd1b368af2f7b3984eaed038c5a17113986122c64682e
7
- data.tar.gz: f597210f7076721c355b99782c0fe445fad1c370db22df59d8af1143329568b0167f31f4e78d879c36e7ba699335cf9caa5e222e85eb799141ed74952b465256
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.1.0 (UNRELEASED)
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
Binary file
@@ -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%postalCode %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{5}"}
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%postalCode %locality"
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.
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available address fields.
7
5
  class AddressField < Enum
@@ -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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available administrative area types.
7
5
  class AdministrativeAreaType < Enum
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available dependent locality types.
7
5
  class DependentLocalityType < Enum
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Addressing
2
4
  class Enum
3
5
  class << self
@@ -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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available locality types.
7
5
  class LocalityType < Enum
@@ -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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available pattern types.
7
5
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressing/enum"
4
-
5
3
  module Addressing
6
4
  # Enumerates available postal code types.
7
5
  class PostalCodeType < Enum
@@ -1,4 +1,4 @@
1
- require "digest"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
4
  class Subdivision
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
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.1.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-02-07 00:00:00.000000000 Z
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