From 7e2a8ba0f848e0d82a8f914fe8972fde5b7eb3b7 Mon Sep 17 00:00:00 2001
From: Alex Reisner <alex@alexreisner.com>
Date: Sun, 23 Dec 2012 23:15:17 -0500
Subject: [PATCH] Implement support for all MaxMind services.

Now it is necessary to specify which service is being used:
  Geocoder.configure(:maxmind => {:service => ...})
where '...' is one of: < :country | :city | :city_isp_org | :omni >
---
 lib/geocoder/lookups/maxmind.rb     |  36 ++++++--
 lib/geocoder/results/maxmind.rb     | 129 ++++++++++++++++++++++------
 test/fixtures/maxmind_24_24_24_21   |   1 +
 test/fixtures/maxmind_24_24_24_22   |   1 +
 test/fixtures/maxmind_24_24_24_23   |   1 +
 test/fixtures/maxmind_24_24_24_24   |   1 +
 test/fixtures/maxmind_74_200_247_59 |   2 +-
 test/services_test.rb               |  40 +++++++--
 test/test_helper.rb                 |   7 +-
 9 files changed, 181 insertions(+), 37 deletions(-)
 create mode 100644 test/fixtures/maxmind_24_24_24_21
 create mode 100644 test/fixtures/maxmind_24_24_24_22
 create mode 100644 test/fixtures/maxmind_24_24_24_23
 create mode 100644 test/fixtures/maxmind_24_24_24_24

diff --git a/lib/geocoder/lookups/maxmind.rb b/lib/geocoder/lookups/maxmind.rb
index e0275033..0be2495c 100644
--- a/lib/geocoder/lookups/maxmind.rb
+++ b/lib/geocoder/lookups/maxmind.rb
@@ -10,21 +10,45 @@ module Geocoder::Lookup
     end
 
     def query_url(query)
-      "#{protocol}://geoip3.maxmind.com/f?" + url_query_string(query)
+      "#{protocol}://geoip.maxmind.com/#{service_code}?" + url_query_string(query)
     end
 
     private # ---------------------------------------------------------------
 
+    def service_code
+      if s = configuration[:service] and services.keys.include?(s)
+        services[s]
+      else
+        raise(
+          Geocoder::ConfigurationError,
+          "When using MaxMind you MUST specify a service name: " +
+          "Geocoder.configure(:maxmind => {:service => ...}), " +
+          "where '...' is one of: #{services.keys.inspect}"
+        )
+      end
+    end
+
+    ##
+    # Service names mapped to code used in URL.
+    #
+    def services
+      {
+        :country => "a",
+        :city => "b",
+        :city_isp_org => "f",
+        :omni => "e"
+      }
+    end
+
     def results(query)
       # don't look up a loopback address, just return the stored result
       return [reserved_result] if query.loopback_ip_address?
       doc = fetch_data(query)
       if doc and doc.is_a?(Array)
-        if doc.size == 10
+        if doc.last.nil?
           return [doc]
-        elsif doc.size > 10 and doc[10] == "INVALID_LICENSE_KEY"
-          raise_error(Geocoder::InvalidApiKey) ||
-            warn("Invalid MaxMind API key.")
+        elsif doc.last == "INVALID_LICENSE_KEY"
+          raise_error(Geocoder::InvalidApiKey) || warn("Invalid MaxMind API key.")
         end
       end
       return []
@@ -35,7 +59,7 @@ module Geocoder::Lookup
     end
 
     def reserved_result
-      ",,,,0,0,0,0,,"
+      ",,,,0,0,0,0,,,"
     end
 
     def query_url_params(query)
diff --git a/lib/geocoder/results/maxmind.rb b/lib/geocoder/results/maxmind.rb
index d787ab77..2cc45beb 100644
--- a/lib/geocoder/results/maxmind.rb
+++ b/lib/geocoder/results/maxmind.rb
@@ -3,53 +3,134 @@ require 'geocoder/results/base'
 module Geocoder::Result
   class Maxmind < Base
 
