rails-geocoder 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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