rails-geocoder 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc CHANGED
@@ -2,10 +2,19 @@
2
2
 
3
3
  Per-release changes to Geocoder.
4
4
 
5
+ == 0.9.8 (TBA)
6
+
7
+ * Include geocode:all Rake task in gem (was missing!).
8
+ * Add Geocoder.search for access to Google's full response.
9
+ * Add ability to configure Google connection timeout.
10
+ * Emit warnings on Google connection problems and errors.
11
+ * Refactor: insert Geocoder into ActiveRecord via Railtie.
12
+
5
13
  == 0.9.7 (2011 Feb 1)
6
14
 
7
15
  * Add reverse geocoding (+reverse_geocoded_by+).
8
16
  * Prevent exception (uninitialized constant Geocoder::Net) when net/http not already required (sleepycat).
17
+ * Refactor: split monolithic Geocoder module into several smaller ones.
9
18
 
10
19
  == 0.9.6 (2011 Jan 19)
11
20
 
data/README.rdoc CHANGED
@@ -97,8 +97,11 @@ If your model has +address+, +city+, +state+, and +country+ attributes you might
97
97
  [address, city, state, country].compact.join(', ')
98
98
  end
99
99
 
100
+ Please see the code (<tt>lib/geocoder/active_record.rb</tt>) for more methods and detailed information about arguments (eg, working with kilometers).
100
101
 
101
- Please see the code for more methods and detailed information about arguments (eg, working with kilometers).
102
+ You can also set the timeout used for connections to Google's geocoding service. The default is 3 seconds, but if you want to set it to 5 you could put the following in an initializer:
103
+
104
+ Geocoder::Configuration.timeout = 5
102
105
 
103
106
 
104
107
  == Reverse Geocoding
@@ -172,15 +175,12 @@ If anyone has a more elegant solution to this problem I am very interested in se
172
175
 
173
176
  == To-do List
174
177
 
178
+ * support different ORMs (DataMapper, Mongoid, etc)
175
179
  * use completely separate "drivers" for different AR adapters?
176
180
  * seems reasonable since we're using very DB-specific features
177
181
  * also need to make sure 'mysql2' is supported
178
182
  * make 'near' scope work with AR associations
179
183
  * http://stackoverflow.com/questions/3266358/geocoder-rails-plugin-near-search-problem-with-activerecord
180
- * prepend table names to column names in SQL distance expression (required
181
- to do joins on another geocoded model)
182
- * unobtrusively add ability to get a result with more data (Geocoder object?)
183
- * the default usage should remain dead simple
184
184
 
185
185
 
186
- Copyright (c) 2009-10 Alex Reisner, released under the MIT license
186
+ Copyright (c) 2009-11 Alex Reisner, released under the MIT license
data/lib/geocoder.rb CHANGED
@@ -1,53 +1,23 @@
1
+ require "geocoder/configuration"
1
2
  require "geocoder/calculations"
2
3
  require "geocoder/lookup"
4
+ require "geocoder/result"
3
5
  require "geocoder/active_record"
6
+ require "geocoder/railtie"
4
7
 
5
- ##
6
- # Add geocoded_by method to ActiveRecord::Base so Geocoder is accessible.
7
- #
8
- ActiveRecord::Base.class_eval do
8
+ module Geocoder
9
+ extend self
9
10
 
10
11
  ##
11
- # Set attribute names and include the Geocoder module.
12
+ # Alias for Geocoder::Lookup.search.
12
13
  #
13
- def self.geocoded_by(address_attr, options = {})
14
- _geocoder_init(
15
- :user_address => address_attr,
16
- :latitude => options[:latitude] || :latitude,
17
- :longitude => options[:longitude] || :longitude
18
- )
14
+ def search(*args)
15
+ Lookup.search(*args)
19
16
  end
20
17
 
