mainstreet 0.1.0 → 0.2.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
- SHA1:
3
- metadata.gz: d572831c5d0c80c6580ac885edf6c1ea8137589d
4
- data.tar.gz: 3f50e8e84d9edad8918aa83e9b87ca6f450bc196
2
+ SHA256:
3
+ metadata.gz: dc0cc69ef3e5d2a656626dc6dca4fc1c2742c302f643f819b84469ee3d3fb5f6
4
+ data.tar.gz: 8c7b9672fa0129541e76b0013ef3c0b3500d1797ffd1363f8739bfd274deec61
5
5
  SHA512:
6
- metadata.gz: f9b58c621595ee590c924beb58aa5f4c6402c9df339fc62fd78ea3789ca8ec23accc756f5dabc41a5733c42d98271431db6e1eba007d21ed8dbf2c232fe69134
7
- data.tar.gz: 954950d8881fd942e1f159e540eb1634780bb36350720402df09f6f25326490d35e1f38d85f9dd98aa4e2ec0fcf0fccc1d34b5a7024d9dc1d9438ada00558e15
6
+ metadata.gz: 97261f746dd103d661b8d08d12c3cf9c903aa8e4ddf2b5ce6697e5ecd990ffe77d4557f828ee08ae56499c14b77bdea01cf5a489aa69abd9b1da3dc09debcd7d
7
+ data.tar.gz: 739008f795c5107cd1a8f41ebebba23a01d992534974294018bdec643f252316246d6f785693b3d31e94733f28a911437f7f55e980c62e3c0c213baf5560db4c
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ ## 0.2.0
2
+
3
+ - Added `AddressVerifier`
4
+ - Added `validates_address` method
5
+ - Removed `acts_as_address` method
6
+
7
+ ## 0.1.0
8
+
9
+ - Added more validation
10
+ - Fixed model generator for Rails 5
11
+
12
+ ## 0.0.1
13
+
14
+ - First release
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Andrew Kane
3
+ Copyright (c) 2015-2018 Andrew Kane
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,95 +1,143 @@
1
1
  # MainStreet
2
2
 
3
- A standard US address model for Rails
3
+ Address verification for Ruby and Rails
4
4
 
5
- You get:
5
+ :earth_americas: Supports international addresses
6
6
 
7
- - verification
8
- - standardization
9
- - geocoding
7
+ ## Installation
10
8
 
11
- ## How It Works
9
+ Add this line to your application’s Gemfile:
12
10
 
13
11
  ```ruby
14
- Address.create!(street: "1 infinite loop", zip_code: "95014")
12
+ gem 'mainstreet'
15
13
  ```
16
14
 
17
- This creates an address with:
15
+ ## Full Verification
18
16
 
