From fd5f7e09f4bd50a20876f41f947af4d5e470b5c6 Mon Sep 17 00:00:00 2001
From: Alex Reisner <alex@alexreisner.com>
Date: Fri, 4 Mar 2011 01:15:34 -0500
Subject: [PATCH] Add IP address geocoding.

---
 lib/geocoder.rb                            | 51 ++++++++++++++++------
 lib/geocoder/lookups/freegeoip.rb          | 23 ++++++++++
 lib/geocoder/results/freegeoip.rb          | 25 +++++++++++
 test/fixtures/freegeoip_74_200_247_59.json | 12 +++++
 test/geocoder_test.rb                      | 14 ++++--
 test/test_helper.rb                        | 18 +++++++-
 6 files changed, 124 insertions(+), 19 deletions(-)
 create mode 100644 lib/geocoder/lookups/freegeoip.rb
 create mode 100644 lib/geocoder/results/freegeoip.rb
 create mode 100644 test/fixtures/freegeoip_74_200_247_59.json

diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 372e49c9..b7c8be64 100644
--- a/lib/geocoder.rb
+++ b/lib/geocoder.rb
@@ -7,14 +7,16 @@ module Geocoder
   extend self
 
   ##
-  # Alias for Geocoder.lookup.search.
+  # Search for information about an address or a set of coordinates.
   #
   def search(*args)
-    lookup.search(*args)
+    return [] if args[0].nil? || args[0] == ""
+    ip = (args.size == 1 and ip_address?(args.first))
+    lookup(ip).search(*args)
   end
 
   ##
-  # Look up the coordinates of the given street address.
+  # Look up the coordinates of the given street or IP address.
   #
   def coordinates(address)
     if (results = search(address)).size > 0
@@ -41,23 +43,44 @@ module Geocoder
 
   ##
   # Get the lookup object (which communicates with the remote geocoding API).
+  # Returns an IP address lookup if +ip+ parameter true.
   #
-  def lookup
-    unless defined?(@lookup)
-      set_lookup Geocoder::Configuration.lookup
+  def lookup(ip = false)
+    if ip
+      get_lookup :freegeoip
+    else
+      get_lookup Geocoder::Configuration.lookup || :google
     end
-    @lookup
   end
 
-  def set_lookup(value)
-    if value == :yahoo
-      require "geocoder/lookups/yahoo"
-      @lookup = Geocoder::Lookup::Yahoo.new
-    else
-      require "geocoder/lookups/google"
-      @lookup = Geocoder::Lookup::Google.new
+  def get_lookup(name)
+    unless defined?(@lookups)
+      @lookups = {}
+    end
+    unless @lookups.include?(name)
+      @lookups[name] = spawn_lookup(name)
+    end
+    @lookups[name]
+  end
+
+  def spawn_lookup(name)
+    if valid_lookups.include?(name)
+      name = name.to_s
+      require "geocoder/lookups/#{name}"
+      eval("Geocoder::Lookup::#{name[0...1].upcase + name[1..-1]}.new")
     end
   end
+
+  def valid_lookups
+    [:google, :yahoo, :freegeoip]
+  end
+
+  ##
+  # Does the given value look like an IP address?
+  #
+  def ip_address?(value)
+    value.match /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
+  end
 end
 
 Geocoder::Railtie.insert
