acts_as_geocodable 1.0.4 → 2.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.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ 2.0.0 - 2010-10-01
2
+ * Rewritten for Rails 3
3
+ * New Rails 3/ARel finder syntax
4
+ * Double Rainbows
5
+
1
6
  0.2.1 - 2008-8-8
2
7
  * Results are now WillPaginate compatible
3
8
 
data/README CHANGED
@@ -1,58 +1,53 @@
1
1
  = acts_as_geocodable
2
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.
3
+ acts_as_geocodable helps you 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
+ Beginning with version 2, we require Rails 3. Use one of the 1.0.x tags to work with Rails 2.3.
6
+
7
+ Also, we've adopted the ARel style syntax for finding records.
4
8
 
5
9
  == Usage
6
10
 
7
11
  event = Event.create :street => "777 NE Martin Luther King, Jr. Blvd.",
8
12
  :locality => "Portland", :region => "Oregon", :postal_code => 97232
9
-
13
+
10
14
  event.geocode.latitude #=> 45.529100000000
11
15
  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
16
 
23
- rename_column :geocodes, :city, :locality
17
+ event.distance_to "49423" #=> 1807.66560483205
24
18
 
25
- Also remember to change your mapping in your geocodable classes to use the :locality key instead of :city:
19
+ Event.origin("97232", :within => 50)
26
20
 
27
- class Event < ActiveRecord::Base
28
- acts_as_geocodable :address => {:street => :address1, :locality => :city,
29
- :region => :state, :postal_code => :zip}
30
- end
21
+ Event.origin("Portland, OR").nearest
31
22
 
32
23
  == Installation
33
24
 