19
- - street - `1 Infinite Loop`
20
- - city - `Cupertino`
21
- - state - `CA`
22
- - zip_code - `95014`
23
- - latitude - `37.33053`
24
- - longitude - `-122.02887`
25
- - original_attributes - `{"street"=>"1 infinite loop", "street2"=>nil, "city"=>nil, "state"=>nil, "zip_code"=>"95014"}`
26
- - verification_info
17
+ By default, bad street numbers, units, and postal codes may pass verification. For full verification, get an account with [SmartyStreets](https://smartystreets.com). The free plan supports 250 lookups per month for US addresses, and plans for international addresses start at $7. To use it, set:
27
18
 
28
- ### Verification
19
+ ```ruby
20
+ ENV["SMARTY_STREETS_AUTH_ID"] = "auth-id"
21
+ ENV["SMARTY_STREETS_AUTH_TOKEN"] = "auth-token"
22
+ ```
23
+
24
+ ## How to Use
29
25
 
30
- By default, MainStreet performs ZIP code verification.
26
+ Check an address with:
31
27
 
32
28
  ```ruby
33
- address = Address.new(street:"1 infinite loop", zip_code: "95015")
34
- address.valid?
35
- # false
36
- address.errors.full_messages
37
- # ["Did you mean 95014?"]
29
+ address = "1600 Pennsylvania Ave NW, Washington DC 20500"
30
+ verifier = MainStreet::AddressVerifier.new(address)
31
+ verifier.success?
38
32
  ```
39
33
 
40
- For full verification, including unit number, [see below](#full-verification).
34
+ If verification fails, get the failure message with:
41
35
 
42
- ## Installation
36
+ ```ruby
37
+ verifier.failure_message
38
+ ```
43
39
 
44
- Add this line to your application’s Gemfile:
40
+ Get details about the result with:
45
41
 
46
42
  ```ruby
47
- gem 'mainstreet'
43
+ verifier.result
48
44
  ```
49
45
 
50
- To create a new address model, run:
46
+ Get the latitude and longitude with:
51
47
 
52
- ```sh
53
- rails g mainstreet:address
48
+ ```ruby
49
+ verifier.latitude
50
+ verifier.longitude
54
51
  ```
55
52
 
56
- This creates an `Address` model with:
53
+ ## Active Record
57
54
 
58
- - street
59
- - street2
60
- - city
61
- - state
62
- - zip_code
63
- - latitude
64
- - longitude
65
- - original_attributes
66
- - verification_info
55
+ For Active Record models, use:
67
56
 
68
- To add to an existing model:
57
+ ```ruby
58
+ class User < ApplicationRecord
59
+ validates_address fields: [:street, :street2, :city, :state, :postal_code]
60
+ end
61
+ ```
69
62
 
70
- 1. Use `alias_attribute` to map existing field names
71
- 2. Add new fields like `original_attributes` and `verification_info`
72
- 3. Add `acts_as_address` to your model
63
+ For performance, the address is only verified if at least one of the fields changes. The order should be the same as if you were to write the address out.
73
64
 
74
- ## Full Verification
65
+ Geocode the address with:
75
66
 
76
- [SmartyStreets](https://smartystreets.com/features) is required for full verification. The free plan supports 250 lookups per month.
67
+ ```ruby
68
+ class User < ApplicationRecord
69
+ validates_address geocode: true, ...
70
+ end
71
+ ```
77
72
 
78
- Set:
73
+ The `latitude` and `longitude` fields are used by default. Specify the fields with:
79
74
 
80
75
  ```ruby
81
- ENV["SMARTY_STREETS_AUTH_ID"] = "auth-id"
82
- ENV["SMARTY_STREETS_AUTH_TOKEN"] = "auth-token"
76
+ class User < ApplicationRecord
77
+ validates_address geocode: {latitude: :lat, longitude: :lon}, ...
78
+ end
79
+ ```
80
+
81
+ Empty addresses are not verified. To require an address, add your own validation.
82
+
83
+ ```ruby
84
+ class User < ApplicationRecord
85
+ validates :street, presence: true
86
+ end
83
87
  ```
84
88
 
85
- To test it, run:
89
+ ## SmartyStreets
90
+
91
+ With SmartyStreets, you must pass the country for non-US addresses.
92
+
93
+ ```ruby
94
+ MainStreet::AddressVerifier.new(address, country: "France")
95
+ ```
96
+
97
+ Here’s the list of [supported countries](https://smartystreets.com/docs/cloud/international-street-api#countries). You can pass the name, ISO-3, ISO-2, or ISO-N code (like `France`, `FRA`, `FR`, or `250`).
98
+
99
+ **Note:** You’ll also need to use a fork of Geocoder until [this PR](https://github.com/alexreisner/geocoder/pull/1367) is merged.
86
100
 
87
101
  ```ruby
88
- address = Address.new(street: "122 Mast Rd", zip_code: "03861")
89
- address.valid?
90
- # should get false
102
+ gem 'geocoder', github: 'ankane/geocoder', branch: 'smarty_streets_international'
91
103
  ```
92
104
 
105
+ For Active Record, use:
106
+
107
+ ```ruby
108
+ class User < ApplicationRecord
109
+ validates_address country: "France"
110
+ end
111
+ ```
112
+
113
+ Or use a proc to make it dynamic
114
+
115
+ ```ruby
116
+ class User < ApplicationRecord
117
+ validates_address country: -> { country }
118
+ end
119
+ ```
120
+
121
+ ## Data Privacy
122
+
123
+ We recommend encrypting the street information for user addresses. Check out [this article](https://ankane.org/sensitive-data-rails) for more details.
124
+
125
+ ```ruby
126
+ class User < ApplicationRecord
127
+ attr_encrypted :street, key: ...
128
+ end
129
+ ```
130
+
131
+ ## Upgrading
132
+
133
+ ### 0.2.0
134
+
135
+ See the [upgrade guide](docs/0-2-Upgrade.md)
136
+
137
+ ## History
138
+
139
+ View the [changelog](https://github.com/ankane/mainstreet/blob/master/CHANGELOG.md)
140
+
93
141
  ## Contributing
94
142
 
95
143
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
data/lib/mainstreet.rb CHANGED
@@ -1,95 +1,22 @@
1
- require "mainstreet/version"
1
+ # dependencies
2
2
  require "geocoder"
3
3
 
4
- module Mainstreet
5
- class << self
6
- attr_writer :lookup
7
-
8
- def lookup
9
- @lookup ||= begin
10
- if ENV["SMARTY_STREETS_AUTH_ID"] && ENV["SMARTY_STREETS_AUTH_TOKEN"]
11
- Geocoder.config[:smarty_streets] ||= {api_key: [ENV["SMARTY_STREETS_AUTH_ID"], ENV["SMARTY_STREETS_AUTH_TOKEN"]]}
12
- end
13
- Geocoder.config[:smarty_streets] ? :smarty_streets : nil
14
- end
15
- end
16
- end
17
-
18
- module Model
19
- def acts_as_address(_options = {})
20
- class_eval do
21
- serialize :original_attributes
22
- serialize :verification_info
23
-
24
- validates :street, presence: true
25
- validate :verify_address, if: -> { address_fields_changed? }
26
- before_save :standardize_address, if: -> { address_fields_changed? }
27
-
28
- def verify_address
29
- if zip_code.blank? && (city.blank? || state.blank?)
30
- errors.add(:base, "Address can't be confirmed")
31
- end
32
- if errors.empty?
33
- @verification_result = fetch_verification_info
34
- if @verification_result
35
- if @verification_result.respond_to?(:analysis)
36
- case @verification_result.analysis["dpv_match_code"]
37
- when "N"
38
- errors.add(:base, "Address can't be confirmed")
39
- when "S"
40
- errors.add(:base, "Apartment or suite can't be confirmed")
41
- when "D"
42
- errors.add(:base, "Apartment or suite is missing")
43
- end
44
- end
45
-
46
- correct_zip_code = @verification_result.postal_code
47
- if zip_code != correct_zip_code
48
- errors.add(:base, "Did you mean #{correct_zip_code}?")
49
- end
50
- else
51
- errors.add(:base, "Address can't be confirmed")
52
- end
53
- end
54
- errors.full_messages
55
- end
56
-
57
- def standardize_address
58
- result = @verification_result
59
- if result
60
- info = result.data
61
- self.original_attributes = attributes.slice(*address_fields)
62
- self.verification_info = result.data.to_hash
63
- self.street =
64
- if result.respond_to?(:delivery_line_1)
65
- result.delivery_line_1
66
- else
67
- result.formatted_address.split(",").first
68
- end
69
- self.street2 = nil
70
- self.city = result.city
71
- self.state = result.state_code
72
- self.zip_code = result.postal_code
73
- self.latitude = result.latitude
74
- self.longitude = result.longitude
75
- end
76
- true
77
- end
78
-
79
- def fetch_verification_info
80
- Geocoder.search("#{street} #{street2} #{city}, #{state} #{zip_code}", lookup: Mainstreet.lookup).first
81
- end
4
+ # modules
5
+ require "mainstreet/address_verifier"
6
+ require "mainstreet/version"
82
7
 
83
- def address_fields_changed?
84
- address_fields.any? { |f| send("#{f}_changed?") }
85
- end
8
+ if ENV["SMARTY_STREETS_AUTH_ID"]
9
+ Geocoder.config[:smarty_streets] ||= {
10
+ api_key: [
11
+ ENV["SMARTY_STREETS_AUTH_ID"],
12
+ ENV["SMARTY_STREETS_AUTH_TOKEN"]
13
+ ]
14
+ }
15
+ end
86
16
 
87
- def address_fields
88
- attributes.keys & %w(street street2 city state zip_code)
89
- end
90
- end
91
- end
17
+ if defined?(ActiveSupport.on_load)
18
+ ActiveSupport.on_load(:active_record) do
19
+ require "mainstreet/model"
20
+ extend MainStreet::Model
92
21
  end
93
22
  end
94
-
95
- ActiveRecord::Base.send(:extend, Mainstreet::Model) if defined?(ActiveRecord)
@@ -0,0 +1,70 @@
1
+ module MainStreet
2
+ class AddressVerifier
3
+ def initialize(address, country: nil)
4
+ @address = address
5
+ @country = country
6
+ end
7
+
8
+ def success?
9
+ failure_message.nil?
10
+ end
11
+
12
+ def failure_message
13
+ if !result
14
+ "Address can't be confirmed"
15
+ elsif result.respond_to?(:analysis)
16
+ analysis = result.analysis
17
+
18
+ if analysis["verification_status"]
19
+ case analysis["verification_status"]
20
+ when "Verified"
21
+ nil # success!!
22
+ when "Ambiguous", "Partial", "None"
23
+ "Address can't be confirmed"
24
+ else
25
+ raise "Unknown verification_status"
26
+ end
27
+ elsif analysis["dpv_match_code"]
28
+ case analysis["dpv_match_code"]
29
+ when "Y"
30
+ nil # success!!
31
+ when "N"
32
+ "Address can't be confirmed"
33
+ when "S"
34
+ "Apartment or suite can't be confirmed"
35
+ when "D"
36
+ "Apartment or suite is missing"
37
+ else
38
+ raise "Unknown dpv_match_code"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def result
45
+ @result ||= begin
46
+ options = {lookup: lookup}
47
+ options[:country] = @country if @country && !usa?
48
+ Geocoder.search(@address, options).first
49
+ end
50
+ end
51
+
52
+ def latitude
53
+ result && result.latitude
54
+ end
55
+
56
+ def longitude
57
+ result && result.longitude
58
+ end
59
+
60
+ private
61
+
62
+ def usa?
63
+ ["United States", "USA", "US", "840"].include?(@country.to_s)
64
+ end
65
+
66
+ def lookup
67
+ ENV["SMARTY_STREETS_AUTH_ID"] ? :smarty_streets : nil
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ module MainStreet
2
+ module Model
3
+ def validates_address(fields:, geocode: false, country: nil)
4
+ fields = Array(fields.map(&:to_s))
5
+ geocode_options = {latitude: :latitude, longitude: :longitude}
6
+ geocode_options = geocode_options.merge(geocode) if geocode.is_a?(Hash)
7
+
8
+ class_eval do
9
+ validate :verify_address, if: -> { fields.any? { |f| changes.key?(f.to_s) } }
10
+
11
+ define_method :verify_address do
12
+ address = fields.map { |v| send(v).presence }.compact.join(", ")
13
+
14
+ if address.present?
15
+ # must use a different variable than country
16
+ record_country = instance_exec(&country) if country.respond_to?(:call)
17
+ verifier = MainStreet::AddressVerifier.new(address, country: record_country)
18
+ if verifier.success?
19
+ if geocode
20
+ self.send("#{geocode_options[:latitude]}=", verifier.latitude)
21
+ self.send("#{geocode_options[:longitude]}=", verifier.longitude)
22
+ end
23
+ else
24
+ errors.add(:base, verifier.failure_message)
25
+ end
26
+
27
+ # legacy - for standardize_address method
28
+ @address_verification_result = verifier.result
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
- module Mainstreet
2
- VERSION = "0.1.0"
1
+ module MainStreet
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mainstreet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-01 00:00:00.000000000 Z
11
+ date: 2018-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: geocoder
@@ -28,32 +28,18 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.8'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.8'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '10.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '10.0'
55
- - !ruby/object:Gem::Dependency
56
- name: minitest
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
@@ -67,7 +53,7 @@ dependencies:
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  - !ruby/object:Gem::Dependency
70
- name: activerecord
56
+ name: minitest
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - ">="
@@ -81,7 +67,7 @@ dependencies:
81
67
  - !ruby/object:Gem::Version
82
68
  version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
- name: sqlite3
70
+ name: activerecord
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
@@ -95,7 +81,7 @@ dependencies:
95
81
  - !ruby/object:Gem::Version
96
82
  version: '0'
97
83
  - !ruby/object:Gem::Dependency
98
- name: smartystreets
84
+ name: sqlite3
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - ">="
@@ -136,22 +122,19 @@ dependencies:
136
122
  - - ">="
137
123
  - !ruby/object:Gem::Version
138
124
  version: '0'
139
- description: A standard US address model for Rails
140
- email:
141
- - andrew@chartkick.com
125
+ description:
126
+ email: andrew@chartkick.com
142
127
  executables: []
143
128
  extensions: []
144
129
  extra_rdoc_files: []
145
130
  files:
146
- - ".gitignore"
147
- - Gemfile
131
+ - CHANGELOG.md
148
132
  - LICENSE.txt
149
133
  - README.md
150
- - Rakefile
151
- - lib/generators/mainstreet/address_generator.rb
152
134
  - lib/mainstreet.rb
135
+ - lib/mainstreet/address_verifier.rb
136
+ - lib/mainstreet/model.rb
153
137
  - lib/mainstreet/version.rb
154
- - mainstreet.gemspec
155
138
  homepage: https://github.com/ankane/mainstreet
156
139
  licenses:
157
140
  - MIT
@@ -164,7 +147,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
147
  requirements:
165
148
  - - ">="
166
149
  - !ruby/object:Gem::Version
167
- version: '0'
150
+ version: '2.2'
168
151
  required_rubygems_version: !ruby/object:Gem::Requirement
169
152
  requirements:
170
153
  - - ">="
@@ -172,8 +155,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
155
  version: '0'
173
156
  requirements: []
174
157
  rubyforge_project:
175
- rubygems_version: 2.5.1
158
+ rubygems_version: 2.7.6
176
159
  signing_key:
177
160
  specification_version: 4
178
- summary: A standard US address model for Rails
161
+ summary: Address verification for Ruby and Rails
179
162
  test_files: []
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /test/fixtures/
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in mainstreet.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- task default: :test
5
- Rake::TestTask.new do |t|
6
- t.libs << "test"
7
- t.pattern = "test/**/*_test.rb"
8
- end
@@ -1,10 +0,0 @@
1
- require "rails/generators"
2
-
3
- module Mainstreet
4
- class AddressGenerator < Rails::Generators::Base
5
- def boom
6
- invoke "model", ["Address", "street:string", "street2:string", "city:string", "state:string", "zip_code:string", "latitude:decimal{15.10}", "longitude:decimal{15.10}", "verification_info:text", "original_attributes:text"], options
7
- insert_into_file "app/models/address.rb", " acts_as_address\n", after: "ApplicationRecord\n"
8
- end
9
- end
10
- end
data/mainstreet.gemspec DELETED
@@ -1,32 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "mainstreet/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "mainstreet"
8
- spec.version = Mainstreet::VERSION
9
- spec.authors = ["Andrew Kane"]
10
- spec.email = ["andrew@chartkick.com"]
11
-
12
- spec.summary = "A standard US address model for Rails"
13
- spec.description = "A standard US address model for Rails"
14
- spec.homepage = "https://github.com/ankane/mainstreet"
15
- spec.license = "MIT"
16
-
17
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
19
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
21
-
22
- spec.add_dependency "geocoder"
23
-
24
- spec.add_development_dependency "bundler", "~> 1.8"
25
- spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_development_dependency "minitest"
27
- spec.add_development_dependency "activerecord"
28
- spec.add_development_dependency "sqlite3"
29
- spec.add_development_dependency "smartystreets"
30
- spec.add_development_dependency "vcr"
31
- spec.add_development_dependency "webmock"
32
- end