mysmallidea-address_standardization 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/README.md +95 -0
- data/Rakefile +58 -0
- data/TODO +1 -0
- data/address_standardization.gemspec +67 -0
- data/lib/address_standardization.rb +28 -0
- data/lib/address_standardization/abstract_service.rb +31 -0
- data/lib/address_standardization/address.rb +57 -0
- data/lib/address_standardization/class_level_inheritable_attributes.rb +20 -0
- data/lib/address_standardization/google_maps.rb +54 -0
- data/lib/address_standardization/melissa_data.rb +71 -0
- data/lib/address_standardization/ruby_ext.rb +23 -0
- data/lib/address_standardization/version.rb +3 -0
- data/test/google_maps_test.rb +122 -0
- data/test/melissa_data_test.rb +148 -0
- data/test/test_helper.rb +48 -0
- metadata +115 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# address_standardization
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
A tiny Ruby library to quickly standardize a postal address, either through MelissaData or Google Maps.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
If you are using Rails, put this in your environment.rb:
|
10
|
+
|
11
|
+
config.gem 'address_standardization'
|
12
|
+
|
13
|
+
Then run `rake gems:install` to install the gem.
|
14
|
+
|
15
|
+
Otherwise, just run
|
16
|
+
|
17
|
+
gem install address_standardization
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Right now this library supports two services: MelissaData and Google Maps.
|
22
|
+
|
23
|
+
MelissaData provides two services itself: [US address lookup](http://www.melissadata.com/lookups/AddressVerify.asp) and [Canadian address lookup](http://www.melissadata.com/lookups/CanadianAddressVerify.asp). They both work the same way, however. First, here's how to standardize a US address:
|
24
|
+
|
25
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
26
|
+
:street => "1 Infinite Loop",
|
27
|
+
:city => "Cupertino",
|
28
|
+
:state => "CA"
|
29
|
+
)
|
30
|
+
|
31
|
+
This submits the address to MelissaData. If the address can't be found, you'll get back `nil`. But if the address can be found (as in this case), you'll get an instance of `AddressStandardization::Address`. If you store the instance, you can refer to the individual fields like so:
|
32
|
+
|
33
|
+
addr.street #=> "1 INFINITE LOOP"
|
34
|
+
addr.city #=> "CUPERTINO"
|
35
|
+
addr.state #=> "CA"
|
36
|
+
addr.zip #=> "95014-2083"
|
37
|
+
addr.country #=> "USA"
|
38
|
+
|
39
|
+
And standardizing a Canadian address:
|
40
|
+
|
41
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
42
|
+
:street => "103 Metig St",
|
43
|
+
:city => "Sault Ste Marie",
|
44
|
+
:province => "ON",
|
45
|
+
:country => "Canada"
|
46
|
+
)
|
47
|
+
addr.street #=> "103 METIG ST RR 4"
|
48
|
+
addr.city #=> "SAULT STE MARIE"
|
49
|
+
addr.province #=> "ON"
|
50
|
+
addr.postalcode #=> "P6A 5K9"
|
51
|
+
addr.country #=> "CANADA"
|
52
|
+
|
53
|
+
Note that when standardizing a Canadian address, the `:country` must be "Canada" (or "CANADA", or anything like that). Otherwise it will be treated as a US address.
|
54
|
+
|
55
|
+
Also note that I'm referring to the address's province as `province`, but you can also use `state` if you like. Same goes for the postal code -- you can also refer to it as `zip`.
|
56
|
+
|
57
|
+
Using Google Maps to validate an address is just as easy:
|
58
|
+
|
59
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
60
|
+
:street => "1600 Amphitheatre Parkway",
|
61
|
+
:city => "Mountain View",
|
62
|
+
:state => "CA"
|
63
|
+
)
|
64
|
+
addr.street #=> "1600 AMPHITHEATRE PKWY"
|
65
|
+
addr.city #=> "MOUNTAIN VIEW"
|
66
|
+
addr.state #=> "CA"
|
67
|
+
addr.zip #=> "94043"
|
68
|
+
addr.country #=> "USA"
|
69
|
+
|
70
|
+
And, again, a Canadian address:
|
71
|
+
|
72
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
73
|
+
:street => "1770 Stenson Blvd.",
|
74
|
+
:city => "Peterborough",
|
75
|
+
:province => "ON"
|
76
|
+
)
|
77
|
+
addr.street #=> "1770 STENSON BLVD"
|
78
|
+
addr.city #=> "PETERBOROUGH"
|
79
|
+
addr.province #=> "ON"
|
80
|
+
addr.postalcode #=> "K9K"
|
81
|
+
addr.country #=> "CANADA"
|
82
|
+
|
83
|
+
Sharp eyes will notice that the Google Maps API doesn't return the full postal code for Canadian addresses. If you know why this is please let me know (my email address is below).
|
84
|
+
|
85
|
+
## Support
|
86
|
+
|
87
|
+
If you find any bugs with this plugin, feel free to:
|
88
|
+
|
89
|
+
* file a bug report in the [Issues area on Github](http://github.com/mcmire/address_standardization/issues)
|
90
|
+
* fork the [project on Github](http://github.com/mcmire/address_standardization) and send me a pull request
|
91
|
+
* email me (*firstname* dot *lastname* at gmail dot com)
|
92
|
+
|
93
|
+
## Author/License
|
94
|
+
|
95
|
+
(c) 2008-2010 Elliot Winkler. Released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + "/lib/address_standardization/version.rb"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.version = AddressStandardization::VERSION
|
10
|
+
gem.name = "address_standardization"
|
11
|
+
gem.summary = %Q{A tiny Ruby library to quickly standardize a postal address}
|
12
|
+
gem.description = %Q{A tiny Ruby library to quickly standardize a postal address}
|
13
|
+
gem.authors = ["Elliot Winkler"]
|
14
|
+
gem.email = "elliot.winkler@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/mcmire/address_standardization"
|
16
|
+
gem.add_dependency "mechanize"
|
17
|
+
gem.add_development_dependency "mcmire-context"
|
18
|
+
gem.add_development_dependency "mcmire-matchy"
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/*_test.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
task :rcov do
|
42
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
task :test => [:"check_dependencies:development", :check_dependencies]
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rake/rdoctask'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
53
|
+
|
54
|
+
rdoc.rdoc_dir = 'rdoc'
|
55
|
+
rdoc.title = "address_standardization #{version}"
|
56
|
+
rdoc.rdoc_files.include('README*')
|
57
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
end
|
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,67 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mysmallidea-address_standardization}
|
8
|
+
s.version = "0.4.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Elliot Winkler"]
|
12
|
+
s.date = %q{2010-02-01}
|
13
|
+
s.description = %q{A tiny Ruby library to quickly standardize a postal address}
|
14
|
+
s.email = %q{elliot.winkler@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md",
|
17
|
+
"TODO"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"README.md",
|
22
|
+
"Rakefile",
|
23
|
+
"TODO",
|
24
|
+
"address_standardization.gemspec",
|
25
|
+
"lib/address_standardization.rb",
|
26
|
+
"lib/address_standardization/abstract_service.rb",
|
27
|
+
"lib/address_standardization/address.rb",
|
28
|
+
"lib/address_standardization/class_level_inheritable_attributes.rb",
|
29
|
+
"lib/address_standardization/google_maps.rb",
|
30
|
+
"lib/address_standardization/melissa_data.rb",
|
31
|
+
"lib/address_standardization/ruby_ext.rb",
|
32
|
+
"lib/address_standardization/version.rb",
|
33
|
+
"test/google_maps_test.rb",
|
34
|
+
"test/melissa_data_test.rb",
|
35
|
+
"test/test_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/mcmire/address_standardization}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.5}
|
41
|
+
s.summary = %q{A tiny Ruby library to quickly standardize a postal address}
|
42
|
+
s.test_files = [
|
43
|
+
"test/google_maps_test.rb",
|
44
|
+
"test/melissa_data_test.rb",
|
45
|
+
"test/test_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<mechanize>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<mcmire-context>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<mcmire-matchy>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<mechanize>, [">= 0"])
|
58
|
+
s.add_dependency(%q<mcmire-context>, [">= 0"])
|
59
|
+
s.add_dependency(%q<mcmire-matchy>, [">= 0"])
|
60
|
+
end
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<mechanize>, [">= 0"])
|
63
|
+
s.add_dependency(%q<mcmire-context>, [">= 0"])
|
64
|
+
s.add_dependency(%q<mcmire-matchy>, [">= 0"])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# address_standardization: A tiny Ruby library to quickly standardize a postal address.
|
2
|
+
# Copyright (C) 2008-2010 Elliot Winkler. Released under the MIT license.
|
3
|
+
|
4
|
+
require 'mechanize'
|
5
|
+
|
6
|
+
require 'address_standardization/ruby_ext'
|
7
|
+
require 'address_standardization/class_level_inheritable_attributes'
|
8
|
+
|
9
|
+
require 'address_standardization/address'
|
10
|
+
require 'address_standardization/abstract_service'
|
11
|
+
require 'address_standardization/melissa_data'
|
12
|
+
require 'address_standardization/google_maps'
|
13
|
+
|
14
|
+
module AddressStandardization
|
15
|
+
class << self
|
16
|
+
attr_accessor :test_mode
|
17
|
+
alias_method :test_mode?, :test_mode
|
18
|
+
|
19
|
+
attr_accessor :debug_mode
|
20
|
+
alias_method :debug_mode?, :debug_mode
|
21
|
+
|
22
|
+
def debug(*args)
|
23
|
+
puts(*args) if debug_mode?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.test_mode = false
|
27
|
+
self.debug_mode = $DEBUG || ENV["DEBUG"] || false
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
class AbstractService
|
3
|
+
extend ClassLevelInheritableAttributes
|
4
|
+
cattr_inheritable :canned_response
|
5
|
+
self.canned_response = :success
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def standardize_address(address_info)
|
9
|
+
if AddressStandardization.test_mode?
|
10
|
+
get_canned_response(address_info)
|
11
|
+
else
|
12
|
+
get_live_response(address_info)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_canned_response(response, &block)
|
17
|
+
old_response = self.canned_response
|
18
|
+
self.canned_response = response
|
19
|
+
ret = yield
|
20
|
+
self.canned_response = old_response
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def get_canned_response(address_info)
|
26
|
+
response = (self.canned_response ||= :success)
|
27
|
+
response == :success ? Address.new(address_info) : nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# TODO: Rename to address.rb
|
2
|
+
|
3
|
+
module AddressStandardization
|
4
|
+
class StandardizationError < StandardError; end
|
5
|
+
|
6
|
+
class Address
|
7
|
+
class << self
|
8
|
+
attr_accessor :valid_keys
|
9
|
+
end
|
10
|
+
self.valid_keys = %w(street city state province zip postalcode country)
|
11
|
+
|
12
|
+
attr_reader :address_info
|
13
|
+
|
14
|
+
def initialize(address_info)
|
15
|
+
raise NotImplementedError, "You must define valid_keys" unless self.class.valid_keys
|
16
|
+
raise ArgumentError, "No address given!" if address_info.empty?
|
17
|
+
address_info = address_info.stringify_keys
|
18
|
+
validate_keys(address_info)
|
19
|
+
standardize_values!(address_info)
|
20
|
+
@address_info = address_info
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_keys(hash)
|
24
|
+
# assume keys are already stringified
|
25
|
+
invalid_keys = hash.keys - self.class.valid_keys
|
26
|
+
unless invalid_keys.empty?
|
27
|
+
raise ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}. Valid keys are: #{self.class.valid_keys.join(', ')}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(name, *args)
|
32
|
+
name = name.to_s
|
33
|
+
if self.class.valid_keys.include?(name)
|
34
|
+
if args.empty?
|
35
|
+
@address_info[name]
|
36
|
+
else
|
37
|
+
@address_info[name] = standardize_value(args.first)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
super(name.to_sym, *args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
other.kind_of?(self.class) && @address_info == other.address_info
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def standardize_values!(hash)
|
50
|
+
hash.each {|k,v| hash[k] = standardize_value(v) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def standardize_value(value)
|
54
|
+
value ? value.strip_whitespace : ""
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# from <http://railstips.org/2008/6/13/a-class-instance-variable-update>
|
2
|
+
module ClassLevelInheritableAttributes
|
3
|
+
def cattr_inheritable(*args)
|
4
|
+
@cattr_inheritable_attrs ||= [:cattr_inheritable_attrs]
|
5
|
+
@cattr_inheritable_attrs += args
|
6
|
+
args.each do |arg|
|
7
|
+
class_eval %(
|
8
|
+
class << self; attr_accessor :#{arg} end
|
9
|
+
)
|
10
|
+
end
|
11
|
+
@cattr_inheritable_attrs
|
12
|
+
end
|
13
|
+
|
14
|
+
def inherited(subclass)
|
15
|
+
@cattr_inheritable_attrs.each do |inheritable_attribute|
|
16
|
+
instance_var = "@#{inheritable_attribute}"
|
17
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module AddressStandardization
|
2
|
+
# See <http://code.google.com/apis/maps/documentation/geocoding/>
|
3
|
+
class GoogleMaps < AbstractService
|
4
|
+
class << self
|
5
|
+
attr_accessor :api_key
|
6
|
+
|
7
|
+
protected
|
8
|
+
# much of this code was borrowed from GeoKit, thanks...
|
9
|
+
def get_live_response(address_info)
|
10
|
+
raise "API key not specified.\nCall AddressStandardization::GoogleMaps.api_key = '...' before you call .standardize()." unless GoogleMaps.api_key
|
11
|
+
|
12
|
+
address_info = address_info.stringify_keys
|
13
|
+
|
14
|
+
address_str = [
|
15
|
+
address_info["street"],
|
16
|
+
address_info["city"],
|
17
|
+
(address_info["state"] || address_info["province"]),
|
18
|
+
address_info["zip"]
|
19
|
+
].join(" ")
|
20
|
+
url = "http://maps.google.com/maps/geo?q=#{address_str.url_escape}&output=xml&key=#{GoogleMaps.api_key}&oe=utf-8"
|
21
|
+
AddressStandardization.debug "[GoogleMaps] Hitting URL: #{url}"
|
22
|
+
uri = URI.parse(url)
|
23
|
+
res = Net::HTTP.get_response(uri)
|
24
|
+
return unless res.is_a?(Net::HTTPSuccess)
|
25
|
+
|
26
|
+
content = res.body
|
27
|
+
AddressStandardization.debug "[GoogleMaps] Response body:"
|
28
|
+
AddressStandardization.debug "--------------------------------------------------"
|
29
|
+
AddressStandardization.debug content
|
30
|
+
AddressStandardization.debug "--------------------------------------------------"
|
31
|
+
xml = Nokogiri::XML(content)
|
32
|
+
xml.remove_namespaces! # good or bad? I say good.
|
33
|
+
return unless xml.at("/kml/Response/Status/code").inner_text == "200"
|
34
|
+
|
35
|
+
addr = {}
|
36
|
+
|
37
|
+
addr[:street] = get_inner_text(xml, '//ThoroughfareName').to_s
|
38
|
+
addr[:city] = get_inner_text(xml, '//LocalityName').to_s
|
39
|
+
addr[:province] = addr[:state] = get_inner_text(xml, '//AdministrativeAreaName').to_s
|
40
|
+
addr[:zip] = addr[:postalcode] = get_inner_text(xml, '//PostalCodeNumber').to_s
|
41
|
+
addr[:country] = get_inner_text(xml, '//CountryName').to_s
|
42
|
+
|
43
|
+
return if addr[:street] =~ /^\s*$/ or addr[:city] =~ /^\s*$/
|
44
|
+
|
45
|
+
Address.new(addr)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def get_inner_text(xml, xpath)
|
50
|
+
lambda {|x| x && x.inner_text.upcase }.call(xml.at(xpath))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
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
|
+
addr = Address.new(address_info)
|
12
|
+
|
13
|
+
url = "http://www.melissadata.com/lookups/#{action(is_canada)}"
|
14
|
+
params = []
|
15
|
+
attrs_to_fields(is_canada).each do |attr, field|
|
16
|
+
key, val = field, address_info[attr]
|
17
|
+
params << "#{key}=#{val.url_escape}" if val
|
18
|
+
end
|
19
|
+
url << "?" + params.join("&")
|
20
|
+
url << "&FindAddress=Submit"
|
21
|
+
|
22
|
+
#puts "URL: <#{url}>"
|
23
|
+
|
24
|
+
attrs = {:country => (is_canada ? "CANADA" : "USA")}
|
25
|
+
Mechanize.new do |ua|
|
26
|
+
AddressStandardization.debug "[MelissaData] Hitting URL: #{url}"
|
27
|
+
results_page = ua.get(url)
|
28
|
+
AddressStandardization.debug "[MelissaData] Response body:"
|
29
|
+
AddressStandardization.debug "--------------------------------------------------"
|
30
|
+
AddressStandardization.debug results_page.body
|
31
|
+
AddressStandardization.debug "--------------------------------------------------"
|
32
|
+
|
33
|
+
table = results_page.search("table.Tableresultborder")[1]
|
34
|
+
return unless table
|
35
|
+
status_row = table.at("span.Titresultableok")
|
36
|
+
return unless status_row && status_row.inner_text =~ /Address Verified/
|
37
|
+
main_td = table.search("tr:eq(#{is_canada ? 2 : 3})/td:eq(2)")
|
38
|
+
main_td_s = main_td.inner_html
|
39
|
+
main_td_s.encode!("utf-8") if main_td_s.respond_to?(:encode!)
|
40
|
+
street_part, city_state_zip_part = main_td_s.split("<br>")[0..1]
|
41
|
+
street = street_part.strip_html.strip_whitespace
|
42
|
+
if main_td_s.respond_to?(:encode!)
|
43
|
+
# ruby 1.9
|
44
|
+
separator = city_state_zip_part.include?("  ") ? "  " : " "
|
45
|
+
else
|
46
|
+
# ruby 1.8
|
47
|
+
separator = "\240\240"
|
48
|
+
end
|
49
|
+
city, state, zip = city_state_zip_part.strip_html.split(separator)
|
50
|
+
attrs[:street] = street.upcase
|
51
|
+
attrs[:city] = city.upcase
|
52
|
+
attrs[:province] = attrs[:state] = state.upcase
|
53
|
+
attrs[:postalcode] = attrs[:zip] = zip.upcase
|
54
|
+
end
|
55
|
+
Address.new(attrs)
|
56
|
+
end
|
57
|
+
|
58
|
+
def action(is_canada)
|
59
|
+
is_canada ? "CanadianAddressVerify.asp" : "AddressVerify.asp"
|
60
|
+
end
|
61
|
+
|
62
|
+
def attrs_to_fields(is_canada)
|
63
|
+
if is_canada
|
64
|
+
{"street" => 'Street', "city" => 'city', "province" => 'Province', "postalcode" => 'Postcode'}
|
65
|
+
else
|
66
|
+
{"street" => 'Address', "city" => 'city', "state" => 'state', "zip" => 'zip'}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class String
|
2
|
+
def strip_html
|
3
|
+
gsub(/<\/?([^>]+)>/, '')
|
4
|
+
end
|
5
|
+
def strip_newlines
|
6
|
+
gsub(/[\r\n]+/, '')
|
7
|
+
end
|
8
|
+
def strip_whitespace
|
9
|
+
strip_newlines.squeeze(" ").strip
|
10
|
+
end
|
11
|
+
|
12
|
+
def url_escape
|
13
|
+
gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
14
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
15
|
+
end.tr(' ', '+')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Hash
|
20
|
+
def stringify_keys
|
21
|
+
self.inject({}) {|h,(k,v)| h[k.to_s] = v; h }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
AddressStandardization::GoogleMaps.api_key = "ABQIAAAALHg3jKnK9wN9K3_ArJA6TxSTZ2OgdK08l2h0_gdsozNQ-6zpaxQvIY84J7Mh1fAHQrYGI4W27qKZaw"
|
4
|
+
|
5
|
+
class GoogleMapsTest < Test::Unit::TestCase
|
6
|
+
shared "production mode tests" do
|
7
|
+
test "A valid US address" do
|
8
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
9
|
+
"street" => "1600 Amphitheatre Parkway",
|
10
|
+
:city => "Mountain View",
|
11
|
+
:state => "CA"
|
12
|
+
)
|
13
|
+
addr.should == AddressStandardization::Address.new(
|
14
|
+
"street" => "1600 AMPHITHEATRE PKWY",
|
15
|
+
"city" => "MOUNTAIN VIEW",
|
16
|
+
"state" => "CA",
|
17
|
+
"province" => "CA",
|
18
|
+
"postalcode" => "94043",
|
19
|
+
"zip" => "94043",
|
20
|
+
"country" => "USA"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
test "A valid Canadian address" do
|
25
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
26
|
+
:street => "1770 Stenson Boulevard",
|
27
|
+
:city => "Peterborough",
|
28
|
+
"province" => "ON"
|
29
|
+
)
|
30
|
+
addr.should == AddressStandardization::Address.new(
|
31
|
+
"street" => "1770 STENSON BLVD",
|
32
|
+
"city" => "PETERBOROUGH",
|
33
|
+
"state" => "ON",
|
34
|
+
"province" => "ON",
|
35
|
+
"postalcode" => "K9K",
|
36
|
+
"zip" => "K9K",
|
37
|
+
"country" => "CANADA"
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
test "An invalid address" do
|
42
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
43
|
+
:street => "123 Imaginary Lane",
|
44
|
+
:city => "Some Town",
|
45
|
+
:state => "AK"
|
46
|
+
)
|
47
|
+
addr.should == nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "With test mode explicitly false" do
|
52
|
+
setup do
|
53
|
+
AddressStandardization.test_mode = false
|
54
|
+
end
|
55
|
+
uses "production mode tests"
|
56
|
+
end
|
57
|
+
|
58
|
+
context "With test mode implicitly false" do
|
59
|
+
setup do
|
60
|
+
AddressStandardization.test_mode = nil
|
61
|
+
end
|
62
|
+
uses "production mode tests"
|
63
|
+
end
|
64
|
+
|
65
|
+
context "With test mode true" do
|
66
|
+
setup do
|
67
|
+
AddressStandardization.test_mode = true
|
68
|
+
end
|
69
|
+
|
70
|
+
test "Valid address (before and after)" do
|
71
|
+
AddressStandardization::GoogleMaps.canned_response = :success
|
72
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
73
|
+
:street => "123 Imaginary Lane",
|
74
|
+
:city => "Some Town",
|
75
|
+
:state => "AK"
|
76
|
+
)
|
77
|
+
addr.should == AddressStandardization::Address.new(
|
78
|
+
:street => "123 Imaginary Lane",
|
79
|
+
:city => "Some Town",
|
80
|
+
:state => "AK"
|
81
|
+
)
|
82
|
+
AddressStandardization::GoogleMaps.canned_response = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
test "Valid address (block)" do
|
86
|
+
AddressStandardization::GoogleMaps.with_canned_response(:success) do
|
87
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
88
|
+
:street => "123 Imaginary Lane",
|
89
|
+
:city => "Some Town",
|
90
|
+
:state => "AK"
|
91
|
+
)
|
92
|
+
addr.should == AddressStandardization::Address.new(
|
93
|
+
:street => "123 Imaginary Lane",
|
94
|
+
:city => "Some Town",
|
95
|
+
:state => "AK"
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
test "Invalid address (before and after)" do
|
101
|
+
AddressStandardization::GoogleMaps.canned_response = :failure
|
102
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
103
|
+
:street => "123 Imaginary Lane",
|
104
|
+
:city => "Some Town",
|
105
|
+
:state => "AK"
|
106
|
+
)
|
107
|
+
addr.should == nil
|
108
|
+
AddressStandardization::GoogleMaps.canned_response = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
test "Invalid address (block)" do
|
112
|
+
AddressStandardization::GoogleMaps.with_canned_response(:failure) do
|
113
|
+
addr = AddressStandardization::GoogleMaps.standardize_address(
|
114
|
+
:street => "123 Imaginary Lane",
|
115
|
+
:city => "Some Town",
|
116
|
+
:state => "AK"
|
117
|
+
)
|
118
|
+
addr.should == nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MelissaDataTest < Test::Unit::TestCase
|
4
|
+
shared "production mode tests" do
|
5
|
+
test "Valid US address (implicit country)" do
|
6
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
7
|
+
:street => "1 Infinite Loop",
|
8
|
+
"city" => "Cupertino",
|
9
|
+
:state => "CA"
|
10
|
+
)
|
11
|
+
addr.should == AddressStandardization::Address.new(
|
12
|
+
"street" => "1 INFINITE LOOP",
|
13
|
+
"city" => "CUPERTINO",
|
14
|
+
"state" => "CA",
|
15
|
+
"province" => "CA",
|
16
|
+
"zip" => "95014-2083",
|
17
|
+
"postalcode" => "95014-2083",
|
18
|
+
"country" => "USA"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
test "Valid US address (explicit country)" do
|
23
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
24
|
+
:street => "1 Infinite Loop",
|
25
|
+
:city => "Cupertino",
|
26
|
+
"state" => "CA",
|
27
|
+
"country" => "USA"
|
28
|
+
)
|
29
|
+
addr.should == AddressStandardization::Address.new(
|
30
|
+
"street" => "1 INFINITE LOOP",
|
31
|
+
"city" => "CUPERTINO",
|
32
|
+
"state" => "CA",
|
33
|
+
"province" => "CA",
|
34
|
+
"zip" => "95014-2083",
|
35
|
+
"postalcode" => "95014-2083",
|
36
|
+
"country" => "USA"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "Invalid US address" do
|
41
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
42
|
+
:street => "123 Imaginary Lane",
|
43
|
+
:city => "Some Town",
|
44
|
+
:state => "AK"
|
45
|
+
)
|
46
|
+
addr.should == nil
|
47
|
+
end
|
48
|
+
|
49
|
+
test "Valid Canadian address" do
|
50
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
51
|
+
"street" => "3025 Clayhill Rd",
|
52
|
+
:city => "Mississauga",
|
53
|
+
"province" => "ON",
|
54
|
+
:country => "CANADA"
|
55
|
+
)
|
56
|
+
addr.should == AddressStandardization::Address.new(
|
57
|
+
"street" => "3025 CLAYHILL RD",
|
58
|
+
"state" => "ON",
|
59
|
+
"province" => "ON",
|
60
|
+
"city" => "MISSISSAUGA",
|
61
|
+
"zip" => "L5B 4L2",
|
62
|
+
"postalcode" => "L5B 4L2",
|
63
|
+
"country" => "CANADA"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
test "Invalid Canadian address" do
|
68
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
69
|
+
:street => "123 Imaginary Lane",
|
70
|
+
:city => "Some Town",
|
71
|
+
:province => "BC"
|
72
|
+
)
|
73
|
+
addr.should == nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "With test mode explicitly false" do
|
78
|
+
setup do
|
79
|
+
AddressStandardization.test_mode = false
|
80
|
+
end
|
81
|
+
uses "production mode tests"
|
82
|
+
end
|
83
|
+
|
84
|
+
context "With test mode implicitly false" do
|
85
|
+
setup do
|
86
|
+
AddressStandardization.test_mode = nil
|
87
|
+
end
|
88
|
+
uses "production mode tests"
|
89
|
+
end
|
90
|
+
|
91
|
+
context "With test mode true" do
|
92
|
+
setup do
|
93
|
+
AddressStandardization.test_mode = true
|
94
|
+
end
|
95
|
+
|
96
|
+
test "Valid address (before and after)" do
|
97
|
+
AddressStandardization::MelissaData.canned_response = :success
|
98
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
99
|
+
:street => "123 Imaginary Lane",
|
100
|
+
:city => "Some Town",
|
101
|
+
:province => "BC"
|
102
|
+
)
|
103
|
+
addr.should == AddressStandardization::Address.new(
|
104
|
+
:street => "123 Imaginary Lane",
|
105
|
+
:city => "Some Town",
|
106
|
+
:province => "BC"
|
107
|
+
)
|
108
|
+
AddressStandardization::MelissaData.canned_response = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
test "Valid address (block)" do
|
112
|
+
AddressStandardization::MelissaData.with_canned_response(:success) do
|
113
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
114
|
+
:street => "123 Imaginary Lane",
|
115
|
+
:city => "Some Town",
|
116
|
+
:province => "BC"
|
117
|
+
)
|
118
|
+
addr.should == AddressStandardization::Address.new(
|
119
|
+
:street => "123 Imaginary Lane",
|
120
|
+
:city => "Some Town",
|
121
|
+
:province => "BC"
|
122
|
+
)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
test "Invalid address (before and after)" do
|
127
|
+
AddressStandardization::MelissaData.canned_response = :failure
|
128
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
129
|
+
:street => "123 Imaginary Lane",
|
130
|
+
:city => "Some Town",
|
131
|
+
:province => "BC"
|
132
|
+
)
|
133
|
+
addr.should == nil
|
134
|
+
AddressStandardization::MelissaData.canned_response = nil
|
135
|
+
end
|
136
|
+
|
137
|
+
test "Invalid address (block)" do
|
138
|
+
AddressStandardization::MelissaData.with_canned_response(:failure) do
|
139
|
+
addr = AddressStandardization::MelissaData.standardize_address(
|
140
|
+
:street => "123 Imaginary Lane",
|
141
|
+
:city => "Some Town",
|
142
|
+
:province => "BC"
|
143
|
+
)
|
144
|
+
addr.should == nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'mcmire-context'
|
3
|
+
require 'context'
|
4
|
+
gem 'mcmire-matchy'
|
5
|
+
require 'matchy'
|
6
|
+
|
7
|
+
require 'address_standardization'
|
8
|
+
|
9
|
+
module Context
|
10
|
+
class SharedBehavior < Module
|
11
|
+
# Fix this so that sharing a module twice within two sub-contexts
|
12
|
+
# doesn't fail with something like "'test: blah blah' is already defined"
|
13
|
+
def included(arg) # :nodoc:
|
14
|
+
arg.instance_eval(&@_behavior)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << Test::Unit::TestCase
|
20
|
+
# Modify this so that the constant is set within the current context (and sub-contexts)
|
21
|
+
def shared(name, &block)
|
22
|
+
case name.class.name
|
23
|
+
when "String"
|
24
|
+
name = name.to_module_name
|
25
|
+
when "Symbol"
|
26
|
+
name = name.to_s.to_module_name
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Provide a String or Symbol as the name of the shared behavior group"
|
29
|
+
end
|
30
|
+
const_set(name, Context::SharedBehavior.create_from_behavior(block))
|
31
|
+
end
|
32
|
+
%w(shared_behavior share_as share_behavior_as shared_examples_for).each {|m| alias_method m, :shared}
|
33
|
+
|
34
|
+
# Modify this so that we look in the current context (and any super-contexts) for the module
|
35
|
+
def use(shared_name)
|
36
|
+
case shared_name.class.name
|
37
|
+
when "Context::SharedBehavior", "Module"
|
38
|
+
include shared_name
|
39
|
+
when "String"
|
40
|
+
include const_get(shared_name.to_module_name)
|
41
|
+
when "Symbol"
|
42
|
+
include const_get(shared_name.to_s.to_module_name)
|
43
|
+
else
|
44
|
+
raise ArgumentError, "Provide a String or Symbol as the name of the shared behavior group or the module name"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
%w(uses it_should_behave_like behaves_like uses_examples_from).each {|m| alias_method m, :use}
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysmallidea-address_standardization
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 1
|
9
|
+
version: 0.4.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Elliot Winkler
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-02-01 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mechanize
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: mcmire-context
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id002
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: mcmire-matchy
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
type: :development
|
55
|
+
version_requirements: *id003
|
56
|
+
description: A tiny Ruby library to quickly standardize a postal address
|
57
|
+
email: elliot.winkler@gmail.com
|
58
|
+
executables: []
|
59
|
+
|
60
|
+
extensions: []
|
61
|
+
|
62
|
+
extra_rdoc_files:
|
63
|
+
- README.md
|
64
|
+
- TODO
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- TODO
|
70
|
+
- address_standardization.gemspec
|
71
|
+
- lib/address_standardization.rb
|
72
|
+
- lib/address_standardization/abstract_service.rb
|
73
|
+
- lib/address_standardization/address.rb
|
74
|
+
- lib/address_standardization/class_level_inheritable_attributes.rb
|
75
|
+
- lib/address_standardization/google_maps.rb
|
76
|
+
- lib/address_standardization/melissa_data.rb
|
77
|
+
- lib/address_standardization/ruby_ext.rb
|
78
|
+
- lib/address_standardization/version.rb
|
79
|
+
- test/google_maps_test.rb
|
80
|
+
- test/melissa_data_test.rb
|
81
|
+
- test/test_helper.rb
|
82
|
+
has_rdoc: true
|
83
|
+
homepage: http://github.com/mcmire/address_standardization
|
84
|
+
licenses: []
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options:
|
88
|
+
- --charset=UTF-8
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.3.6
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: A tiny Ruby library to quickly standardize a postal address
|
112
|
+
test_files:
|
113
|
+
- test/google_maps_test.rb
|
114
|
+
- test/melissa_data_test.rb
|
115
|
+
- test/test_helper.rb
|