acts_as_geocodable 1.0.0

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.
@@ -0,0 +1,2 @@
1
+ debug.log
2
+ pkg
@@ -0,0 +1,13 @@
1
+ 0.2.1 - 2008-8-8
2
+ * Results are now WillPaginate compatible
3
+
4
+ 0.2.0 - 2007-10-27
5
+ * Added validates_as_geocodable (Mark Van Holstyn)
6
+ * Allow address mapping to be a single field (Mark Van Holstyn)
7
+
8
+ 0.1.0
9
+ * Added remote_location to get a users location based on their remote_ip
10
+ * renamed :city to :locality in address mapping to be consistent with Graticule 0.2
11
+ create a migration with:
12
+ rename_column :geocodes, :city, :locality
13
+ * replace #full_address with #to_location
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2006 Daniel Morrison, Collective Idea
2
+ http://collectiveidea.com
3
+
4
+ Testing code was heavily influenced by Rick Olson. Thanks, Rick!
5
+ Testing Portions Copyright (c) 2006 Rick Olson
6
+ http://techno-weenie.net
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,104 @@
1
+ = acts_as_geocodable
2
+
3
+ acts_as_geocodable is a plugin to help build geo-aware applications. It automatically geocodes your models when they are saved, giving you the ability to search by location and calculate distances between records.
4
+
5
+ == Usage
6
+
7
+ event = Event.create :street => "777 NE Martin Luther King, Jr. Blvd.",
8
+ :locality => "Portland", :region => "Oregon", :postal_code => 97232
9
+
10
+ event.geocode.latitude #=> 45.529100000000
11
+ event.geocode.longitude #=> -122.644200000000
12
+
13
+ event.distance_to "49423" #=> 1807.66560483205
14
+
15
+ Event.find(:all, :within => 50, :origin => "97232")
16
+
17
+ Event.find(:nearest, :origin => "Portland, OR")
18
+
19
+ == Upgrading
20
+
21
+ If you're upgrading from a previous version of this plugin, note that :city has been renamed to :locality to be consistent with Graticule 0.2. Create a migration that has:
22
+
23
+ rename_column :geocodes, :city, :locality
24
+
25
+ Also remember to change your mapping in your geocodable classes to use the :locality key instead of :city:
26
+
27
+ class Event < ActiveRecord::Base
28
+ acts_as_geocodable :address => {:street => :address1, :locality => :city,
29
+ :region => :state, :postal_code => :zip}
30
+ end
31
+
32
+ == Installation
33
+
34
+ Graticule[link:http://rubyforge.org/projects/graticule] is used for all the heavy lifting.
35
+
36
+ gem install graticule --include-dependencies
37
+
38
+ Install as a plugin
39
+
40
+ script/plugin install git://github.com/collectiveidea/acts_as_geocodable.git
41
+
42
+ Or, install as a gem
43
+
44
+ gem install acts_as_geocodable --source=http://gemcutter.org
45
+
46
+
47
+ == Upgrading
48
+
49
+ Before October 2008, precision wasn't included in the Geocode model. Make sure you add a string precision column to your geocode table if you're upgrading from an older version, and update Graticule.
50
+
51
+ == Configuration
52
+
53
+ Create the required tables
54
+
55
+ script/generate geocodable_migration add_geocodable_tables
56
+ rake db:migrate
57
+
58
+ Set the default geocoder in your environment.rb file.
59
+
60
+ Geocode.geocoder = Graticule.service(:yahoo).new 'your_api_key'
61
+
62
+ Then, in each model you want to make geocodable, add acts_as_geocodable.
63
+
64
+ class Event < ActiveRecord::Base
65
+ acts_as_geocodable
66
+ end
67
+
68
+ The only requirement is that your model must have address fields. By default, acts_as_geocodable looks for attributes called +street+, +locality+, +region+, +postal_code+, and +country+. To change these, you can provide a mapping in the <tt>:address</tt> option:
69
+
70
+ class Event < ActiveRecord::Base
71
+ acts_as_geocodable :address => {:street => :address1, :locality => :city, :region => :state, :postal_code => :zip}
72
+ end
73
+
74
+ If that doesn't meet your needs, simply override the default +to_location+ method in your model, and return a Graticule::Location with those attributes set.
75
+
76
+ acts_as_geocodable can also update your address fields with the data returned from the geocoding service:
77
+
78
+ class Event < ActiveRecord::Base
79
+ acts_as_geocodable :normalize_address => true
80
+ end
81
+
82
+ == IP-based Geocoding
83
+
84
+ acts_as_geocodable adds a remote_location method in your controllers that uses http://hostip.info to guess remote users location based on their IP address.
85
+
86
+ def index
87
+ @nearest = Store.find(:nearest, :origin => remote_location) if remote_location
88
+ @stores = Store.find(:all)
89
+ end
90
+
91
+ Keep in mind that IP-based geocoding is not always accurate, and often will not return any results.
92
+
93
+ == Development
94
+
95
+ The source code is available at:
96
+ http://github.com/collectiveidea/acts_as_geocodable
97
+ git://github.com/collectiveidea/acts_as_geocodable.git
98
+
99
+ Patches and suggestions are welcome!
100
+
101
+ == To Do
102
+
103
+ * Documentation!!!
104
+ * configurable formulas
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'load_multi_rails_rake_tasks'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Test the acts_as_geocodable plugin.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the acts_as_geocodable plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'ActsAsGeocodable'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = 'acts_as_geocodable'
29
+ gemspec.summary = 'Simple geocoding for Rails ActiveRecord models'
30
+ gemspec.description = 'Simple geocoding for Rails ActiveRecord models. See the README for more details.'
31
+ gemspec.email = 'info@collectiveidea.com'
32
+ gemspec.homepage = 'http://github.com/collectiveidea/acts_as_geocodable'
33
+ gemspec.authors = ['Daniel Morrison', 'Brandon Keepers']
34
+ end
35
+ Jeweler::GemcutterTasks.new
36
+ rescue LoadError
37
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
38
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,7 @@
1
+ author:
2
+ name: Brandon Keepers and Daniel Morrison, Collective Idea
3
+ homepage: http://collectiveidea.com
4
+ summary: A plugin to help build geo-aware applications
5
+ homepage: http://opensoul.org/2007/2/13/geocoding-as-easy-as-1-2
6
+ plugin: http://source.collectiveidea.com/public/rails/plugins/acts_as_geocodable
7
+ license: MIT
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_geocodable}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Daniel Morrison", "Brandon Keepers"]
12
+ s.date = %q{2009-10-21}
13
+ s.description = %q{Simple geocoding for Rails ActiveRecord models. See the README for more details.}
14
+ s.email = %q{info@collectiveidea.com}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG",
21
+ "MIT-LICENSE",
22
+ "README",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "about.yml",
26
+ "acts_as_geocodable.gemspec",
27
+ "generators/geocodable_migration/USAGE",
28
+ "generators/geocodable_migration/geocodable_migration_generator.rb",
29
+ "generators/geocodable_migration/templates/migration.rb",
30
+ "install.rb",
31
+ "lib/acts_as_geocodable.rb",
32
+ "lib/acts_as_geocodable/geocode.rb",
33
+ "lib/acts_as_geocodable/geocoding.rb",
34
+ "lib/acts_as_geocodable/remote_location.rb",
35
+ "lib/acts_as_geocodable/tasks/acts_as_geocodable_tasks.rake",
36
+ "rails/init.rb",
37
+ "test/acts_as_geocodable_test.rb",
38
+ "test/db/database.yml",
39
+ "test/db/schema.rb",
40
+ "test/fixtures/cities.yml",
41
+ "test/fixtures/geocodes.yml",
42
+ "test/fixtures/geocodings.yml",
43
+ "test/fixtures/vacations.yml",
44
+ "test/geocode_test.rb",
45
+ "test/test_helper.rb",
46
+ "uninstall.rb"
47
+ ]
48
+ s.homepage = %q{http://github.com/collectiveidea/acts_as_geocodable}
49
+ s.rdoc_options = ["--charset=UTF-8"]
50
+ s.require_paths = ["lib"]
51
+ s.rubygems_version = %q{1.3.5}
52
+ s.summary = %q{Simple geocoding for Rails ActiveRecord models}
53
+ s.test_files = [
54
+ "test/acts_as_geocodable_test.rb",
55
+ "test/db/schema.rb",
56
+ "test/geocode_test.rb",
57
+ "test/test_helper.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ else
66
+ end
67
+ else
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ Description:
2
+ The geocodable migration generator creates a migration which you can use to generate two tables:
3
+
4
+ geocodes - Contains the latitude and longitude coordinates, with the address they refer to.
5
+ geocodings - The polymorphic join table.
6
+
7
+
8
+ Example:
9
+ ./script/generate geocodable_migration add_geocodable_tables
10
+
11
+ With 4 existing migrations, this will create an AddGeocodableTables migration in the
12
+ file db/migrate/5_add_geocodable_tables.rb
@@ -0,0 +1,7 @@
1
+ class GeocodableMigrationGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ class <%= class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table "geocodes" do |t|
4
+ t.column "latitude", :decimal, :precision => 15, :scale => 12
5
+ t.column "longitude", :decimal, :precision => 15, :scale => 12
6
+ t.column "query", :string
7
+ t.column "street", :string
8
+ t.column "locality", :string
9
+ t.column "region", :string
10
+ t.column "postal_code", :string
11
+ t.column "country", :string
12
+ t.column "precision", :string
13
+ end
14
+
15
+ add_index "geocodes", ["longitude"], :name => "geocodes_longitude_index"
16
+ add_index "geocodes", ["latitude"], :name => "geocodes_latitude_index"
17
+ add_index "geocodes", ["query"], :name => "geocodes_query_index", :unique => true
18
+ add_index "geocodes", ["locality"], :name => "geocodes_locality_index"
19
+ add_index "geocodes", ["region"], :name => "geocodes_region_index"
20
+ add_index "geocodes", ["postal_code"], :name => "geocodes_postal_code_index"
21
+ add_index "geocodes", ["country"], :name => "geocodes_country_index"
22
+ add_index "geocodes", ["precision"], :name => "geocodes_precision_index"
23
+
24
+
25
+ create_table "geocodings" do |t|
26
+ t.column "geocodable_id", :integer
27
+ t.column "geocode_id", :integer
28
+ t.column "geocodable_type", :string
29
+ end
30
+
31
+ add_index "geocodings", ["geocodable_type"], :name => "geocodings_geocodable_type_index"
32
+ add_index "geocodings", ["geocode_id"], :name => "geocodings_geocode_id_index"
33
+ add_index "geocodings", ["geocodable_id"], :name => "geocodings_geocodable_id_index"
34
+ end
35
+
36
+ def self.down
37
+ drop_table :geocodes
38
+ drop_table :geocodings
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,286 @@
1
+ require 'acts_as_geocodable/geocoding'
2
+ require 'acts_as_geocodable/geocode'
3
+ require 'acts_as_geocodable/remote_location'
4
+
5
+ module CollectiveIdea #:nodoc:
6
+ module Acts #:nodoc:
7
+ module Geocodable #:nodoc:
8
+
9
+ def self.included(mod)
10
+ mod.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ # Make a model geocodable.
16
+ #
17
+ # class Event < ActiveRecord::Base
18
+ # acts_as_geocodable
19
+ # end
20
+ #
21
+ # == Options
22
+ # * <tt>:address</tt>: A hash that maps geocodable attirbutes (<tt>:street</tt>,
23
+ # <tt>:locality</tt>, <tt>:region</tt>, <tt>:postal_code</tt>, <tt>:country</tt>)
24
+ # to your model's address fields, or a symbol to store the entire address in one field
25
+ # * <tt>:normalize_address</tt>: If set to true, you address fields will be updated
26
+ # using the address fields returned by the geocoder. (Default is +false+)
27
+ # * <tt>:units</tt>: Default units-<tt>:miles</tt> or <tt>:kilometers</tt>-used for
28
+ # distance calculations and queries. (Default is <tt>:miles</tt>)
29
+ #
30
+ def acts_as_geocodable(options = {})
31
+ options = {
32
+ :address => {
33
+ :street => :street, :locality => :locality, :region => :region,
34
+ :postal_code => :postal_code, :country => :country},
35
+ :normalize_address => false,
36
+ :distance_column => 'distance',
37
+ :units => :miles
38
+ }.merge(options)
39
+
40
+ write_inheritable_attribute :acts_as_geocodable_options, options
41
+ class_inheritable_reader :acts_as_geocodable_options
42
+
43
+ define_callbacks :after_geocoding
44
+
45
+ has_one :geocoding, :as => :geocodable, :include => :geocode, :dependent => :destroy
46
+
47
+ after_save :attach_geocode
48
+
49
+ include CollectiveIdea::Acts::Geocodable::InstanceMethods
50
+ extend CollectiveIdea::Acts::Geocodable::SingletonMethods
51
+ end
52
+
53
+ end
54
+
55
+ module SingletonMethods
56
+
57
+ # Extends ActiveRecord's find method to be geo-aware.
58
+ #
59
+ # Model.find(:all, :within => 10, :origin => "Chicago, IL")
60
+ #
61
+ # Whenever find is called with an <tt>:origin</tt>, a +distance+ attribute
62
+ # indicating the distance to the origin is added to each of the results:
63
+ #
64
+ # Model.find(:first, :origin => "Portland, OR").distance #=> 388.383
65
+ #
66
+ # +acts_as_geocodable+ adds 2 other retrieval approaches to ActiveRecord's default
67
+ # find by id, find <tt>:first</tt>, and find <tt>:all</tt>:
68
+ #
69
+ # * <tt>:nearest</tt>: find the nearest location to the given origin
70
+ # * <tt>:farthest</tt>: find the farthest location from the given origin
71
+ #
72
+ # Model.find(:nearest, :origin => "Grand Rapids, MI")
73
+ #
74
+ # == Options
75
+ #
76
+ # * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
77
+ # the origin
78
+ # * <tt>:within</tt>: Limit to results within this radius of the origin
79
+ # * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
80
+ # * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
81
+ # Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
82
+ # declaration.
83
+ #
84
+ def find(*args)
85
+ options = args.extract_options!
86
+ origin = location_to_geocode options.delete(:origin)
87
+ if origin
88
+ options[:units] ||= acts_as_geocodable_options[:units]
89
+ add_distance_to_select!(origin, options)
90
+ with_proximity!(args, options) do
91
+ geocode_conditions!(options, origin) do
92
+ join_geocodes { super *args.push(options) }
93
+ end
94
+ end
95
+ else
96
+ super *args.push(options)
97
+ end
98
+ end
99
+
100
+ # Extends ActiveRecord's count method to be geo-aware.
101
+ #
102
+ # Model.count(:within => 10, :origin => "Chicago, IL")
103
+ #
104
+ # == Options
105
+ #
106
+ # * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
107
+ # the origin
108
+ # * <tt>:within</tt>: Limit to results within this radius of the origin
109
+ # * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
110
+ # * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
111
+ # Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
112
+ # declaration.
113
+ #
114
+ def count(*args)
115
+ options = args.extract_options!
116
+ origin = location_to_geocode options.delete(:origin)
117
+ if origin
118
+ options[:units] ||= acts_as_geocodable_options[:units]
119
+ with_proximity!(args, options) do
120
+ geocode_conditions!(options, origin) do
121
+ join_geocodes { super *args.push(options) }
122
+ end
123
+ end
124
+ else
125
+ super *args.push(options)
126
+ end
127
+ end
128
+
129
+ # Convert the given location to a Geocode
130
+ def location_to_geocode(location)
131
+ case location
132
+ when Geocode then location
133
+ when InstanceMethods then location.geocode
134
+ when String, Fixnum then Geocode.find_or_create_by_query(location)
135
+ end
136
+ end
137
+
138
+ def validates_as_geocodable(options = {})
139
+ options = options.reverse_merge :message => "Address could not be geocoded.", :allow_nil => false
140
+ validate do |geocodable|
141
+ if !(options[:allow_nil] && geocodable.to_location.attributes.all?(&:blank?)) &&
142
+ !Geocode.find_or_create_by_location(geocodable.to_location)
143
+ geocodable.errors.add_to_base options[:message]
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def add_distance_to_select!(origin, options)
151
+ (options[:select] ||= "#{table_name}.*") <<
152
+ ", #{sql_for_distance(origin, options[:units])} AS
153
+ #{acts_as_geocodable_options[:distance_column]}"
154
+ end
155
+
156
+ def with_proximity!(args, options)
157
+ if [:nearest, :farthest].include?(args.first)
158
+ raise ArgumentError, ":include cannot be specified with :nearest and :farthest" if options[:include]
159
+ direction = args.first == :nearest ? "ASC" : "DESC"
160
+ args[0] = :first
161
+ with_scope :find => { :order => "#{acts_as_geocodable_options[:distance_column]} #{direction}"} do
162
+ yield
163
+ end
164
+ else
165
+ yield
166
+ end
167
+ end
168
+
169
+ def join_geocodes(&block)
170
+ with_scope :find => { :joins => "JOIN geocodings ON
171
+ #{table_name}.#{primary_key} = geocodings.geocodable_id AND
172
+ geocodings.geocodable_type = '#{class_name}'
173
+ JOIN geocodes ON geocodings.geocode_id = geocodes.id" } do
174
+ yield
175
+ end
176
+ end
177
+
178
+ def geocode_conditions!(options, origin)
179
+ units = options.delete(:units)
180
+ conditions = []
181
+ conditions << "#{sql_for_distance(origin, units)} <= #{options.delete(:within)}" if options[:within]
182
+ conditions << "#{sql_for_distance(origin, units)} > #{options.delete(:beyond)}" if options[:beyond]
183
+ if conditions.empty?
184
+ yield
185
+ else
186
+ with_scope(:find => { :conditions => conditions.join(" AND ") }) { yield }
187
+ end
188
+ end
189
+
190
+ def sql_for_distance(origin, units = acts_as_geocodable_options[:units])
191
+ Graticule::Distance::Spherical.to_sql(
192
+ :latitude => origin.latitude,
193
+ :longitude => origin.longitude,
194
+ :latitude_column => "geocodes.latitude",
195
+ :longitude_column => "geocodes.longitude",
196
+ :units => units
197
+ )
198
+ end
199
+
200
+ end
201
+
202
+ module InstanceMethods
203
+
204
+ # Get the geocode for this model
205
+ def geocode
206
+ geocoding.geocode if geocoding
207
+ end
208
+
209
+ # Create a Graticule::Location
210
+ def to_location
211
+ returning Graticule::Location.new do |location|
212
+ [:street, :locality, :region, :postal_code, :country].each do |attr|
213
+ location.send "#{attr}=", geo_attribute(attr)
214
+ end
215
+ end
216
+ end
217
+
218
+ # Get the distance to the given destination. The destination can be an
219
+ # acts_as_geocodable model, a Geocode, or a string
220
+ #
221
+ # myhome.distance_to "Chicago, IL"
222
+ # myhome.distance_to "49423"
223
+ # myhome.distance_to other_model
224
+ #
225
+ # == Options
226
+ # * <tt>:units</tt>: <tt>:miles</tt> or <tt>:kilometers</tt>
227
+ # * <tt>:formula</tt>: The formula to use to calculate the distance. This can
228
+ # be any formula supported by Graticule. The default is <tt>:haversine</tt>.
229
+ #
230
+ def distance_to(destination, options = {})
231
+ units = options[:units] || acts_as_geocodable_options[:units]
232
+ formula = options[:formula] || :haversine
233
+
234
+ geocode = self.class.location_to_geocode(destination)
235
+ self.geocode.distance_to(geocode, units, formula)
236
+ end
237
+
238
+ protected
239
+
240
+ # Perform the geocoding
241
+ def attach_geocode
242
+ new_geocode = Geocode.find_or_create_by_location self.to_location unless self.to_location.blank?
243
+ if new_geocode && self.geocode != new_geocode
244
+ self.geocoding = Geocoding.new :geocode => new_geocode
245
+ self.update_address self.acts_as_geocodable_options[:normalize_address]
246
+ callback :after_geocoding
247
+ elsif !new_geocode && self.geocoding
248
+ self.geocoding.destroy
249
+ end
250
+ rescue Graticule::Error => e
251
+ logger.warn e.message
252
+ end
253
+
254
+
255
+ def update_address(force = false)
256
+ unless self.geocode.blank?
257
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
258
+ method = self.acts_as_geocodable_options[:address]
259
+ if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
260
+ self.send "#{method}=", self.geocode.to_location.to_s
261
+ end
262
+ else
263
+ self.acts_as_geocodable_options[:address].each do |attribute,method|
264
+ if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
265
+ self.send "#{method}=", self.geocode.send(attribute)
266
+ end
267
+ end
268
+ end
269
+
270
+ update_without_callbacks
271
+ end
272
+ end
273
+
274
+ def geo_attribute(attr_key)
275
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
276
+ attr_name = self.acts_as_geocodable_options[:address]
277
+ attr_key == :street ? self.send(attr_name) : nil
278
+ else
279
+ attr_name = self.acts_as_geocodable_options[:address][attr_key]
280
+ attr_name && self.respond_to?(attr_name) ? self.send(attr_name) : nil
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end