From 77af74b975a2eb290ee970356142bfffc91833f9 Mon Sep 17 00:00:00 2001
From: Alex Reisner <alex@alexreisner.com>
Date: Tue, 1 Mar 2011 16:29:51 -0500
Subject: [PATCH] Make Geocoder::Lookup class abstract.

Add implementation for Google and anticipate addition of Yahoo (supply
config option).
---
 lib/geocoder.rb                               | 25 ++++++-
 lib/geocoder/active_record.rb                 |  6 +-
 lib/geocoder/configuration.rb                 |  7 +-
 lib/geocoder/lookups/base.rb                  | 75 +++++++++++++++++++
 lib/geocoder/{lookup.rb => lookups/google.rb} | 42 ++---------
 test/geocoder_test.rb                         |  2 +-
 test/test_helper.rb                           |  6 +-
 7 files changed, 114 insertions(+), 49 deletions(-)
 create mode 100644 lib/geocoder/lookups/base.rb
 rename lib/geocoder/{lookup.rb => lookups/google.rb} (53%)

diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 3b28027f..7a2303a2 100644
--- a/lib/geocoder.rb
+++ b/lib/geocoder.rb
@@ -1,6 +1,5 @@
 require "geocoder/configuration"
 require "geocoder/calculations"
-require "geocoder/lookup"
 require "geocoder/result"
 require "geocoder/active_record"
 require "geocoder/railtie"
@@ -9,10 +8,30 @@ module Geocoder
   extend self
 
   ##
-  # Alias for Geocoder::Lookup.search.
+  # Alias for Geocoder.lookup.search.
   #
   def search(*args)
-    Lookup.search(*args)
+    lookup.search(*args)
+  end
+
+  ##
+  # Get the lookup object (which communicates with the remote geocoding API).
+  #
+  def lookup
+    unless defined?(@lookup)
+      set_lookup Geocoder::Configuration.lookup
+    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
+    end
   end
 
   # exception classes