-    def address(format = :full)
-      s = state_code.to_s == "" ? "" : ", #{state_code}"
-      "#{city}#{s} #{postal_code}, #{country_code}".sub(/^[ ,]*/, "")
-    end
+    ##
+    # Hash mapping service names to names of returned fields.
+    #
+    def self.field_names
+      {
+        :country => [
+          :country_code,
+          :error
+        ],
 
-    def country_code
-      @data[0]
+        :city => [
+          :country_code,
+          :region_code,
+          :city_name,
+          :latitude,
+          :longitude,
+          :error
+        ],
+
+        :city_isp_org => [
+          :country_code,
+          :region_code,
+          :city_name,
+          :postal_code,
+          :latitude,
+          :longitude,
+          :metro_code,
+          :area_code,
+          :isp_name,
+          :organization_name,
+          :error
+        ],
+
+        :omni => [
+          :country_code,
+          :country_name,
+          :region_code,
+          :region_name,
+          :city_name,
+          :latitude,
+          :longitude,
+          :metro_code,
+          :area_code,
+          :time_zone,
+          :continent_code,
+          :postal_code,
+          :isp_name,
+          :organization_name,
+          :domain,
+          :as_number,
+          :netspeed,
+          :user_type,
+          :accuracy_radius,
+          :country_confidence_factor,
+          :city_confidence_factor,
+          :region_confidence_factor,
+          :postal_confidence_factor,
+          :error
+        ]
+      }
     end
 
-    def state_code
-      @data[1]
+    ##
+    # Name of the MaxMind service being used.
+    # Inferred from format of @data.
+    #
+    def service_name
+      self.class.field_names.to_a.each do |n,f|
+        return n if f.size == @data.size
+      end
+      nil
     end
 
-    def city
-      @data[2]
+    def field_names
+      self.class.field_names[service_name]
     end
 
-    def postal_code
-      @data[3]
+    def data_hash
+      @data_hash ||= Hash[*field_names.zip(@data).flatten]
     end
 
     def coordinates
-      [@data[4].to_f, @data[5].to_f]
+      [data_hash[:latitude].to_f, data_hash[:longitude].to_f]
     end
 
-    def metrocode
-      @data[6]
+    def address(format = :full)
+      s = state_code.to_s == "" ? "" : ", #{state_code}"
+      "#{city}#{s} #{postal_code}, #{country_code}".sub(/^[ ,]*/, "")
     end
 
-    def area_code
-      @data[7]
+    def city
+      data_hash[:city_name]
     end
 
-    def isp
-      @data[8]
+    def state # not given by MaxMind
+      data_hash[:region_name] || data_hash[:region_code]
     end
 
-    def organization
-      @data[9]
+    def state_code
+      data_hash[:region_code]
     end
 
     def country #not given by MaxMind
-      country_code
+      data_hash[:country_name] || data_hash[:country_code]
+    end
+
+    def country_code
+      data_hash[:country_code]
+    end
+
+    def postal_code
+      data_hash[:postal_code]
+    end
+
+    def method_missing(method, *args, &block)
+      if field_names.include?(method)
+        data_hash[method]
+      else
+        super
+      end
     end
 
-    def state #not given by MaxMind
-      state_code
+    def respond_to?(method)
+      if field_names.include?(method)
+        true
+      else
+        super
+      end
     end
   end
 end