diff --git a/lib/geocoder/lookups/freegeoip.rb b/lib/geocoder/lookups/freegeoip.rb
new file mode 100644
index 00000000..ee6d8a69
--- /dev/null
+++ b/lib/geocoder/lookups/freegeoip.rb
@@ -0,0 +1,23 @@
+require 'geocoder/lookups/base'
+require 'geocoder/results/freegeoip'
+
+module Geocoder::Lookup
+  class Freegeoip < Base
+
+    private # ---------------------------------------------------------------
+
+    def results(query, reverse = false)
+      begin
+        if doc = fetch_data(query, reverse)
+          [doc]
+        end
+      rescue StandardError # Freegeoip.net returns HTML on bad request
+        nil
+      end
+    end
+
+    def query_url(query, reverse = false)
+      "http://freegeoip.net/json/#{query}"
+    end
+  end
+end
diff --git a/lib/geocoder/results/freegeoip.rb b/lib/geocoder/results/freegeoip.rb
new file mode 100644
index 00000000..00388bad
--- /dev/null
+++ b/lib/geocoder/results/freegeoip.rb
@@ -0,0 +1,25 @@
+require 'geocoder/results/base'
+
+module Geocoder::Result
+  class Freegeoip < Base
+
+    def coordinates
+      [latitude.to_f, longitude.to_f]
+    end
+
+    def address(format = :full)
+      "#{city}#{', ' + region_code unless region_code == ''} #{zipcode}, #{country_name}"
+    end
+
+    def self.response_attributes
+      %w[city region_code region_name metrocode zipcode
+        latitude longitude country_name country_code ip]
+    end
+
+    response_attributes.each do |a|
+      define_method a do
+        @data[a]
+      end
+    end
+  end
+end
diff --git a/test/fixtures/freegeoip_74_200_247_59.json b/test/fixtures/freegeoip_74_200_247_59.json
new file mode 100644
index 00000000..78c7ce3e
--- /dev/null
+++ b/test/fixtures/freegeoip_74_200_247_59.json
@@ -0,0 +1,12 @@
+{
+  "city": "Plano",
+  "region_code": "TX",
+  "region_name": "Texas",
+  "metrocode": "623",
+  "zipcode": "75093",
+  "longitude": "-96.8134",
+  "country_name": "United States",
+  "country_code": "US",
+  "ip": "74.200.247.59",
+  "latitude": "33.0347"
+}
diff --git a/test/geocoder_test.rb b/test/geocoder_test.rb
index 6b6042ca..a9752e94 100644
--- a/test/geocoder_test.rb
+++ b/test/geocoder_test.rb
@@ -4,7 +4,6 @@ class GeocoderTest < Test::Unit::TestCase
 
   def setup
     Geocoder::Configuration.lookup = :google
-    Geocoder.send :set_lookup, :google
   end
 
   def test_fetch_coordinates
@@ -49,16 +48,25 @@ class GeocoderTest < Test::Unit::TestCase
   # --- Yahoo ---
   def test_yahoo_result_components
     Geocoder::Configuration.lookup = :yahoo
-    Geocoder.send :set_lookup, :yahoo
     results = Geocoder.search("Madison Square Garden, New York, NY")
     assert_equal "10001", results.first.postal
   end
 
   def test_yahoo_address_formatting
     Geocoder::Configuration.lookup = :yahoo
-    Geocoder.send :set_lookup, :yahoo
     results = Geocoder.search("Madison Square Garden, New York, NY")
     assert_equal "Madison Square Garden, New York, NY  10001, United States",
       results.first.address
   end
+
+  # --- FreeGeoIp ---
+  def test_freegeoip_result_on_ip_address_search
+    results = Geocoder.search("74.200.247.59")
+    assert results.first.is_a?(Geocoder::Result::Freegeoip)
+  end
+
+  def test_freegeoip_result_components
+    results = Geocoder.search("74.200.247.59")
+    assert_equal "Plano, TX 75093, United States", results.first.address
+  end
 end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 2ec28ee2..b2017a9d 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -40,10 +40,24 @@ require "geocoder/lookups/base"
 #
 module Geocoder
   module Lookup
-    class Base
+    class Google < Base
       private #-----------------------------------------------------------------
       def fetch_raw_data(query, reverse = false)
-        File.read(File.join("test", "fixtures", "#{Geocoder::Configuration.lookup}_madison_square_garden.json"))
+        File.read(File.join("test", "fixtures", "google_madison_square_garden.json"))
+      end
+    end
+
+    class Yahoo < Base
+      private #-----------------------------------------------------------------
+      def fetch_raw_data(query, reverse = false)
+        File.read(File.join("test", "fixtures", "yahoo_madison_square_garden.json"))
+      end
+    end
+
+    class Freegeoip < Base
+      private #-----------------------------------------------------------------
+      def fetch_raw_data(query, reverse = false)
+        File.read(File.join("test", "fixtures", "freegeoip_74_200_247_59.json"))
       end
     end
   end
-- 
GitLab