diff --git a/lib/geocoder/active_record.rb b/lib/geocoder/active_record.rb
index 597998ca..fd52d470 100644
--- a/lib/geocoder/active_record.rb
+++ b/lib/geocoder/active_record.rb
@@ -28,7 +28,7 @@ module Geocoder
         #
         scope :near, lambda{ |location, *args|
           latitude, longitude = location.is_a?(Array) ?
-            location : Geocoder::Lookup.coordinates(location)
+            location : Geocoder.lookup.coordinates(location)
           if latitude and longitude
             near_scope_options(latitude, longitude, *args)
           else
@@ -189,7 +189,7 @@ module Geocoder
           "You are attempting to fetch coordinates but have not specified " +
           "a method which provides an address for the object."
       end
-      coords = Geocoder::Lookup.coordinates(send(address_method))
+      coords = Geocoder.lookup.coordinates(send(address_method))
       unless coords.blank?
         method = (save ? "update" : "write") + "_attribute"
         send method, self.class.geocoder_options[:latitude],  coords[0]
@@ -217,7 +217,7 @@ module Geocoder
           "You are attempting to fetch an address but have not specified " +
           "attributes which provide coordinates for the object."
       end
-      address = Geocoder::Lookup.address(send(lat_attr), send(lon_attr))
+      address = Geocoder.lookup.address(send(lat_attr), send(lon_attr))
       unless address.blank?
         method = (save ? "update" : "write") + "_attribute"
         send method, self.class.geocoder_options[:fetched_address], address
diff --git a/lib/geocoder/configuration.rb b/lib/geocoder/configuration.rb
index d10e1be8..ba1211cc 100644
--- a/lib/geocoder/configuration.rb
+++ b/lib/geocoder/configuration.rb
@@ -1,8 +1,9 @@
 module Geocoder
   class Configuration
-    cattr_accessor :timeout
+    cattr_accessor :timeout, :lookup, :yahoo_appid
   end
 end
 
-Geocoder::Configuration.timeout = 3
-
+Geocoder::Configuration.timeout     = 3
+Geocoder::Configuration.lookup      = :google
+Geocoder::Configuration.yahoo_appid = ""
diff --git a/lib/geocoder/lookups/base.rb b/lib/geocoder/lookups/base.rb
new file mode 100644
index 00000000..65d0f4db
--- /dev/null
+++ b/lib/geocoder/lookups/base.rb
@@ -0,0 +1,75 @@
+require 'net/http'
+require 'active_support/json'
+
+module Geocoder
+  module Lookup
+    class Base
+
+      ##
+      # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS")
+      # for geocoding, or coordinates (latitude, longitude) for reverse
+      # geocoding. Returns an array of Geocoder::Result objects,
+      # or nil if not found or if network error.
+      #
+      def search(*args)
+        return [] if args[0].blank?
+        if res = results(args.join(","), args.size == 2)
+          res.map{ |r| result_class.new(r) }
+        end
+      end
+
+      ##
+      # Look up the coordinates of the given address.
+      #
+      def coordinates(address)
+        if (results = search(address)).size > 0
+          results.first.coordinates
+        end
+      end
+
+      ##
+      # Look up the address of the given coordinates.
+      #
+      def address(latitude, longitude)
+        if (results = search(latitude, longitude)).size > 0
+          results.first.address
+        end
+      end
+
+
+      private # -------------------------------------------------------------
+
+      ##
+      # Class of the result objects
+      #
+      def result_class
+        fail
+      end
+
+      ##
+      # Returns a parsed search result (Ruby hash).
+      #
+      def fetch_data(query, reverse = false)
+        begin
+          ActiveSupport::JSON.decode(fetch_raw_data(query, reverse))
+        rescue SocketError
+          warn "Geocoding API connection cannot be established."
+        rescue TimeoutError
+          warn "Geocoding API not responding fast enough " +
+            "(see Geocoder::Configuration.timeout to set limit)."
+        end
+      end
+
+      ##
+      # Fetches a raw search result (JSON string).
+      #
+      def fetch_raw_data(query, reverse = false)
+        return nil if query.blank?
+        url = query_url(query, reverse)
+        timeout(Geocoder::Configuration.timeout) do
+          Net::HTTP.get_response(URI.parse(url)).body
+        end
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookups/google.rb
similarity index 53%
rename from lib/geocoder/lookup.rb
rename to lib/geocoder/lookups/google.rb
index 21580e7c..3889bf1e 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookups/google.rb
@@ -1,12 +1,8 @@
-require 'net/http'
+require 'geocoder/lookups/base'
 
-module Geocoder
-  module Lookup
-    extend self
+module Geocoder::Lookup
+  class Google < Base
 
-    ##
-    # Query Google for the coordinates of the given address.
-    #
     def coordinates(address)
       if (results = search(address)).size > 0
         place = results.first.geometry['location']
@@ -14,27 +10,18 @@ module Geocoder
       end
     end
 
-    ##
-    # Query Google for the address of the given coordinates.
-    #
     def address(latitude, longitude)
       if (results = search(latitude, longitude)).size > 0
         results.first.formatted_address
       end
     end
 
-    ##
-    # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS") for
-    # geocoding, or coordinates (latitude, longitude) for reverse geocoding.
-    # Returns an array of Geocoder::Result objects,
-    # or nil if not found or if network error.
-    #
     def search(*args)
       return [] if args[0].blank?
       doc = parsed_response(args.join(","), args.size == 2)
       [].tap do |results|
         if doc
-          doc['results'].each{ |r| results << Result.new(r) }
+          doc['results'].each{ |r| results << Geocoder::Result.new(r) }
         end
       end
     end
@@ -47,15 +34,7 @@ module Geocoder
     # Returns nil if non-200 HTTP response, timeout, or other error.
     #
     def parsed_response(query, reverse = false)
-      begin
-        doc = ActiveSupport::JSON.decode(fetch_data(query, reverse))
-      rescue SocketError
-        warn "Google Geocoding API connection cannot be established."
-      rescue TimeoutError
-        warn "Google Geocoding API not responding fast enough " +
-          "(see Geocoder::Configuration.timeout to set limit)."
-      end
-
+      doc = fetch_data(query, reverse)
       case doc['status']; when "OK"
         doc
       when "OVER_QUERY_LIMIT"
@@ -67,17 +46,6 @@ module Geocoder
       end
     end
 
-    ##
-    # Fetches a raw Google geocoder search result (JSON string).
-    #
-    def fetch_data(query, reverse = false)
-      return nil if query.blank?
-      url = query_url(query, reverse)
-      timeout(Geocoder::Configuration.timeout) do
-        Net::HTTP.get_response(URI.parse(url)).body
-      end
-    end
-
     def query_url(query, reverse = false)
       params = {
         (reverse ? :latlng : :address) => query,
diff --git a/test/geocoder_test.rb b/test/geocoder_test.rb
index f6a07392..27a43afa 100644
--- a/test/geocoder_test.rb
+++ b/test/geocoder_test.rb
@@ -29,7 +29,7 @@ class GeocoderTest < Test::Unit::TestCase
   end
 
   def test_result_address_components_of_type
-    results = Geocoder::Lookup.search("Madison Square Garden, New York, NY")
+    results = Geocoder.search("Madison Square Garden, New York, NY")
     assert_equal "Manhattan",
       results.first.address_components_of_type(:sublocality).first['long_name']
   end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 7d281361..dfc82fa9 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -33,18 +33,20 @@ end
 
 # Require Geocoder after ActiveRecord simulator.
 require 'geocoder'
+require "geocoder/lookups/base"
 
 ##
 # Mock HTTP request to geocoding service.
 #
 module Geocoder
   module Lookup
-    extend self
+  class Base
     private #-----------------------------------------------------------------
-    def fetch_data(query, reverse = false)
+    def fetch_raw_data(query, reverse = false)
       File.read(File.join("test", "fixtures", "madison_square_garden.json"))
     end
   end
+  end
 end
 
 ##
-- 
GitLab