21
- ##
22
- # Set attribute names and include the Geocoder module.
23
- #
24
- def self.reverse_geocoded_by(latitude_attr, longitude_attr, options = {})
25
- _geocoder_init(
26
- :fetched_address => options[:address] || :address,
27
- :latitude => latitude_attr,
28
- :longitude => longitude_attr
29
- )
30
- end
31
-
32
- def self._geocoder_init(options)
33
- unless _geocoder_initialized?
34
- class_inheritable_reader :geocoder_options
35
- class_inheritable_hash_writer :geocoder_options
36
- end
37
- self.geocoder_options = options
38
- unless _geocoder_initialized?
39
- include Geocoder::ActiveRecord
40
- end
41
- end
42
-
43
- def self._geocoder_initialized?
44
- included_modules.include? Geocoder::ActiveRecord
45
- end
18
+ # exception classes
19
+ class Error < StandardError; end
20
+ class ConfigurationError < Error; end
46
21
  end
47
22
 
48
-
49
- class GeocoderError < StandardError
50
- end
51
-
52
- class GeocoderConfigurationError < GeocoderError
53
- end
23
+ Geocoder::Railtie.insert
@@ -185,7 +185,7 @@ module Geocoder
185
185
  def fetch_coordinates(save = false)
186
186
  address_method = self.class.geocoder_options[:user_address]
187
187
  unless address_method.is_a? Symbol
188
- raise GeocoderConfigurationError,
188
+ raise Geocoder::ConfigurationError,
189
189
  "You are attempting to fetch coordinates but have not specified " +
190
190
  "a method which provides an address for the object."
191
191
  end
@@ -213,7 +213,7 @@ module Geocoder
213
213
  lat_attr = self.class.geocoder_options[:latitude]
214
214
  lon_attr = self.class.geocoder_options[:longitude]
215
215
  unless lat_attr.is_a?(Symbol) and lon_attr.is_a?(Symbol)
216
- raise GeocoderConfigurationError,
216
+ raise Geocoder::ConfigurationError,
217
217
  "You are attempting to fetch an address but have not specified " +
218
218
  "attributes which provide coordinates for the object."
219
219
  end
@@ -0,0 +1,8 @@
1
+ module Geocoder
2
+ class Configuration
3
+ cattr_accessor :timeout
4
+ end
5
+ end
6
+
7
+ Geocoder::Configuration.timeout = 3
8
+
@@ -6,73 +6,85 @@ module Geocoder
6
6
 
7
7
  ##
8
8
  # Query Google for the coordinates of the given address.
9
- # Returns array [lat,lon] if found, nil if not found or if network error.
10
9
  #
11
10
  def coordinates(address)
12
- return nil if address.blank?
13
- return nil unless doc = search(address, false)
14
- # blindly use first result (assume it is most accurate)
15
- place = doc['results'].first['geometry']['location']
16
- ['lat', 'lng'].map{ |i| place[i] }
11
+ if (results = search(address)).size > 0
12
+ place = results.first.geometry['location']
13
+ ['lat', 'lng'].map{ |i| place[i] }
14
+ end
17
15
  end
18
16
 
19
17
  ##
20
18
  # Query Google for the address of the given coordinates.
21
- # Returns string if found, nil if not found or if network error.
22
19
  #
23
20
  def address(latitude, longitude)
24
- return nil if latitude.blank? || longitude.blank?
25
- return nil unless doc = search("#{latitude},#{longitude}", true)
26
- # blindly use first result (assume it is most accurate)
27
- doc['results'].first['formatted_address']
21
+ if (results = search(latitude, longitude)).size > 0
22
+ results.first.formatted_address
23
+ end
28
24
  end
29
25
 
30
-
31
- private # ---------------------------------------------------------------
32
-
33
26
  ##
34
- # Query Google for geographic information about the given phrase.
35
- # Returns a hash representing a valid geocoder response.
36
- # Returns nil if non-200 HTTP response, timeout, or other error.
27
+ # Takes a search string (eg: "Mississippi Coast Coliseumf, Biloxi, MS") for
28
+ # geocoding, or coordinates (latitude, longitude) for reverse geocoding.
29
+ # Returns an array of Geocoder::Result objects,
30
+ # or nil if not found or if network error.
37
31
  #
38
- def search(query, reverse = false)
39
- doc = fetch_parsed_response(query, reverse)
40
- doc && doc['status'] == "OK" ? doc : nil
32
+ def search(*args)
33
+ return nil if args[0].blank?
34
+ doc = parsed_response(args.join(","), args.size == 2)
35
+ [].tap do |results|
36
+ if doc
37
+ doc['results'].each{ |r| results << Result.new(r) }
38
+ end
39
+ end
41
40
  end
