addressing 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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