diff --git a/Gemfile.lock b/Gemfile.lock
index 3359999..02b9772 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,17 +2,23 @@ PATH
remote: .
specs:
address-standardization (0.4.1)
+ httparty
mechanize (~> 2.0.1)
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.3)
+ httparty (0.8.1)
+ multi_json
+ multi_xml
mechanize (2.0.1)
net-http-digest_auth (~> 1.1, >= 1.1.1)
net-http-persistent (~> 1.8)
nokogiri (~> 1.4)
webrobots (~> 0.0, >= 0.0.9)
+ multi_json (1.0.4)
+ multi_xml (0.4.1)
net-http-digest_auth (1.2)
net-http-persistent (1.9)
nokogiri (1.5.0)
diff --git a/README.md b/README.md
index aa04e83..9b5d52a 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ MelissaData provides two services itself: [US address lookup](http://www.melissa
:city => "Cupertino",
:state => "CA"
)
-
+
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:
addr.street #=> "1 INFINITE LOOP"
@@ -61,26 +61,26 @@ Using Google Maps to validate an address is just as easy:
:city => "Mountain View",
:state => "CA"
)
- addr.street #=> "1600 AMPHITHEATRE PKWY"
- addr.city #=> "MOUNTAIN VIEW"
+ addr.street #=> "1600 Amphitheatre Pkwy",
+ addr.city #=> "Mountain View"
+ addr.county #=> "Santa Clara"
addr.state #=> "CA"
addr.zip #=> "94043"
- addr.country #=> "USA"
-
+ addr.country #=> "United States"
+
And, again, a Canadian address:
addr = AddressStandardization::GoogleMaps.standardize_address(
- :street => "1770 Stenson Blvd.",
- :city => "Peterborough",
- :province => "ON"
+ :street => "55 East Cordova St. Apt 415",
+ :city => "Vancouver",
+ :province => "BC"
)
- addr.street #=> "1770 STENSON BLVD"
- addr.city #=> "PETERBOROUGH"
+ addr.street #=> "55 E Cordova St"
+ addr.city #=> "Vancouver"
+ addr.county #=> "Greater Vancouver Regional District"
addr.province #=> "ON"
- addr.postalcode #=> "K9K"
- addr.country #=> "CANADA"
-
-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).
+ addr.postalcode #=> "V6A 1K3"
+ addr.country #=> "Canada"
## Support
@@ -92,4 +92,4 @@ If you find any bugs with this plugin, feel free to:
## Author/License
-(c) 2008-2010 Elliot Winkler. Released under the MIT license.
\ No newline at end of file
+(c) 2008-2010 Elliot Winkler. Released under the MIT license.
diff --git a/address_standardization.gemspec b/address_standardization.gemspec
index c4c61d2..ca7ed78 100644
--- a/address_standardization.gemspec
+++ b/address_standardization.gemspec
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
gem.version = AddressStandardization::VERSION
gem.add_runtime_dependency('mechanize', '~> 2.0.1')
+ gem.add_runtime_dependency('httparty', '~> 0.8.1')
gem.add_development_dependency('rspec', '~> 2.7.0')
end
diff --git a/lib/address_standardization.rb b/lib/address_standardization.rb
index 2e3047f..ebaa89b 100644
--- a/lib/address_standardization.rb
+++ b/lib/address_standardization.rb
@@ -1,28 +1,32 @@
# address_standardization: A tiny Ruby library to quickly standardize a postal address.
# Copyright (C) 2008-2010 Elliot Winkler. Released under the MIT license.
+# TODO: Force users to require MelissaData or GoogleMaps manually
+# so that no dependency is required without being unused
require 'mechanize'
+require 'httparty'
-require 'address_standardization/ruby_ext'
-require 'address_standardization/class_level_inheritable_attributes'
-
-require 'address_standardization/address'
-require 'address_standardization/abstract_service'
-require 'address_standardization/melissa_data'
-require 'address_standardization/google_maps'
+here = File.expand_path('..', __FILE__)
+require "#{here}/address_standardization/ruby_ext"
+require "#{here}/address_standardization/class_level_inheritable_attributes"
+
+require "#{here}/address_standardization/address"
+require "#{here}/address_standardization/abstract_service"
+require "#{here}/address_standardization/melissa_data"
+require "#{here}/address_standardization/google_maps"
module AddressStandardization
class << self
attr_accessor :test_mode
alias_method :test_mode?, :test_mode
-
+
attr_accessor :debug_mode
alias_method :debug_mode?, :debug_mode
-
+
def debug(*args)
puts(*args) if debug_mode?
end
end
self.test_mode = false
self.debug_mode = $DEBUG || ENV["DEBUG"] || false
-end
\ No newline at end of file
+end
diff --git a/lib/address_standardization/address.rb b/lib/address_standardization/address.rb
index f8b3eab..337cc74 100644
--- a/lib/address_standardization/address.rb
+++ b/lib/address_standardization/address.rb
@@ -1,16 +1,16 @@
-# TODO: Rename to address.rb
-
module AddressStandardization
class StandardizationError < StandardError; end
-
+
+ # TODO: Rewrite this class so keys are attr_accessorized
+ # TODO: Alias county to district
class Address
class << self
attr_accessor :valid_keys
end
self.valid_keys = %w(street city state province zip postalcode country county)
-
+
attr_reader :address_info
-
+
def initialize(address_info)
raise NotImplementedError, "You must define valid_keys" unless self.class.valid_keys
raise ArgumentError, "No address given!" if address_info.empty?
@@ -19,7 +19,7 @@ def initialize(address_info)
standardize_values!(address_info)
@address_info = address_info
end
-
+
def validate_keys(hash)
# assume keys are already stringified
invalid_keys = hash.keys - self.class.valid_keys
@@ -27,7 +27,7 @@ def validate_keys(hash)
raise ArgumentError, "Invalid keys: #{invalid_keys.join(', ')}. Valid keys are: #{self.class.valid_keys.join(', ')}"
end
end
-
+
def method_missing(name, *args)
name = name.to_s
if self.class.valid_keys.include?(name)
@@ -40,18 +40,18 @@ def method_missing(name, *args)
super(name.to_sym, *args)
end
end
-
+
def ==(other)
other.kind_of?(self.class) && @address_info == other.address_info
end
-
+
private
def standardize_values!(hash)
hash.each {|k,v| hash[k] = standardize_value(v) }
end
-
+
def standardize_value(value)
value ? value.strip_whitespace : ""
end
end
-end
\ No newline at end of file
+end
diff --git a/lib/address_standardization/google_maps.rb b/lib/address_standardization/google_maps.rb
index 61cdb4a..bb69f19 100644
--- a/lib/address_standardization/google_maps.rb
+++ b/lib/address_standardization/google_maps.rb
@@ -1,55 +1,78 @@
module AddressStandardization
- # See
+ # See http://code.google.com/apis/maps/documentation/geocoding/
+ # for documentation on Google's Geocoding API.
class GoogleMaps < AbstractService
class << self
- attr_accessor :api_key
-
+ def api_key; end
+ def api_key=(value)
+ 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."
+ end
+
protected
- # much of this code was borrowed from GeoKit, thanks...
def get_live_response(address_info)
- raise "API key not specified.\nCall AddressStandardization::GoogleMaps.api_key = '...' before you call .standardize()." unless GoogleMaps.api_key
-
+ # Much of this code was borrowed from GeoKit, specifically:
+ # https://github.com/andre/geokit-gem/blob/master/lib/geokit/geocoders.rb#L530
+
address_info = address_info.stringify_keys
-
+
address_str = [
address_info["street"],
address_info["city"],
(address_info["state"] || address_info["province"]),
address_info["zip"]
].compact.join(" ")
- url = "http://maps.google.com/maps/geo?q=#{address_str.url_escape}&output=xml&key=#{GoogleMaps.api_key}&oe=utf-8"
- AddressStandardization.debug "[GoogleMaps] Hitting URL: #{url}"
- uri = URI.parse(url)
- res = Net::HTTP.get_response(uri)
- return unless res.is_a?(Net::HTTPSuccess)
-
- content = res.body
- AddressStandardization.debug "[GoogleMaps] Response body:"
- AddressStandardization.debug "--------------------------------------------------"
- AddressStandardization.debug content
- AddressStandardization.debug "--------------------------------------------------"
- xml = Nokogiri::XML(content)
- xml.remove_namespaces! # good or bad? I say good.
- return unless xml.at("/kml/Response/Status/code").inner_text == "200"
-
- addr = {}
-
- addr[:street] = get_inner_text(xml, '//ThoroughfareName').to_s
- addr[:city] = get_inner_text(xml, '//LocalityName').to_s
- addr[:province] = addr[:state] = get_inner_text(xml, '//AdministrativeAreaName').to_s
- addr[:zip] = addr[:postalcode] = get_inner_text(xml, '//PostalCodeNumber').to_s
- addr[:country] = get_inner_text(xml, '//CountryName').to_s
- addr[:county] = get_inner_text(xml, '//SubAdministrativeAreaName').to_s
-
- return if addr[:street] =~ /^\s*$/ or addr[:city] =~ /^\s*$/
-
- Address.new(addr)
- end
-
- private
- def get_inner_text(xml, xpath)
- lambda {|x| x && x.inner_text.upcase }.call(xml.at(xpath))
+ address_country = address_info["country"] || "US"
+
+ resp = HTTParty.get("http://maps.google.com/maps/api/geocode/json",
+ :query => {
+ :sensor => 'false',
+ :address => address_str,
+ :bias => address_country.downcase
+ }
+ )
+ data = resp.parsed_response
+ AddressStandardization.debug < "CA"
)
addr.should == AddressStandardization::Address.new(
- "street" => "1600 AMPHITHEATRE PKWY",
- "city" => "MOUNTAIN VIEW",
+ "street" => "1600 Amphitheatre Pkwy",
+ "city" => "Mountain View",
+ "county" => "Santa Clara",
"state" => "CA",
"province" => "CA",
"postalcode" => "94043",
"zip" => "94043",
- "country" => "USA"
+ "country" => "United States"
)
end
@@ -34,13 +33,14 @@
"province" => "BC"
)
addr.should == AddressStandardization::Address.new(
- "street" => "55 CORDOVA ST E #415",
- "city" => "VANCOUVER",
+ "street" => "55 E Cordova St",
+ "city" => "Vancouver",
+ "county" => "Greater Vancouver Regional District",
"state" => "BC",
"province" => "BC",
- "postalcode" => "V6A",
- "zip" => "V6A",
- "country" => "CANADA"
+ "postalcode" => "V6A 1K3",
+ "zip" => "V6A 1K3",
+ "country" => "Canada"
)
end