42
41
 
42
+
43
+ private # ---------------------------------------------------------------
44
+
43
45
  ##
44
46
  # Returns a parsed Google geocoder search result (hash).
45
- # This method is not intended for general use (prefer Geocoder.search).
47
+ # Returns nil if non-200 HTTP response, timeout, or other error.
46
48
  #
47
- def fetch_parsed_response(query, reverse = false)
48
- if doc = fetch_raw_response(query, reverse)
49
- ActiveSupport::JSON.decode(doc)
49
+ def parsed_response(query, reverse = false)
50
+ begin
51
+ doc = ActiveSupport::JSON.decode(fetch_data(query, reverse))
52
+ rescue SocketError
53
+ warn "Google Geocoding API connection cannot be established."
54
+ rescue TimeoutError
55
+ warn "Google Geocoding API not responding fast enough " +
56
+ "(see Geocoder::Configuration.timeout to set limit)."
57
+ end
58
+
59
+ case doc['status']; when "OK"
60
+ doc
61
+ when "OVER_QUERY_LIMIT"
62
+ warn "Google Geocoding API error: over query limit."
63
+ when "REQUEST_DENIED"
64
+ warn "Google Geocoding API error: request denied."
65
+ when "INVALID_REQUEST"
66
+ warn "Google Geocoding API error: invalid request."
50
67
  end
51
68
  end
52
69
 
53
70
  ##
54
- # Returns a raw Google geocoder search result (JSON).
55
- # This method is not intended for general use (prefer Geocoder.search).
71
+ # Fetches a raw Google geocoder search result (JSON string).
56
72
  #
57
- def fetch_raw_response(query, reverse = false)
73
+ def fetch_data(query, reverse = false)
58
74
  return nil if query.blank?
59
-
60
- # name parameter based on forward/reverse geocoding
61
- param = reverse ? :latlng : :address
62
-
63
- # build URL
64
- params = { param => query, :sensor => "false" }
65
- url = "http://maps.google.com/maps/api/geocode/json?" + params.to_query
66
-
67
- # query geocoder and make sure it responds quickly
68
- begin
69
- resp = nil
70
- timeout(3) do
71
- Net::HTTP.get_response(URI.parse(url)).body
72
- end
73
- rescue SocketError, TimeoutError
74
- return nil
75
+ url = query_url(query, reverse)
76
+ timeout(Geocoder::Configuration.timeout) do
77
+ Net::HTTP.get_response(URI.parse(url)).body
75
78
  end
76
79
  end
80
+
81
+ def query_url(query, reverse = false)
82
+ params = {
83
+ (reverse ? :latlng : :address) => query,
84
+ :sensor => "false"
85
+ }
86
+ "http://maps.google.com/maps/api/geocode/json?" + params.to_query
87
+ end
77
88
  end
78
89
  end