diff --git a/test/fixtures/maxmind_24_24_24_21 b/test/fixtures/maxmind_24_24_24_21
new file mode 100644
index 00000000..3fde7152
--- /dev/null
+++ b/test/fixtures/maxmind_24_24_24_21
@@ -0,0 +1 @@
+US,
diff --git a/test/fixtures/maxmind_24_24_24_22 b/test/fixtures/maxmind_24_24_24_22
new file mode 100644
index 00000000..426280ed
--- /dev/null
+++ b/test/fixtures/maxmind_24_24_24_22
@@ -0,0 +1 @@
+US,NY,Jamaica,40.6915,-73.8057,
diff --git a/test/fixtures/maxmind_24_24_24_23 b/test/fixtures/maxmind_24_24_24_23
new file mode 100644
index 00000000..8ffaf07f
--- /dev/null
+++ b/test/fixtures/maxmind_24_24_24_23
@@ -0,0 +1 @@
+US,NY,Jamaica,,40.6915,-73.8057,501,718,"Road Runner","Road Runner",
diff --git a/test/fixtures/maxmind_24_24_24_24 b/test/fixtures/maxmind_24_24_24_24
new file mode 100644
index 00000000..ab20e037
--- /dev/null
+++ b/test/fixtures/maxmind_24_24_24_24
@@ -0,0 +1 @@
+US,"United States",NY,"New York",Jamaica,40.6915,-73.8057,501,718,America/New_York,NA,,"Road Runner","Road Runner",rr.com,"AS11351 Road Runner HoldCo LLC",Cable/DSL,residential,779,99,37,76,,
diff --git a/test/fixtures/maxmind_74_200_247_59 b/test/fixtures/maxmind_74_200_247_59
index 22fce339..9eb1bd01 100644
--- a/test/fixtures/maxmind_74_200_247_59
+++ b/test/fixtures/maxmind_74_200_247_59
@@ -1 +1 @@
-US,TX,Plano,75093,33.034698,-96.813400,623,972,"Layered Technologies , US","Layered Technologies , US"
\ No newline at end of file
+US,TX,Plano,75093,33.034698,-96.813400,623,972,"Layered Technologies , US","Layered Technologies , US",
diff --git a/test/services_test.rb b/test/services_test.rb
index 30968707..551040c9 100644
--- a/test/services_test.rb
+++ b/test/services_test.rb
@@ -154,15 +154,45 @@ class ServicesTest < Test::Unit::TestCase
   # --- MaxMind ---
 
   def test_maxmind_result_on_ip_address_search
-    Geocoder::Configuration.ip_lookup = :maxmind
+    Geocoder.configure(:ip_lookup => :maxmind, :maxmind => {:service => :city_isp_org})
     result = Geocoder.search("74.200.247.59").first
     assert result.is_a?(Geocoder::Result::Maxmind)
   end
 
-  def test_maxmind_result_components
-    Geocoder::Configuration.ip_lookup = :maxmind
-    result = Geocoder.search("74.200.247.59").first
-    assert_equal "Plano, TX 75093, US", result.address
+  def test_maxmind_result_knows_country_service_name
+    Geocoder.configure(:ip_lookup => :maxmind)
+    assert_equal :country, Geocoder.search("24.24.24.21").first.service_name
+  end
+
+  def test_maxmind_result_knows_city_service_name
+    Geocoder.configure(:ip_lookup => :maxmind)
+    assert_equal :city, Geocoder.search("24.24.24.22").first.service_name
+  end
+
+  def test_maxmind_result_knows_city_isp_org_service_name
+    Geocoder.configure(:ip_lookup => :maxmind)
+    assert_equal :city_isp_org, Geocoder.search("24.24.24.23").first.service_name
+  end
+
+  def test_maxmind_result_knows_omni_service_name
+    Geocoder.configure(:ip_lookup => :maxmind)
+    assert_equal :omni, Geocoder.search("24.24.24.24").first.service_name
+  end
+
+  def test_maxmind_special_result_components
+    Geocoder.configure(:ip_lookup => :maxmind)
+    result = Geocoder.search("24.24.24.24").first
+    assert_equal "Road Runner", result.isp_name
+    assert_equal "Cable/DSL", result.netspeed
+    assert_equal "rr.com", result.domain
+  end
+
+  def test_maxmind_raises_exception_when_service_not_configured
+    Geocoder.configure(:ip_lookup => :maxmind)
+    Geocoder.configure(:maxmind => {:service => nil})
+    assert_raises Geocoder::ConfigurationError do
+      Geocoder::Query.new("24.24.24.24").url
+    end
   end
 
 
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 6fca5010..2fe194c1 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -102,7 +102,8 @@ module Geocoder
         if query.reverse_geocode?
           filename = "#{fixture_prefix}_reverse"
         else
-          filename = "#{fixture_prefix}_#{query.text.gsub(" ", "_")}"
+          label = query.text.gsub(/[ \.]/, "_")
+          filename = "#{fixture_prefix}_#{label}"
         end
         if fixture_exists?(filename)
           read_fixture "#{filename}"
@@ -238,6 +239,10 @@ end
 
 class Test::Unit::TestCase
 
+  def setup
+    Geocoder.configure(:maxmind => {:service => :omni})
+  end
+
   def teardown
     Geocoder.send(:remove_const, :Configuration)
     load "geocoder/configuration.rb"
-- 
GitLab