address-standardization 0.4.2.rc1
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.
- 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
|