acts_as_geocodable 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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