address-standardization 0.4.2.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/README.md +158 -0
- data/Rakefile +9 -0
- data/TODO +1 -0
- data/address_standardization.gemspec +24 -0
- data/lib/address_standardization.rb +34 -0
- data/lib/address_standardization/abstract_service.rb +39 -0
- data/lib/address_standardization/address.rb +56 -0
- data/lib/address_standardization/google_maps.rb +78 -0
- data/lib/address_standardization/helpers.rb +21 -0
- data/lib/address_standardization/melissa_data.rb +87 -0
- data/lib/address_standardization/ruby_ext.rb +29 -0
- data/lib/address_standardization/version.rb +3 -0
- data/spec/address_spec.rb +142 -0
- data/spec/google_maps_spec.rb +114 -0
- data/spec/melissa_data_spec.rb +114 -0
- data/spec/spec_helper.rb +2 -0
- metadata +114 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create 1.9.3@address_standardization
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
address-standardization (0.4.1)
|
5
|
+
httparty (~> 0.8.1)
|
6
|
+
logging
|
7
|
+
mechanize (= 2.0.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
diff-lcs (1.1.3)
|
13
|
+
httparty (0.8.1)
|
14
|
+
multi_json
|
15
|
+
multi_xml
|
16
|
+
little-plugger (1.1.3)
|
17
|
+
logging (1.6.1)
|
18
|
+
little-plugger (>= 1.1.2)
|
19
|
+
mechanize (2.0)
|
20
|
+
net-http-digest_auth (~> 1.1, >= 1.1.1)
|
21
|
+
net-http-persistent (~> 1.8)
|
22
|
+
nokogiri (~> 1.4)
|
23
|
+
webrobots (~> 0.0, >= 0.0.9)
|
24
|
+
multi_json (1.0.4)
|
25
|
+
multi_xml (0.4.1)
|
26
|
+
net-http-digest_auth (1.2)
|
27
|
+
net-http-persistent (1.9)
|
28
|
+
nokogiri (1.5.0)
|
29
|
+
rspec (2.7.0)
|
30
|
+
rspec-core (~> 2.7.0)
|
31
|
+
rspec-expectations (~> 2.7.0)
|
32
|
+
rspec-mocks (~> 2.7.0)
|
33
|
+
rspec-core (2.7.1)
|
34
|
+
rspec-expectations (2.7.0)
|
35
|
+
diff-lcs (~> 1.1.2)
|
36
|
+
rspec-mocks (2.7.0)
|
37
|
+
webrobots (0.0.12)
|
38
|
+
nokogiri (>= 1.4.4)
|
39
|
+
|
40
|
+
PLATFORMS
|
41
|
+
ruby
|
42
|
+
|
43
|
+
DEPENDENCIES
|
44
|
+
address-standardization!
|
45
|
+
rspec (~> 2.7.0)
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# address_standardization
|
2
|
+
|
3
|
+
## What is it?
|
4
|
+
|
5
|
+
Despite the long name, this is a tiny Ruby library to quickly standardize a
|
6
|
+
postal address, either via the Google Geocoding API or MelissaData. So if you
|
7
|
+
have an online store and you need to verify a shipping address, then you might
|
8
|
+
consider using this library.
|
9
|
+
|
10
|
+
## What is it *not*?
|
11
|
+
|
12
|
+
This is not a library to *geocode* addresses or locations. In other words, you
|
13
|
+
won't get world coordinates back for a location. If you need this information,
|
14
|
+
then there are other libraries that will do this for you, such as
|
15
|
+
[graticule](http://github.com/collectiveidea/graticule) or
|
16
|
+
[GeoKit](http://github.com/andre/geokit-gem).
|
17
|
+
|
18
|
+
## How do I install it?
|
19
|
+
|
20
|
+
gem install address_standardization
|
21
|
+
|
22
|
+
## How do I use it?
|
23
|
+
|
24
|
+
Right now this library supports two services: the Google Geocoding API and
|
25
|
+
MelissaData. All services work the same way: you call a `standardize_address`
|
26
|
+
method with a hash of address data. If the address exists (according to the
|
27
|
+
service), you'll get back a full hash of address data. Otherwise, you'll get
|
28
|
+
back `nil`.
|
29
|
+
|
30
|
+
Both Google and MelissaData have benefits and drawbacks. First, in terms of
|
31
|
+
postal addresses there are three kinds of addresses you are probably concerned
|
32
|
+
with: US addresses, Canadian addresses, and then international addresses.
|
33
|
+
|
34
|
+
### Google
|
35
|
+
|
36
|
+
Google is pretty great in that in knows about all sorts of addresses. So if
|
37
|
+
you're trying to verify an address in France, then Google probably knows about
|
38
|
+
it. However, it is not very useful in terms of standardizing US addresses,
|
39
|
+
because it doesn't know what to do with P.O. boxes.
|
40
|
+
|
41
|
+
That said, here is how you'd standardize a US address:
|
42
|
+
|
43
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
44
|
+
:street => "1600 Amphitheatre Parkway",
|
45
|
+
:city => "Mountain View",
|
46
|
+
:state => "CA"
|
47
|
+
)
|
48
|
+
addr.street #=> "1600 AMPHITHEATRE PKWY"
|
49
|
+
addr.city #=> "MOUNTAIN VIEW"
|
50
|
+
addr.county #=> "SANTA CLARA"
|
51
|
+
addr.state #=> "CA"
|
52
|
+
addr.zip #=> "94043"
|
53
|
+
addr.country #=> "UNITED STATES"
|
54
|
+
|
55
|
+
By default, the `standardize_address` method will leave it up to the service to
|
56
|
+
decide which country to scope the address query to. Google may be able to figure
|
57
|
+
it out based on the address you provide, but you can always narrow it down by
|
58
|
+
providing a `:country` key. So for instance, to standardize a Canadian address:
|
59
|
+
|
60
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
61
|
+
:street => "55 East Cordova St. Apt 415",
|
62
|
+
:city => "Vancouver",
|
63
|
+
:province => "BC",
|
64
|
+
:country => "Canada"
|
65
|
+
)
|
66
|
+
addr.street #=> "55 E CORDOVA ST"
|
67
|
+
addr.city #=> "VANCOUVER"
|
68
|
+
addr.district #=> "GREATER VANCOUVER REGIONAL DISTRICT"
|
69
|
+
addr.province #=> "ON"
|
70
|
+
addr.postal_code #=> "V6A 1K3"
|
71
|
+
addr.country #=> "CANADA"
|
72
|
+
|
73
|
+
Note that `province` is an alias for `state`, `district` is an alias for
|
74
|
+
`county` and `postal_code` is an alias for `zip`.
|
75
|
+
|
76
|
+
And to standardize a French address:
|
77
|
+
|
78
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
79
|
+
:street => "Parvis de Notre Dame",
|
80
|
+
:city => "Paris",
|
81
|
+
:country => "France"
|
82
|
+
)
|
83
|
+
addr.street #=> "PLACE DU PARVIS NOTRE-DAME"
|
84
|
+
addr.city #=> "PARIS"
|
85
|
+
addr.region #=> "ÎLE-DE-FRANCE"
|
86
|
+
addr.postal_code #=> "75004"
|
87
|
+
addr.country #=> "FRANCE"
|
88
|
+
|
89
|
+
Finally, note what happens if we attempt to standardize a bogus address:
|
90
|
+
|
91
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
92
|
+
:street => "abcdef"
|
93
|
+
)
|
94
|
+
addr #=> nil
|
95
|
+
|
96
|
+
### MelissaData
|
97
|
+
|
98
|
+
If you want to simply verify US addresses, then MelissaData is a better bet, as
|
99
|
+
it not only understands P.O. boxes, but it is also able to tell whether a unit
|
100
|
+
or apartment number is left out of the address (although currently this
|
101
|
+
information is not used; there is an
|
102
|
+
[open ticket](https://github.com/mcmire/address_standardization/issues/12) to
|
103
|
+
do this).
|
104
|
+
|
105
|
+
Standardizing a US address works much the same way as with Google:
|
106
|
+
|
107
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
108
|
+
:street => "1600 Amphitheatre Parkway",
|
109
|
+
:city => "Mountain View",
|
110
|
+
:state => "CA"
|
111
|
+
)
|
112
|
+
addr.street #=> "1600 AMPHITHEATRE PKWY"
|
113
|
+
addr.city #=> "MOUNTAIN VIEW"
|
114
|
+
addr.county #=> "SANTA CLARA"
|
115
|
+
addr.state #=> "CA"
|
116
|
+
addr.zip #=> "94043-1351"
|
117
|
+
addr.country #=> "UNITED STATES"
|
118
|
+
|
119
|
+
As mentioned, you can also do P.O. boxes:
|
120
|
+
|
121
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
122
|
+
:street => 'P.O. Box 400160',
|
123
|
+
:city => 'Charlottesville',
|
124
|
+
:state => 'VA'
|
125
|
+
)
|
126
|
+
addr.street #=> "PO BOX 400160"
|
127
|
+
addr.city #=> "CHARLOTTESVILLE"
|
128
|
+
addr.county #=> "ALBEMARLE"
|
129
|
+
addr.state #=> "VA"
|
130
|
+
addr.zip #=> "22904-4160"
|
131
|
+
addr.country #=> "UNITED STATES"
|
132
|
+
|
133
|
+
## How do I hack on it?
|
134
|
+
|
135
|
+
* Run `bundle install` to ensure you have all dependencies.
|
136
|
+
* Add your changes.
|
137
|
+
* Write some tests! Tests are written in RSpec, so `rspec FILE` to run a
|
138
|
+
single test, and `rake test` to run all of the tests.
|
139
|
+
* If you're feeling generous, fork the project and submit a pull request. I'll
|
140
|
+
take a look at it when I can.
|
141
|
+
|
142
|
+
## Contributors
|
143
|
+
|
144
|
+
* Evan Whalen ([evanwhalen](http://github.com/evanwhalen))
|
145
|
+
* Ryan Heneise ([mysmallidea](http://github.com/mysmallidea))
|
146
|
+
|
147
|
+
## Support
|
148
|
+
|
149
|
+
If you need help, you can get in touch with me here on Github, or via:
|
150
|
+
|
151
|
+
* **Twitter**: [@mcmire](http://twitter.com/mcmire)
|
152
|
+
* **Email**: <elliot.winkler@gmail.com>
|
153
|
+
|
154
|
+
## Copyright/License
|
155
|
+
|
156
|
+
(c) 2008-2011 Elliot Winkler. All code in this project is free to use, modify,
|
157
|
+
and redistribute for any purpose, commercial or personal. An attribution, while
|
158
|
+
not required, would be appreciated.
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* MelissaData will let us know if there are other residents in the building that the address points to and thus whether to supply the suite number -- tap into this
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/address_standardization/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ['Elliot Winkler']
|
6
|
+
gem.email = ['elliot.winkler@gmail.com']
|
7
|
+
gem.description = "A tiny Ruby library to quickly standardize a postal address"
|
8
|
+
gem.summary = "A tiny Ruby library to quickly standardize a postal address"
|
9
|
+
gem.homepage = 'http://github.com/mcmire/address_standardization'
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "address-standardization"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = AddressStandardization::VERSION
|
17
|
+
|
18
|
+
# 2.0.1 contains a bug, hardcode to 2.0.0 for now until 2.1 comes out
|
19
|
+
gem.add_runtime_dependency('mechanize', '2.0.0')
|
20
|
+
gem.add_runtime_dependency('httparty', '~> 0.8.1')
|
21
|
+
gem.add_runtime_dependency('logging', '~> 1.6.1')
|
22
|
+
|
23
|
+
gem.add_development_dependency('rspec', '~> 2.7.0')
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# address_standardization: A tiny Ruby library to quickly standardize a postal address.
|
2
|
+
# Copyright (C) 2008-2011 Elliot Winkler. Released under the MIT license.
|
3
|
+
|
4
|
+
require 'logging'
|
5
|
+
|
6
|
+
# TODO: Force users to require MelissaData or GoogleMaps manually
|
7
|
+
# so that no dependency is required without being unused
|
8
|
+
require 'mechanize'
|
9
|
+
require 'httparty'
|
10
|
+
|
11
|
+
here = File.expand_path('..', __FILE__)
|
12
|
+
|
13
|
+
require "#{here}/address_standardization/ruby_ext"
|
14
|
+
require "#{here}/address_standardization/helpers"
|
15
|
+
|
16
|
+
require "#{here}/address_standardization/address"
|
17
|
+
require "#{here}/address_standardization/abstract_service"
|
18
|
+
require "#{here}/address_standardization/melissa_data"
|
19
|
+
require "#{here}/address_standardization/google_maps"
|
20
|
+
|
21
|
+
module AddressStandardization
|
22
|
+
class << self
|
23
|
+
attr_accessor :test_mode
|
24
|
+
alias_method :test_mode?, :test_mode
|
25
|
+
|
26
|
+
def logger
|
27
|
+
@_logger ||= Logging.logger(STDOUT)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
self.test_mode = false
|
32
|
+
|
33
|
+
logger.level = ($DEBUG || ENV['DEBUG']) ? :debug : :info
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
class AbstractService
|
3
|
+
class << self
|
4
|
+
attr_accessor :canned_response
|
5
|
+
|
6
|
+
def standardize_address(address_info)
|
7
|
+
if AddressStandardization.test_mode?
|
8
|
+
get_canned_response(address_info)
|
9
|
+
else
|
10
|
+
get_live_response(address_info)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_canned_response(response, &block)
|
15
|
+
old_response = self.canned_response
|
16
|
+
self.canned_response = response
|
17
|
+
ret = yield
|
18
|
+
self.canned_response = old_response
|
19
|
+
ret
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def get_canned_response(address_info)
|
24
|
+
response = (self.canned_response ||= :success)
|
25
|
+
response == :success ? Address.new(address_info) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_live_response
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def logger
|
33
|
+
AddressStandardization.logger
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
self.canned_response = :success
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
class Address
|
3
|
+
ATTRIBUTE_DEFS = [
|
4
|
+
:street,
|
5
|
+
:city,
|
6
|
+
[:region, :state, :province],
|
7
|
+
[:postal_code, :postcode, :zip],
|
8
|
+
[:district, :county],
|
9
|
+
:country
|
10
|
+
]
|
11
|
+
ATTRIBUTE_NAMES = ATTRIBUTE_DEFS.flatten
|
12
|
+
UNIQUE_ATTRIBUTE_NAMES = ATTRIBUTE_DEFS.map { |attr|
|
13
|
+
Array === attr ? attr[0] : attr
|
14
|
+
}
|
15
|
+
|
16
|
+
ATTRIBUTE_DEFS.each do |duck|
|
17
|
+
duck = [duck] unless Array === duck
|
18
|
+
name, aliases = duck[0], duck[1..-1]
|
19
|
+
|
20
|
+
attr_reader name
|
21
|
+
define_method(:"#{name}=") do |value|
|
22
|
+
instance_variable_set("@#{name}", value.to_s.upcase)
|
23
|
+
end
|
24
|
+
|
25
|
+
aliases.each do |a|
|
26
|
+
alias_method a, name
|
27
|
+
alias_method "#{a}=", "#{name}="
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(attrs={})
|
32
|
+
attrs.symbolize_keys!
|
33
|
+
_assert_valid_attrs!(attrs)
|
34
|
+
attrs.each do |attr, value|
|
35
|
+
__send__("#{attr}=", Helpers.strip_whitespace(value))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def attributes
|
40
|
+
UNIQUE_ATTRIBUTE_NAMES.inject({}) {|h,k| h[k] = __send__(k); h }
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
self.class === other && attributes == other.attributes
|
45
|
+
end
|
46
|
+
|
47
|
+
def _assert_valid_attrs!(attrs)
|
48
|
+
# assume attrs's keys are already symbols
|
49
|
+
valid_keys = ATTRIBUTE_NAMES
|
50
|
+
invalid_keys = attrs.keys - valid_keys
|
51
|
+
if invalid_keys.any?
|
52
|
+
raise ArgumentError, "You gave invalid attributes: #{invalid_keys.join(', ')}. Valid attributes are: #{valid_keys.join(', ')}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
# See http://code.google.com/apis/maps/documentation/geocoding/
|
3
|
+
# for documentation on Google's Geocoding API.
|
4
|
+
class GoogleMaps < AbstractService
|
5
|
+
class << self
|
6
|
+
def api_key; end
|
7
|
+
def api_key=(value)
|
8
|
+
warn "The Google Maps API no longer requires a key, so you are free to remove `AddressStandardization::GoogleMaps.api_key = ...` from your code as it is now a no-op."
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def get_live_response(address_info)
|
13
|
+
# Much of this code was borrowed from GeoKit, specifically:
|
14
|
+
# https://github.com/andre/geokit-gem/blob/master/lib/geokit/geocoders.rb#L530
|
15
|
+
|
16
|
+
address_info = address_info.stringify_keys
|
17
|
+
|
18
|
+
address_str = [
|
19
|
+
address_info["street"],
|
20
|
+
address_info["city"],
|
21
|
+
(address_info["state"] || address_info["province"]),
|
22
|
+
address_info["zip"]
|
23
|
+
].compact.join(" ")
|
24
|
+
address_country = address_info["country"] || "US"
|
25
|
+
|
26
|
+
resp = HTTParty.get("http://maps.google.com/maps/api/geocode/json",
|
27
|
+
:query => {
|
28
|
+
:sensor => 'false',
|
29
|
+
:address => address_str,
|
30
|
+
:bias => address_country.downcase
|
31
|
+
}
|
32
|
+
)
|
33
|
+
data = resp.parsed_response
|
34
|
+
logger.debug <<EOT
|
35
|
+
[GoogleMaps] Response body:
|
36
|
+
--------------------------------------------------------------------------------
|
37
|
+
#{resp.body}
|
38
|
+
--------------------------------------------------------------------------------
|
39
|
+
EOT
|
40
|
+
logger.debug <<EOT
|
41
|
+
[GoogleMaps] Parsed response:
|
42
|
+
--------------------------------------------------------------------------------
|
43
|
+
#{data}
|
44
|
+
--------------------------------------------------------------------------------
|
45
|
+
EOT
|
46
|
+
|
47
|
+
if data['results'].any? && data['status'] == "OK"
|
48
|
+
result = data['results'].first
|
49
|
+
addr = Address.new
|
50
|
+
street = ["", ""]
|
51
|
+
result['address_components'].each do |comp|
|
52
|
+
case
|
53
|
+
when comp['types'].include?("street_number")
|
54
|
+
street[0] = comp['short_name']
|
55
|
+
when comp['types'].include?("route")
|
56
|
+
street[1] = comp['long_name']
|
57
|
+
when comp['types'].include?("locality")
|
58
|
+
addr.city = comp['long_name']
|
59
|
+
when comp['types'].include?("administrative_area_level_1")
|
60
|
+
addr.region = comp['short_name']
|
61
|
+
when comp['types'].include?("postal_code")
|
62
|
+
addr.postal_code = comp['long_name']
|
63
|
+
when comp['types'].include?("country")
|
64
|
+
# addr[:country_code] = comp['short_name']
|
65
|
+
addr.country = comp['long_name']
|
66
|
+
when comp['types'].include?("administrative_area_level_2")
|
67
|
+
addr.district = comp['long_name']
|
68
|
+
end
|
69
|
+
end
|
70
|
+
addr.street = street.join(" ").strip
|
71
|
+
return addr
|
72
|
+
else
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
module Helpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def strip_html(str)
|
6
|
+
str.gsub(/<\/?([^>]+)>/, '')
|
7
|
+
end
|
8
|
+
def strip_newlines(str)
|
9
|
+
str.gsub(/[\r\n]+/, '')
|
10
|
+
end
|
11
|
+
def strip_whitespace(str)
|
12
|
+
strip_newlines(str).squeeze(" ").strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def url_escape(str)
|
16
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
17
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
18
|
+
end.tr(' ', '+')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module AddressStandardization
|
4
|
+
class MelissaData < AbstractService
|
5
|
+
class << self
|
6
|
+
protected
|
7
|
+
def get_live_response(address_info)
|
8
|
+
address_info = address_info.stringify_keys
|
9
|
+
|
10
|
+
is_canada = (address_info["country"].to_s.upcase == "CANADA")
|
11
|
+
|
12
|
+
if is_canada
|
13
|
+
raise "The MelissaData adapter doesn't work for Canadian addresses currently. This is a known issue and will be fixed in a future release."
|
14
|
+
end
|
15
|
+
|
16
|
+
url = "http://www.melissadata.com/lookups/#{action(is_canada)}"
|
17
|
+
params = []
|
18
|
+
attrs_to_fields(is_canada).each do |attr, field|
|
19
|
+
key, val = field, address_info[attr]
|
20
|
+
params << "#{key}=#{Helpers.url_escape(val)}" if val
|
21
|
+
end
|
22
|
+
url << "?" + params.join("&")
|
23
|
+
url << "&FindAddress=Submit"
|
24
|
+
|
25
|
+
addr = Address.new(
|
26
|
+
:country => (is_canada ? "Canada" : "United States")
|
27
|
+
)
|
28
|
+
ua = Mechanize.new
|
29
|
+
logger.debug "[MelissaData] Hitting URL: #{url}"
|
30
|
+
results_page = ua.get(url)
|
31
|
+
logger.debug "[MelissaData] Response body:"
|
32
|
+
logger.debug "--------------------------------------------------"
|
33
|
+
logger.debug results_page.body
|
34
|
+
logger.debug "--------------------------------------------------"
|
35
|
+
|
36
|
+
table = results_page.at("table.Tableresultborder")
|
37
|
+
unless table
|
38
|
+
logger.debug "Couldn't find table"
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
status_row = table.at("div.Titresultableok")
|
42
|
+
unless status_row
|
43
|
+
logger.debug "Couldn't find status_row"
|
44
|
+
return nil
|
45
|
+
end
|
46
|
+
unless status_row.inner_text =~ /Address Verified/
|
47
|
+
logger.debug "Address not verified"
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
main_td = table.search("tr:eq(#{is_canada ? 2 : 3})/td:eq(2)")
|
51
|
+
main_td_s = main_td.inner_html
|
52
|
+
main_td_s.encode!("utf-8") if main_td_s.respond_to?(:encode!)
|
53
|
+
street_part, city_state_zip_part = main_td_s.split("<br>")[0..1]
|
54
|
+
street = Helpers.strip_whitespace(Helpers.strip_html(street_part))
|
55
|
+
if main_td_s.respond_to?(:encode!)
|
56
|
+
# ruby 1.9
|
57
|
+
separator = city_state_zip_part.include?("  ") ? "  " : " "
|
58
|
+
else
|
59
|
+
# ruby 1.8
|
60
|
+
separator = "\240\240"
|
61
|
+
end
|
62
|
+
county_row = table.search('tr.tdresul01')[4]
|
63
|
+
county = county_row.inner_text.match(/County ([A-Za-z ]+)/)[1].strip
|
64
|
+
city, state, zip = Helpers.strip_html(city_state_zip_part).split(separator)
|
65
|
+
addr.street = street
|
66
|
+
addr.city = city
|
67
|
+
addr.province = state
|
68
|
+
addr.postal_code = zip
|
69
|
+
addr.district = county
|
70
|
+
|
71
|
+
return addr
|
72
|
+
end
|
73
|
+
|
74
|
+
def action(is_canada)
|
75
|
+
is_canada ? "CanadianAddressVerify.asp" : "AddressVerify.asp"
|
76
|
+
end
|
77
|
+
|
78
|
+
def attrs_to_fields(is_canada)
|
79
|
+
if is_canada
|
80
|
+
{'street' => 'Street', 'city' => 'city', 'province' => 'Province', 'postalcode' => 'Postcode'}
|
81
|
+
else
|
82
|
+
{'street' => 'Address', 'city' => 'city', 'state' => 'state', 'zip' => 'zip'}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Hash
|
2
|
+
# Return a new hash with all keys converted to strings.
|
3
|
+
def stringify_keys
|
4
|
+
dup.stringify_keys!
|
5
|
+
end
|
6
|
+
|
7
|
+
# Destructively convert all keys to strings.
|
8
|
+
def stringify_keys!
|
9
|
+
keys.each do |key|
|
10
|
+
self[key.to_s] = delete(key)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a new hash with all keys converted to symbols, as long as
|
16
|
+
# they respond to +to_sym+.
|
17
|
+
def symbolize_keys
|
18
|
+
dup.symbolize_keys!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Destructively convert all keys to symbols, as long as they respond
|
22
|
+
# to +to_sym+.
|
23
|
+
def symbolize_keys!
|
24
|
+
keys.each do |key|
|
25
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe AddressStandardization::Address do
|
4
|
+
describe '.new' do
|
5
|
+
it "raises an ArgumentError if the given hash contains unknown attributes" do
|
6
|
+
lambda { described_class.new(:foo => 'bar') }.
|
7
|
+
should raise_error(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "doesn't raise an ArgumentError if given nothing" do
|
11
|
+
lambda { described_class.new }.should_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it "doesn't raise an ArgumentError if given an empty hash" do
|
15
|
+
lambda { described_class.new({}) }.should_not raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't raise an ArgumentError if given a valid hash" do
|
19
|
+
lambda { described_class.new(:street => '111 Main St.') }.should_not raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets the given attributes" do
|
23
|
+
addr = described_class.new(
|
24
|
+
:street => '111 Main St.',
|
25
|
+
:city => 'Boulder',
|
26
|
+
:state => 'CO'
|
27
|
+
)
|
28
|
+
addr.street.should == '111 Main St.'
|
29
|
+
addr.city.should == 'Boulder'
|
30
|
+
addr.state.should == 'CO'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "has a street attribute" do
|
35
|
+
subject.street = 'foo'
|
36
|
+
subject.street.should == 'foo'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "has a city attribute" do
|
40
|
+
subject.city = 'foo'
|
41
|
+
subject.city.should == 'foo'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "has a region attribute (aliased to state and province)" do
|
45
|
+
subject.region = 'foo'
|
46
|
+
subject.region.should == 'foo'
|
47
|
+
subject.state.should == 'foo'
|
48
|
+
subject.province.should == 'foo'
|
49
|
+
|
50
|
+
subject.state = 'bar'
|
51
|
+
subject.region.should == 'bar'
|
52
|
+
subject.state.should == 'bar'
|
53
|
+
subject.province.should == 'bar'
|
54
|
+
|
55
|
+
subject.province = 'baz'
|
56
|
+
subject.region.should == 'baz'
|
57
|
+
subject.state.should == 'baz'
|
58
|
+
subject.province.should == 'baz'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "has a postal_code attribute (aliased to postcode and zip)" do
|
62
|
+
subject.postal_code = 11111
|
63
|
+
subject.postal_code.should == 11111
|
64
|
+
subject.postcode.should == 11111
|
65
|
+
subject.zip.should == 11111
|
66
|
+
|
67
|
+
subject.postal_code = 22222
|
68
|
+
subject.postal_code.should == 22222
|
69
|
+
subject.postcode.should == 22222
|
70
|
+
subject.zip.should == 22222
|
71
|
+
|
72
|
+
subject.postal_code = 33333
|
73
|
+
subject.postal_code.should == 33333
|
74
|
+
subject.postcode.should == 33333
|
75
|
+
subject.zip.should == 33333
|
76
|
+
end
|
77
|
+
|
78
|
+
it "has a district attribute (aliased to county)" do
|
79
|
+
subject.district = 'foo'
|
80
|
+
subject.district.should == 'foo'
|
81
|
+
subject.county.should == 'foo'
|
82
|
+
|
83
|
+
subject.county = 'bar'
|
84
|
+
subject.district.should == 'bar'
|
85
|
+
subject.county.should == 'bar'
|
86
|
+
end
|
87
|
+
|
88
|
+
it "has a country attribute" do
|
89
|
+
subject.country = 'foo'
|
90
|
+
subject.country.should == 'foo'
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#attributes' do
|
94
|
+
it "returns all of the attributes as a hash" do
|
95
|
+
subject.street = '111 Main St.'
|
96
|
+
subject.region = 'Georgia'
|
97
|
+
subject.country = 'United States'
|
98
|
+
subject.attributes.should == {
|
99
|
+
:street => '111 Main St.',
|
100
|
+
:city => nil,
|
101
|
+
:region => 'Georgia',
|
102
|
+
:postal_code => nil,
|
103
|
+
:district => nil,
|
104
|
+
:country => 'United States'
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#==' do
|
110
|
+
it "returns false if the given object is not an described_class" do
|
111
|
+
subject.should_not == :foo
|
112
|
+
end
|
113
|
+
|
114
|
+
it "returns false if the attributes of the given described_class don't match this described_class's attributes" do
|
115
|
+
addr1 = described_class.new(
|
116
|
+
:street => '111 Pearl St.',
|
117
|
+
:city => 'Boulder',
|
118
|
+
:state => 'CO'
|
119
|
+
)
|
120
|
+
addr2 = described_class.new(
|
121
|
+
:street => '111 Pearl Pkwy.',
|
122
|
+
:city => 'Boulder',
|
123
|
+
:state => 'CO'
|
124
|
+
)
|
125
|
+
addr1.should_not == addr2
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns true otherwise" do
|
129
|
+
addr1 = described_class.new(
|
130
|
+
:street => '111 Pearl St.',
|
131
|
+
:city => 'Boulder',
|
132
|
+
:state => 'CO'
|
133
|
+
)
|
134
|
+
addr2 = described_class.new(
|
135
|
+
:street => '111 Pearl St.',
|
136
|
+
:city => 'Boulder',
|
137
|
+
:state => 'CO'
|
138
|
+
)
|
139
|
+
addr1.should == addr2
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe AddressStandardization::GoogleMaps do
|
4
|
+
context 'in production mode' do
|
5
|
+
before do
|
6
|
+
AddressStandardization.test_mode = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the correct data for a valid US address" do
|
10
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
11
|
+
# test that it works regardless of symbols or strings
|
12
|
+
"street" => "1600 Amphitheatre Parkway",
|
13
|
+
:city => "Mountain View",
|
14
|
+
:state => "CA"
|
15
|
+
)
|
16
|
+
addr.attributes.should == {
|
17
|
+
:street => "1600 AMPHITHEATRE PKWY",
|
18
|
+
:city => "MOUNTAIN VIEW",
|
19
|
+
:district => "SANTA CLARA",
|
20
|
+
:region => "CA",
|
21
|
+
:postal_code => "94043",
|
22
|
+
:country => "UNITED STATES"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the correct data for a valid Canadian address" do
|
27
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
28
|
+
# test that it works regardless of symbols or strings
|
29
|
+
:street => "55 East Cordova St. Apt 415",
|
30
|
+
:city => "Vancouver",
|
31
|
+
"province" => "BC"
|
32
|
+
)
|
33
|
+
addr.attributes.should == {
|
34
|
+
:street => "55 E CORDOVA ST",
|
35
|
+
:city => "VANCOUVER",
|
36
|
+
:district => "GREATER VANCOUVER REGIONAL DISTRICT",
|
37
|
+
:region => "BC",
|
38
|
+
:postal_code => "V6A 1K3",
|
39
|
+
:country => "CANADA"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns nothing for an invalid address" do
|
44
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
45
|
+
:street => "123 Imaginary Lane",
|
46
|
+
:city => "Some Town",
|
47
|
+
:state => "AK"
|
48
|
+
)
|
49
|
+
addr.should be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'in test mode' do
|
54
|
+
before do
|
55
|
+
AddressStandardization.test_mode = true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns the correct data for a successful response" do
|
59
|
+
AddressStandardization::GoogleMaps.canned_response = :success
|
60
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
61
|
+
:street => "123 Imaginary Lane",
|
62
|
+
:city => "Some Town",
|
63
|
+
:state => "AK"
|
64
|
+
)
|
65
|
+
addr.attributes.should == {
|
66
|
+
:street => "123 IMAGINARY LANE",
|
67
|
+
:city => "SOME TOWN",
|
68
|
+
:district => nil,
|
69
|
+
:region => "AK",
|
70
|
+
:postal_code => nil,
|
71
|
+
:country => nil
|
72
|
+
}
|
73
|
+
AddressStandardization::GoogleMaps.canned_response = nil
|
74
|
+
|
75
|
+
# block form
|
76
|
+
AddressStandardization::GoogleMaps.with_canned_response(:success) do
|
77
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
78
|
+
:street => "123 Imaginary Lane",
|
79
|
+
:city => "Some Town",
|
80
|
+
:state => "AK"
|
81
|
+
)
|
82
|
+
addr.attributes.should == {
|
83
|
+
:street => "123 IMAGINARY LANE",
|
84
|
+
:city => "SOME TOWN",
|
85
|
+
:district => nil,
|
86
|
+
:region => "AK",
|
87
|
+
:postal_code => nil,
|
88
|
+
:country => nil
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns nothing for an unsuccessful response" do
|
94
|
+
AddressStandardization::GoogleMaps.canned_response = :failure
|
95
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
96
|
+
:street => "123 Imaginary Lane",
|
97
|
+
:city => "Some Town",
|
98
|
+
:state => "AK"
|
99
|
+
)
|
100
|
+
addr.should be_nil
|
101
|
+
AddressStandardization::GoogleMaps.canned_response = nil
|
102
|
+
|
103
|
+
# block form
|
104
|
+
AddressStandardization::GoogleMaps.with_canned_response(:failure) do
|
105
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
106
|
+
:street => "123 Imaginary Lane",
|
107
|
+
:city => "Some Town",
|
108
|
+
:state => "AK"
|
109
|
+
)
|
110
|
+
addr.should be_nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe AddressStandardization::MelissaData do
|
4
|
+
context 'in production mode' do
|
5
|
+
before do
|
6
|
+
AddressStandardization.test_mode = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the correct data for a valid US address" do
|
10
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
11
|
+
# test that it works regardless of symbols or strings
|
12
|
+
"street" => "1600 Amphitheatre Parkway",
|
13
|
+
:city => "Mountain View",
|
14
|
+
:state => "CA"
|
15
|
+
)
|
16
|
+
addr.attributes.should == {
|
17
|
+
:street => "1600 AMPHITHEATRE PKWY",
|
18
|
+
:city => "MOUNTAIN VIEW",
|
19
|
+
:district => "SANTA CLARA",
|
20
|
+
:region => "CA",
|
21
|
+
:postal_code => "94043-1351",
|
22
|
+
:country => "UNITED STATES"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the correct data for a valid Canadian address" do
|
27
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
28
|
+
# test that it works regardless of symbols or strings
|
29
|
+
:street => "55 East Cordova St. Apt 415",
|
30
|
+
:city => "Vancouver",
|
31
|
+
"province" => "BC"
|
32
|
+
)
|
33
|
+
addr.attributes.should == {
|
34
|
+
:street => "55 E CORDOVA ST",
|
35
|
+
:city => "VANCOUVER",
|
36
|
+
:district => "GREATER VANCOUVER REGIONAL DISTRICT",
|
37
|
+
:region => "BC",
|
38
|
+
:postal_code => "V6A 1K3",
|
39
|
+
:country => "CANADA"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns nothing for an invalid address" do
|
44
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
45
|
+
:street => "123 Imaginary Lane",
|
46
|
+
:city => "Some Town",
|
47
|
+
:state => "AK"
|
48
|
+
)
|
49
|
+
addr.should be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'in test mode' do
|
54
|
+
before do
|
55
|
+
AddressStandardization.test_mode = true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns the correct data for a successful response" do
|
59
|
+
AddressStandardization::MelissaData.canned_response = :success
|
60
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
61
|
+
:street => "123 Imaginary Lane",
|
62
|
+
:city => "Some Town",
|
63
|
+
:state => "AK"
|
64
|
+
)
|
65
|
+
addr.attributes.should == {
|
66
|
+
:street => "123 IMAGINARY LANE",
|
67
|
+
:city => "SOME TOWN",
|
68
|
+
:district => nil,
|
69
|
+
:region => "AK",
|
70
|
+
:postal_code => nil,
|
71
|
+
:country => nil
|
72
|
+
}
|
73
|
+
AddressStandardization::MelissaData.canned_response = nil
|
74
|
+
|
75
|
+
# block form
|
76
|
+
AddressStandardization::MelissaData.with_canned_response(:success) do
|
77
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
78
|
+
:street => "123 Imaginary Lane",
|
79
|
+
:city => "Some Town",
|
80
|
+
:state => "AK"
|
81
|
+
)
|
82
|
+
addr.attributes.should == {
|
83
|
+
:street => "123 IMAGINARY LANE",
|
84
|
+
:city => "SOME TOWN",
|
85
|
+
:district => nil,
|
86
|
+
:region => "AK",
|
87
|
+
:postal_code => nil,
|
88
|
+
:country => nil
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns nothing for an unsuccessful response" do
|
94
|
+
AddressStandardization::MelissaData.canned_response = :failure
|
95
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
96
|
+
:street => "123 Imaginary Lane",
|
97
|
+
:city => "Some Town",
|
98
|
+
:state => "AK"
|
99
|
+
)
|
100
|
+
addr.should be_nil
|
101
|
+
AddressStandardization::MelissaData.canned_response = nil
|
102
|
+
|
103
|
+
# block form
|
104
|
+
AddressStandardization::MelissaData.with_canned_response(:failure) do
|
105
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
106
|
+
:street => "123 Imaginary Lane",
|
107
|
+
:city => "Some Town",
|
108
|
+
:state => "AK"
|
109
|
+
)
|
110
|
+
addr.should be_nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: address-standardization
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.2.rc1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Elliot Winkler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mechanize
|
16
|
+
requirement: &70277233513360 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70277233513360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: httparty
|
27
|
+
requirement: &70277233512180 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.8.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70277233512180
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: logging
|
38
|
+
requirement: &70277233510980 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.6.1
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70277233510980
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70277233510300 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70277233510300
|
58
|
+
description: A tiny Ruby library to quickly standardize a postal address
|
59
|
+
email:
|
60
|
+
- elliot.winkler@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .rspec
|
67
|
+
- .rvmrc
|
68
|
+
- Gemfile
|
69
|
+
- Gemfile.lock
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- TODO
|
73
|
+
- address_standardization.gemspec
|
74
|
+
- lib/address_standardization.rb
|
75
|
+
- lib/address_standardization/abstract_service.rb
|
76
|
+
- lib/address_standardization/address.rb
|
77
|
+
- lib/address_standardization/google_maps.rb
|
78
|
+
- lib/address_standardization/helpers.rb
|
79
|
+
- lib/address_standardization/melissa_data.rb
|
80
|
+
- lib/address_standardization/ruby_ext.rb
|
81
|
+
- lib/address_standardization/version.rb
|
82
|
+
- spec/address_spec.rb
|
83
|
+
- spec/google_maps_spec.rb
|
84
|
+
- spec/melissa_data_spec.rb
|
85
|
+
- spec/spec_helper.rb
|
86
|
+
homepage: http://github.com/mcmire/address_standardization
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>'
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.1
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.10
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: A tiny Ruby library to quickly standardize a postal address
|
110
|
+
test_files:
|
111
|
+
- spec/address_spec.rb
|
112
|
+
- spec/google_maps_spec.rb
|
113
|
+
- spec/melissa_data_spec.rb
|
114
|
+
- spec/spec_helper.rb
|