34
- Graticule[link:http://rubyforge.org/projects/graticule] is used for all the heavy lifting.
25
+ Install as a gem
35
26
 
36
- gem install graticule --include-dependencies
27
+ gem install acts_as_geocodable
37
28
 
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
-
29
+ Graticule[link:http://rubyforge.org/projects/graticule] is used for all the heavy lifting and will be installed too.
46
30
 
47
31
  == Upgrading
48
-
32
+
49
33
  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
34
 
35
+ Also, 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:
36
+
37
+ rename_column :geocodes, :city, :locality
38
+
39
+ Also remember to change your mapping in your geocodable classes to use the :locality key instead of :city:
40
+
41
+ class Event < ActiveRecord::Base
42
+ acts_as_geocodable :address => {:street => :address1, :locality => :city,
43
+ :region => :state, :postal_code => :zip}
44
+ end
45
+
51
46
  == Configuration
52
47
 
53
48
  Create the required tables
54
-
55
- script/generate geocodable_migration add_geocodable_tables
49
+
50
+ rails generate acts_as_geocodable
56
51
  rake db:migrate
57
52
 
58
53
  Set the default geocoder in your environment.rb file.
@@ -62,13 +57,13 @@ Set the default geocoder in your environment.rb file.
62
57
  Then, in each model you want to make geocodable, add acts_as_geocodable.
63
58
 
64
59
  class Event < ActiveRecord::Base
65
- acts_as_geocodable
60
+ acts_as_geocodable
66
61
  end
67
62
 
68
63
  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
64
 
70
65
  class Event < ActiveRecord::Base
71
- acts_as_geocodable :address => {:street => :address1, :locality => :city, :region => :state, :postal_code => :zip}
66
+ acts_as_geocodable :address => {:street => :address1, :locality => :city, :region => :state, :postal_code => :zip}
72
67
  end
73
68
 
74
69
  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.
@@ -76,7 +71,7 @@ If that doesn't meet your needs, simply override the default +to_location+ metho
76
71
  acts_as_geocodable can also update your address fields with the data returned from the geocoding service:
77
72
 
78
73
  class Event < ActiveRecord::Base
79
- acts_as_geocodable :normalize_address => true
74
+ acts_as_geocodable :normalize_address => true
80
75
  end
81
76
 
82
77
  == IP-based Geocoding
@@ -84,8 +79,8 @@ acts_as_geocodable can also update your address fields with the data returned fr
84
79
  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
80
 
86
81
  def index
87
- @nearest = Store.find(:nearest, :origin => remote_location) if remote_location
88
- @stores = Store.find(:all)
82
+ @nearest = Store.origin(remote_location).nearest if remote_location
83
+ @stores = Store.all
89
84
  end
90
85
 
91
86
  Keep in mind that IP-based geocoding is not always accurate, and often will not return any results.
@@ -100,5 +95,4 @@ Patches and suggestions are welcome!
100
95
 
101
96
  == To Do
102
97
 
103
- * Documentation!!!
104
98
  * configurable formulas
@@ -1,307 +1,262 @@
1
+ require 'graticule'
1
2
  require 'acts_as_geocodable/geocoding'
2
3
  require 'acts_as_geocodable/geocode'
3
4
  require 'acts_as_geocodable/remote_location'
4
5
 
5
- module CollectiveIdea #:nodoc:
6
- module Acts #:nodoc:
7
- module Geocodable #:nodoc:
8
-
9
- def self.included(mod)
10
- mod.extend(ClassMethods)
6
+ module ActiveSupport::Callbacks::ClassMethods
7
+ def without_callback(*args, &block)
8
+ skip_callback(*args)
9
+ yield
10
+ set_callback(*args)
11
+ end
12
+ end
13
+
14
+ module ActsAsGeocodable #:nodoc:
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 :geocoding
44
+
45
+ has_one :geocoding, :as => :geocodable, :include => :geocode, :dependent => :destroy
46
+
47
+ after_save :attach_geocode
48
+
49
+ # Would love to do a simpler scope here, like:
50
+ # scope :with_geocode_fields, includes(:geocoding)
51
+ # But we need to use select() and it would get overwritten.
52
+ scope :with_geocode_fields, lambda {
53
+ joins("JOIN geocodings ON
54
+ #{table_name}.#{primary_key} = geocodings.geocodable_id AND
55
+ geocodings.geocodable_type = '#{model_name}'
56
+ JOIN geocodes ON geocodings.geocode_id = geocodes.id")
57
+ }
58
+
59
+ # Use ActiveRecord ARel style syntax for finding records.
60
+ #
61
+ # Model.origin("Chicago, IL", :within => 10)
62
+ #
63
+ # a +distance+ attribute indicating the distance
64
+ # to the origin is added to each of the results:
65
+ #
66
+ # Model.origin("Portland, OR").first.distance #=> 388.383
67
+ #
68
+ # == Options
69
+ #
70
+ # * <tt>origin</tt>: A Geocode, String, or geocodable model that specifies
71
+ # the origin
72
+ # * <tt>:within</tt>: Limit to results within this radius of the origin
73
+ # * <tt>:beyond</tt>: Limit to results outside of this radius from the origin
74
+ # * <tt>:units</tt>: Units to use for <tt>:within</tt> or <tt>:beyond</tt>.
75
+ # Default is <tt>:miles</tt> unless specified otherwise in the +acts_as_geocodable+
76
+ # declaration.
77
+ #
78
+ scope :origin, lambda {|*args|
79
+ origin = args[0]
80
+ options = {
81
+ :units => acts_as_geocodable_options[:units],
82
+ }.merge(args[1] || {})
83
+ distance_sql = sql_for_distance(origin, options[:units])
84
+
85
+ scope = with_geocode_fields.select("#{table_name}.*, #{distance_sql} AS
86
+ #{acts_as_geocodable_options[:distance_column]}")
87
+
88
+ scope = scope.where("#{distance_sql} > #{options[:beyond]}") if options[:beyond]
89
+ scope = scope.where("#{distance_sql} <= #{options[:within]}") if options[:within]
90
+ scope
91
+ }
92
+
93
+ scope :near, order("#{acts_as_geocodable_options[:distance_column]} ASC")
94
+ scope :far, order("#{acts_as_geocodable_options[:distance_column]} DESC")
95
+
96
+ include ActsAsGeocodable::Model
97
+ end
98
+
99
+ module Model
100
+ extend ActiveSupport::Concern
101
+
102
+ module ClassMethods
103
+
104
+ # Find the nearest location to the given origin
105
+ #
106
+ # Model.origin("Grand Rapids, MI").nearest
107
+ #
108
+ def nearest
109
+ near.first
11
110
  end
12
111
 
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
-
112
+ # Find the farthest location to the given origin
113
+ #
114
+ # Model.origin("Grand Rapids, MI").farthest
115
+ #
116
+ def farthest
117
+ far.first
53
118
  end
54
119
 
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
120
+ # Convert the given location to a Geocode
121
+ def location_to_geocode(location)
122
+ case location
123
+ when Geocode then location
124
+ when InstanceMethods then location.geocode
125
+ when String, Fixnum then Geocode.find_or_create_by_query(location)
136
126
  end
137
-
138
- # Validate that the model can be geocoded
139
- #
140
- # Options:
141
- # * <tt>:message</tt>: Added to errors base (Default: Address could not be geocoded.)
142
- # * <tt>:allow_nil</tt>: If all the address attributes are blank, then don't try to
143
- # validate the geocode (Default: false)
144
- # * <tt>:precision</tt>: Require a minimum geocoding precision
145
- #
146
- # validates_as_geocodable also takes a block that you can use to performa additional
147
- # checks on the geocode. If this block returns false, then validation will fail.
148
- #
149
- # validates_as_geocodable do |geocode|
150
- # geocode.country == "US"
151
- # end
152
- #
153
- def validates_as_geocodable(options = {})
154
- options = options.reverse_merge :message => "Address could not be geocoded.", :allow_nil => false
155
- validate do |model|
156
- is_blank = model.to_location.attributes.except(:precision).all?(&:blank?)
157
- unless options[:allow_nil] && is_blank
158
- geocode = model.send :attach_geocode
159
- if !geocode ||
160
- (options[:precision] && geocode.precision < options[:precision]) ||
161
- (block_given? && yield(geocode) == false)
162
- model.errors.add_to_base options[:message]
163
- end
164
- end
165
- end
166
- end
167
-
168
- private
169
-
170
- def add_distance_to_select!(origin, options)
171
- (options[:select] ||= "#{table_name}.*") <<
172
- ", #{sql_for_distance(origin, options[:units])} AS
173
- #{acts_as_geocodable_options[:distance_column]}"
174
- end
175
-
176
- def with_proximity!(args, options)
177
- if [:nearest, :farthest].include?(args.first)
178
- raise ArgumentError, ":include cannot be specified with :nearest and :farthest" if options[:include]
179
- direction = args.first == :nearest ? "ASC" : "DESC"
180
- args[0] = :first
181
- with_scope :find => { :order => "#{acts_as_geocodable_options[:distance_column]} #{direction}"} do
182
- yield
127
+ end
128
+
129
+ # Validate that the model can be geocoded
130
+ #
131
+ # Options:
132
+ # * <tt>:message</tt>: Added to errors base (Default: Address could not be geocoded.)
133
+ # * <tt>:allow_nil</tt>: If all the address attributes are blank, then don't try to
134
+ # validate the geocode (Default: false)
135
+ # * <tt>:precision</tt>: Require a minimum geocoding precision
136
+ #
137
+ # validates_as_geocodable also takes a block that you can use to performa additional
138
+ # checks on the geocode. If this block returns false, then validation will fail.
139
+ #
140
+ # validates_as_geocodable do |geocode|
141
+ # geocode.country == "US"
142
+ # end
143
+ #
144
+ def validates_as_geocodable(options = {})
145
+ options = options.reverse_merge :message => "Address could not be geocoded.", :allow_nil => false
146
+ validate do |model|
147
+ is_blank = model.to_location.attributes.except(:precision).all?(&:blank?)
148
+ unless options[:allow_nil] && is_blank
149
+ geocode = model.send :attach_geocode
150
+ if !geocode ||
151
+ (options[:precision] && geocode.precision < options[:precision]) ||
152
+ (block_given? && yield(geocode) == false)
153
+ model.errors.add :base, options[:message]
183
154
  end
184
- else
185
- yield
186
155
  end
187
156
  end
188
-
189
- def join_geocodes(&block)
190
- with_scope :find => { :joins => "JOIN geocodings ON
191
- #{table_name}.#{primary_key} = geocodings.geocodable_id AND
192
- geocodings.geocodable_type = '#{class_name}'
193
- JOIN geocodes ON geocodings.geocode_id = geocodes.id" } do
194
- yield
195
- end
196
- end
197
-
198
- def geocode_conditions!(options, origin)
199
- units = options.delete(:units)
200
- conditions = []
201
- conditions << "#{sql_for_distance(origin, units)} <= #{options.delete(:within)}" if options[:within]
202
- conditions << "#{sql_for_distance(origin, units)} > #{options.delete(:beyond)}" if options[:beyond]
203
- if conditions.empty?
204
- yield
205
- else
206
- with_scope(:find => { :conditions => conditions.join(" AND ") }) { yield }
207
- end
208
- end
209
-
210
- def sql_for_distance(origin, units = acts_as_geocodable_options[:units])
211
- Graticule::Distance::Spherical.to_sql(
212
- :latitude => origin.latitude,
213
- :longitude => origin.longitude,
214
- :latitude_column => "geocodes.latitude",
215
- :longitude_column => "geocodes.longitude",
216
- :units => units
217
- )
218
- end
219
-
220
157
  end
221
158
 
222
- module InstanceMethods
223
-
224
- # Get the geocode for this model
225
- def geocode
226
- geocoding.geocode if geocoding
227
- end
228
-
229
- # Create a Graticule::Location
230
- def to_location
231
- Graticule::Location.new.tap do |location|
232
- [:street, :locality, :region, :postal_code, :country].each do |attr|
233
- location.send "#{attr}=", geo_attribute(attr)
234
- end
159
+ private
160
+
161
+ def sql_for_distance(origin, units = acts_as_geocodable_options[:units])
162
+ origin = location_to_geocode(origin)
163
+ Graticule::Distance::Spherical.to_sql(
164
+ :latitude => origin.latitude,
165
+ :longitude => origin.longitude,
166
+ :latitude_column => "geocodes.latitude",
167
+ :longitude_column => "geocodes.longitude",
168
+ :units => units
169
+ )
170
+ end
171
+ end
172
+
173
+ module InstanceMethods
174
+
175
+ # Get the geocode for this model
176
+ def geocode
177
+ geocoding.geocode if geocoding
178
+ end
179
+
180
+ # Create a Graticule::Location
181
+ def to_location
182
+ Graticule::Location.new.tap do |location|
183
+ [:street, :locality, :region, :postal_code, :country].each do |attr|
184
+ location.send "#{attr}=", geo_attribute(attr)
235
185
  end
236
186
  end
237
-
238
- # Get the distance to the given destination. The destination can be an
239
- # acts_as_geocodable model, a Geocode, or a string
240
- #
241
- # myhome.distance_to "Chicago, IL"
242
- # myhome.distance_to "49423"
243
- # myhome.distance_to other_model
244
- #
245
- # == Options
246
- # * <tt>:units</tt>: <tt>:miles</tt> or <tt>:kilometers</tt>
247
- # * <tt>:formula</tt>: The formula to use to calculate the distance. This can
248
- # be any formula supported by Graticule. The default is <tt>:haversine</tt>.
249
- #
250
- def distance_to(destination, options = {})
251
- units = options[:units] || acts_as_geocodable_options[:units]
252
- formula = options[:formula] || :haversine
253
-
254
- geocode = self.class.location_to_geocode(destination)
255
- self.geocode.distance_to(geocode, units, formula)
256
- end
257
-
258
- protected
259
-
260
- # Perform the geocoding
261
- def attach_geocode
262
- new_geocode = Geocode.find_or_create_by_location self.to_location unless self.to_location.blank?
263
- if new_geocode && self.geocode != new_geocode
187
+ end
188
+
189
+ # Get the distance to the given destination. The destination can be an
190
+ # acts_as_geocodable model, a Geocode, or a string
191
+ #
192
+ # myhome.distance_to "Chicago, IL"
193
+ # myhome.distance_to "49423"
194
+ # myhome.distance_to other_model
195
+ #
196
+ # == Options
197
+ # * <tt>:units</tt>: <tt>:miles</tt> or <tt>:kilometers</tt>
198
+ # * <tt>:formula</tt>: The formula to use to calculate the distance. This can
199
+ # be any formula supported by Graticule. The default is <tt>:haversine</tt>.
200
+ #
201
+ def distance_to(destination, options = {})
202
+ units = options[:units] || acts_as_geocodable_options[:units]
203
+ formula = options[:formula] || :haversine
204
+
205
+ geocode = self.class.location_to_geocode(destination)
206
+ self.geocode.distance_to(geocode, units, formula)
207
+ end
208
+
209
+ protected
210
+
211
+ # Perform the geocoding
212
+ def attach_geocode
213
+ new_geocode = Geocode.find_or_create_by_location self.to_location unless self.to_location.blank?
214
+ if new_geocode && self.geocode != new_geocode
215
+ run_callbacks :geocoding do
264
216
  self.geocoding = Geocoding.new :geocode => new_geocode
265
217
  self.update_address self.acts_as_geocodable_options[:normalize_address]
266
- callback :after_geocoding
267
- elsif !new_geocode && self.geocoding
268
- self.geocoding.destroy
269
218
  end
270
- new_geocode
271
- rescue Graticule::Error => e
272
- logger.warn e.message
219
+ elsif !new_geocode && self.geocoding
220
+ self.geocoding.destroy
273
221
  end
274
-
275
-
276
- def update_address(force = false) #:nodoc:
277
- unless self.geocode.blank?
278
- if self.acts_as_geocodable_options[:address].is_a? Symbol
279
- method = self.acts_as_geocodable_options[:address]
222
+ new_geocode
223
+ rescue Graticule::Error => e
224
+ logger.warn e.message
225
+ end
226
+
227
+
228
+ def update_address(force = false) #:nodoc:
229
+ unless self.geocode.blank?
230
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
231
+ method = self.acts_as_geocodable_options[:address]
232
+ if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
233
+ self.send "#{method}=", self.geocode.to_location.to_s
234
+ end
235
+ else
236
+ self.acts_as_geocodable_options[:address].each do |attribute,method|
280
237
  if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
281
- self.send "#{method}=", self.geocode.to_location.to_s
282
- end
283
- else
284
- self.acts_as_geocodable_options[:address].each do |attribute,method|
285
- if self.respond_to?("#{method}=") && (self.send(method).blank? || force)
286
- self.send "#{method}=", self.geocode.send(attribute)
287
- end
238
+ self.send "#{method}=", self.geocode.send(attribute)
288
239
  end
289
240
  end
290
-
291
- update_without_callbacks
292
241
  end
293
- end
294
-
295
- def geo_attribute(attr_key) #:nodoc:
296
- if self.acts_as_geocodable_options[:address].is_a? Symbol
297
- attr_name = self.acts_as_geocodable_options[:address]
298
- attr_key == :street ? self.send(attr_name) : nil
299
- else
300
- attr_name = self.acts_as_geocodable_options[:address][attr_key]
301
- attr_name && self.respond_to?(attr_name) ? self.send(attr_name) : nil
242
+
243
+ self.class.without_callback(:save, :after, :attach_geocode) do
244
+ save
302
245
  end
303
246
  end
304
247
  end
248
+
249
+ def geo_attribute(attr_key) #:nodoc:
250
+ if self.acts_as_geocodable_options[:address].is_a? Symbol
251
+ attr_name = self.acts_as_geocodable_options[:address]
252
+ attr_key == :street ? self.send(attr_name) : nil
253
+ else
254
+ attr_name = self.acts_as_geocodable_options[:address][attr_key]
255
+ attr_name && self.respond_to?(attr_name) ? self.send(attr_name) : nil
256
+ end
257
+ end
305
258
  end
306
259
  end
307
- end
260
+ end
261
+
262
+ ActiveRecord::Base.send :extend, ActsAsGeocodable