delayed_acts_as_geocodable 1.0.3

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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ debug.log
2
+ pkg
3
+ rdoc
data/CHANGELOG ADDED
@@ -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
data/MIT-LICENSE ADDED
@@ -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
data/Rakefile ADDED
@@ -0,0 +1,39 @@
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
+ gemspec.add_dependency 'graticule', '>=1.0.0.pre2'
35
+ end
36
+ Jeweler::GemcutterTasks.new
37
+ rescue LoadError
38
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
39
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.3
data/about.yml ADDED
@@ -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,75 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{delayed_acts_as_geocodable}
8
+ s.version = "1.0.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rubem Azenha", "Daniel Morrison", "Brandon Keepers"]
12
+ s.date = %q{2010-08-26}
13
+ s.description = %q{Simple geocoding for Rails ActiveRecord models. See the README for more details.}
14
+ s.email = %q{rubem.azenha@gmail.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
+ s.add_runtime_dependency(%q<graticule>, [">= 1.0.0.pre2"])
66
+ else
67
+ s.add_dependency(%q<graticule>, [">= 1.0.0.pre2"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<graticule>, [">= 1.0.0.pre2"])
71
+ end
72
+
73
+ s.add_dependency(%q<delayed_job>, [">= 2.0.3"])
74
+ end
75
+
@@ -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
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,321 @@
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 :prepare_attach_geocode
48
+
49
+
50
+ include CollectiveIdea::Acts::Geocodable::InstanceMethods
51
+ extend CollectiveIdea::Acts::Geocodable::SingletonMethods
52
+ end
53
+
54
+ end
55
+
56
+ module SingletonMethods
57
+
58
+ # Extends ActiveRecord's find method to be geo-aware.
59
+ #
60
+ # Model.find(:all, :within => 10, :origin => "Chicago, IL")
61
+ #
62
+ # Whenever find is called with an <tt>:origin</tt>, a +distance+ attribute
63
+ # indicating the distance to the origin is added to each of the results:
64
+ #
65
+ # Model.find(:first, :origin => "Portland, OR").distance #=> 388.383
66
+ #
67
+ # +acts_as_geocodable+ adds 2 other retrieval approaches to ActiveRecord's default
68
+ # find by id, find <tt>:first</tt>, and find <tt>:all</tt>:
69
+ #
70
+ # * <tt>:nearest</tt>: find the nearest location to the given origin
71
+ # * <tt>:farthest</tt>: find the farthest location from the given origin
72
+ #
73
+ # Model.find(:nearest, :origin => "Grand Rapids, MI")
74
+ #
75
+ # == Options
76
+ #
77
+ # * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
78
+ # the origin
79
+ # * <tt>:within</tt>: Limit to results within this radius of the origin
80
+ # * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
81
+ # * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
82
+ # Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
83
+ # declaration.
84
+ #
85
+ def find(*args)
86
+ options = args.extract_options!
87
+ origin = location_to_geocode options.delete(:origin)
88
+ if origin
89
+ options[:units] ||= acts_as_geocodable_options[:units]
90
+ add_distance_to_select!(origin, options)
91
+ with_proximity!(args, options) do
92
+ geocode_conditions!(options, origin) do
93
+ join_geocodes { super *args.push(options) }
94
+ end
95
+ end
96
+ else
97
+ super *args.push(options)
98
+ end
99
+ end
100
+
101
+ # Extends ActiveRecord's count method to be geo-aware.
102
+ #
103
+ # Model.count(:within => 10, :origin => "Chicago, IL")
104
+ #
105
+ # == Options
106
+ #
107
+ # * <tt>:origin</tt>: A Geocode, String, or geocodable model that specifies
108
+ # the origin
109
+ # * <tt>:within</tt>: Limit to results within this radius of the origin
110
+ # * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
111
+ # * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
112
+ # Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
113
+ # declaration.
114
+ #
115
+ def count(*args)
116
+ options = args.extract_options!
117
+ origin = location_to_geocode options.delete(:origin)
118
+ if origin
119
+ options[:units] ||= acts_as_geocodable_options[:units]
120
+ with_proximity!(args, options) do
121
+ geocode_conditions!(options, origin) do
122
+ join_geocodes { super *args.push(options) }
123
+ end
124
+ end
125
+ else
126
+ super *args.push(options)
127
+ end
128
+ end
129
+
130
+ # Convert the given location to a Geocode
131
+ def location_to_geocode(location)
132
+ case location
133
+ when Geocode then location
134
+ when InstanceMethods then location.geocode
135
+ when String, Fixnum then Geocode.find_or_create_by_query(location)
136
+ end
137
+ end
138
+
139
+ # Validate that the model can be geocoded
140
+ #
141
+ # Options:
142
+ # * <tt>:message</tt>: Added to errors base (Default: Address could not be geocoded.)
143
+ # * <tt>:allow_nil</tt>: If all the address attributes are blank, then don't try to
144
+ # validate the geocode (Default: false)
145
+ # * <tt>:precision</tt>: Require a minimum geocoding precision
146
+ #
147
+ # validates_as_geocodable also takes a block that you can use to performa additional
148
+ # checks on the geocode. If this block returns false, then validation will fail.
149
+ #
150
+ # validates_as_geocodable do |geocode|
151
+ # geocode.country == "US"
152
+ # end
153
+ #
154
+ def validates_as_geocodable(options = {})
155
+ options = options.reverse_merge :message => "Address could not be geocoded.", :allow_nil => false
156
+ validate do |model|
157
+ is_blank = model.to_location.attributes.except(:precision).all?(&:blank?)
158
+ unless options[:allow_nil] && is_blank
159
+ geocode = model.send :attach_geocode
160
+ if !geocode ||
161
+ (options[:precision] && geocode.precision < options[:precision]) ||
162
+ (block_given? && yield(geocode) == false)
163
+ model.errors.add_to_base options[:message]
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def add_distance_to_select!(origin, options)
172
+ (options[:select] ||= "#{table_name}.*") <<
173
+ ", #{sql_for_distance(origin, options[:units])} AS
174
+ #{acts_as_geocodable_options[:distance_column]}"
175
+ end
176
+
177
+ def with_proximity!(args, options)
178
+ if [:nearest, :farthest].include?(args.first)
179
+ raise ArgumentError, ":include cannot be specified with :nearest and :farthest" if options[:include]
180
+ direction = args.first == :nearest ? "ASC" : "DESC"
181
+ args[0] = :first
182
+ with_scope :find => { :order => "#{acts_as_geocodable_options[:distance_column]} #{direction}"} do
183
+ yield
184
+ end
185
+ else
186
+ yield
187
+ end
188
+ end
189
+
190
+ def join_geocodes(&block)
191
+ with_scope :find => { :joins => "JOIN geocodings ON
192
+ #{table_name}.#{primary_key} = geocodings.geocodable_id AND
193
+ geocodings.geocodable_type = '#{class_name}'
194
+ JOIN geocodes ON geocodings.geocode_id = geocodes.id" } do
195
+ yield
196
+ end
197
+ end
198
+
199
+ def geocode_conditions!(options, origin)
200
+ units = options.delete(:units)
201
+ conditions = []
202
+ conditions << "#{sql_for_distance(origin, units)} <= #{options.delete(:within)}" if options[:within]
203
+ conditions << "#{sql_for_distance(origin, units)} > #{options.delete(:beyond)}" if options[:beyond]
204
+ if conditions.empty?
205
+ yield
206
+ else
207
+ with_scope(:find => { :conditions => conditions.join(" AND ") }) { yield }
208
+ end
209
+ end
210
+
211
+ def sql_for_distance(origin, units = acts_as_geocodable_options[:units])
212
+ Graticule::Distance::Spherical.to_sql(
213
+ :latitude => origin.latitude,
214
+ :longitude => origin.longitude,
215
+ :latitude_column => "geocodes.latitude",
216
+ :longitude_column => "geocodes.longitude",
217
+ :units => units
218
+ )
219
+ end
220
+
221
+ end
222
+
223
+ module InstanceMethods
224
+
225
+ # Get the geocode for this model
226
+ def geocode
227
+ geocoding.geocode if geocoding
228
+ end
229
+
230
+ # Create a Graticule::Location
231
+ def to_location
232
+ returning Graticule::Location.new do |location|
233
+ [:street, :locality, :region, :postal_code, :country].each do |attr|
234
+ location.send "#{attr}=", geo_attribute(attr)
235
+ end
236
+ end
237
+ end
238
+
239
+ # Get the distance to the given destination. The destination can be an
240
+ # acts_as_geocodable model, a Geocode, or a string
241
+ #
242
+ # myhome.distance_to "Chicago, IL"
243
+ # myhome.distance_to "49423"
244
+ # myhome.distance_to other_model
245
+ #
246
+ # == Options
247
+ # * <tt>:units</tt>: <tt>:miles</tt> or <tt>:kilometers</tt>
248
+ # * <tt>:formula</tt>: The formula to use to calculate the distance. This can
249
+ # be any formula supported by Graticule. The default is <tt>:haversine</tt>.
250
+ #
251
+ def distance_to(destination, options = {})
252
+ units = options[:units] || acts_as_geocodable_options[:units]
253
+ formula = options[:formula] || :haversine
254
+
255
+ geocode = self.class.location_to_geocode(destination)
256
+ self.geocode.distance_to(geocode, units, formula)
257
+ end
258
+
259
+ def attach_geocode_now
260
+ attach_geocode
261
+ end
262
+
263
+ def attach_geocode_with_delay
264
+ self.send_later :attach_geocode
265
+ end
266
+
267
+ protected
268
+
269
+ def prepare_attach_geocode
270
+ AttachStrategy::strategy.attach(self)
271
+ end
272
+
273
+
274
+ # Perform the geocoding
275
+ def attach_geocode
276
+ new_geocode = Geocode.find_or_create_by_location self.to_location unless self.to_location.blank?
277
+ if new_geocode && self.geocode != new_geocode
278
+ self.geocoding = Geocoding.new :geocode => new_geocode
279
+ self.update_address self.acts_as_geocodable_options[:normalize_address]
280
+ callback :after_geocoding
281
+ elsif !new_geocode && self.geocoding
282
+ self.geocoding.destroy
283
+ end
284
+ new_geocode
285
+ rescue Graticule::Error => e
286
+ logger.warn e.message
287
+ end
288
+
289
+
290
+ def update_address(force = false) #:nodoc:
291
+ unless self.geocode.blank?
292
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
293
+ method = self.acts_as_geocodable_options[:address]
294
+ if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
295
+ self.send "#{method}=", self.geocode.to_location.to_s
296
+ end
297
+ else
298
+ self.acts_as_geocodable_options[:address].each do |attribute,method|
299
+ if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
300
+ self.send "#{method}=", self.geocode.send(attribute)
301
+ end
302
+ end
303
+ end
304
+
305
+ update_without_callbacks
306
+ end
307
+ end
308
+
309
+ def geo_attribute(attr_key) #:nodoc:
310
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
311
+ attr_name = self.acts_as_geocodable_options[:address]
312
+ attr_key == :street ? self.send(attr_name) : nil
313
+ else
314
+ attr_name = self.acts_as_geocodable_options[:address][attr_key]
315
+ attr_name && self.respond_to?(attr_name) ? self.send(attr_name) : nil
316
+ end
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end