90
+
@@ -0,0 +1,68 @@
1
+ require 'geocoder'
2
+
3
+ module Geocoder
4
+ if defined? Rails::Railtie
5
+ require 'rails'
6
+ class Railtie < Rails::Railtie
7
+ initializer 'geocoder.insert_into_active_record' do
8
+ ActiveSupport.on_load :active_record do
9
+ Geocoder::Railtie.insert
10
+ end
11
+ end
12
+ rake_tasks do
13
+ load "tasks/geocoder.rake"
14
+ end
15
+ end
16
+ end
17
+
18
+ class Railtie
19
+ def self.insert
20
+
21
+ return unless defined?(::ActiveRecord)
22
+
23
+ ##
24
+ # Add methods to ActiveRecord::Base so Geocoder is accessible by models.
25
+ #
26
+ ::ActiveRecord::Base.class_eval do
27
+
28
+ ##
29
+ # Set attribute names and include the Geocoder module.
30
+ #
31
+ def self.geocoded_by(address_attr, options = {})
32
+ _geocoder_init(
33
+ :user_address => address_attr,
34
+ :latitude => options[:latitude] || :latitude,
35
+ :longitude => options[:longitude] || :longitude
36
+ )
37
+ end
38
+
39
+ ##
40
+ # Set attribute names and include the Geocoder module.
41
+ #
42
+ def self.reverse_geocoded_by(latitude_attr, longitude_attr, options = {})
43
+ _geocoder_init(
44
+ :fetched_address => options[:address] || :address,
45
+ :latitude => latitude_attr,
46
+ :longitude => longitude_attr
47
+ )
48
+ end
49
+
50
+ def self._geocoder_init(options)
51
+ unless _geocoder_initialized?
52
+ class_inheritable_reader :geocoder_options
53
+ class_inheritable_hash_writer :geocoder_options
54
+ end
55
+ self.geocoder_options = options
56
+ unless _geocoder_initialized?
57
+ include Geocoder::ActiveRecord
58
+ end
59
+ end
60
+
61
+ def self._geocoder_initialized?
62
+ included_modules.include? Geocoder::ActiveRecord
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,42 @@
1
+ module Geocoder
2
+ class Result
3
+ attr_accessor :data
4
+
5
+ ##
6
+ # Takes a hash of result data from a parsed Google result document.
7
+ #
8
+ def initialize(data)
9
+ @data = data
10
+ end
11
+
12
+ def types
13
+ @data['types']
14
+ end
15
+
16
+ def formatted_address
17
+ @data['formatted_address']
18
+ end
19
+
20
+ def address_components
21
+ @data['address_components']
22
+ end
23
+
24
+ ##
25
+ # Get address components of a given type. Valid types are defined in
26
+ # Google's Geocoding API documentation and include (among others):
27
+ #
28
+ # :street_number
29
+ # :locality
30
+ # :neighborhood
31
+ # :route
32
+ # :postal_code
33
+ #
34
+ def address_components_of_type(type)
35
+ address_components.select{ |c| c['types'].include?(type.to_s) }
36
+ end
37
+
38
+ def geometry
39
+ @data['geometry']
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ def klass
2
+ class_name = ENV['CLASS'] || ENV['class']
3
+ raise "Please specify a CLASS (model)" unless class_name
4
+ Object.const_get(class_name)
5
+ end
6
+
7
+ namespace :geocode do
8
+
9
+ desc "Geocode all objects without coordinates."
10
+ task :all => :environment do
11
+ klass.not_geocoded.each do |obj|
12
+ obj.fetch_coordinates!
13
+ end
14
+ end
15
+ end
@@ -23,8 +23,14 @@ class GeocoderTest < Test::Unit::TestCase
23
23
 
24
24
  def test_exception_raised_for_unconfigured_geocoding
25
25
  l = Landmark.new("Mount Rushmore", 43.88, -103.46)
26
- assert_raises GeocoderConfigurationError do
26
+ assert_raises Geocoder::ConfigurationError do
27
27
  l.fetch_coordinates
28
28
  end
29
29
  end
30
+
31
+ def test_result_address_components_of_type
32
+ results = Geocoder::Lookup.search("Madison Square Garden, New York, NY")
33
+ assert_equal "Manhattan",
34
+ results.first.address_components_of_type(:sublocality).first['long_name']
35
+ end
30
36
  end
data/test/test_helper.rb CHANGED
@@ -39,7 +39,9 @@ require 'geocoder'
39
39
  #
40
40
  module Geocoder
41
41
  module Lookup
42
- def self.fetch_raw_response(query, reverse = false)
42
+ extend self
43
+ private #-----------------------------------------------------------------
44
+ def fetch_data(query, reverse = false)
43
45
  File.read(File.join("test", "fixtures", "madison_square_garden.json"))
44
46
  end
45
47
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-geocoder
3
3
  version: !ruby/object:Gem::Version
4
- hash: 53
4
+ hash: 43
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 7
10
- version: 0.9.7
9
+ - 8
10
+ version: 0.9.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alex Reisner
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-01 00:00:00 -05:00
18
+ date: 2011-02-08 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -32,7 +32,11 @@ files:
32
32
  - lib/geocoder.rb
33
33
  - lib/geocoder/lookup.rb
34
34
  - lib/geocoder/calculations.rb
35
+ - lib/geocoder/railtie.rb
36
+ - lib/geocoder/result.rb
37
+ - lib/geocoder/configuration.rb
35
38
  - lib/geocoder/active_record.rb
39
+ - lib/tasks/geocoder.rake
36
40
  - test/test_helper.rb
37
41
  - test/geocoder_test.rb
38
42
  - CHANGELOG.rdoc