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 +5 -0
- data/README +31 -37
- data/lib/acts_as_geocodable.rb +233 -278
- data/lib/acts_as_geocodable/geocode.rb +13 -13
- data/lib/acts_as_geocodable/remote_location.rb +6 -4
- data/lib/acts_as_geocodable/version.rb +3 -0
- data/lib/generators/acts_as_geocodable/USAGE +12 -0
- data/lib/generators/acts_as_geocodable/acts_as_geocodable_generator.rb +28 -0
- data/{generators/geocodable_migration → lib/generators/acts_as_geocodable}/templates/migration.rb +2 -2
- metadata +110 -41
- data/.gitignore +0 -3
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -23
- data/Rakefile +0 -39
- data/VERSION +0 -1
- data/about.yml +0 -7
- data/acts_as_geocodable.gemspec +0 -75
- data/generators/geocodable_migration/USAGE +0 -12
- data/generators/geocodable_migration/geocodable_migration_generator.rb +0 -7
- data/install.rb +0 -1
- data/lib/acts_as_geocodable/tasks/acts_as_geocodable_tasks.rake +0 -4
- data/rails/init.rb +0 -5
- data/test/acts_as_geocodable_test.rb +0 -382
- data/test/db/database.yml +0 -18
- data/test/db/schema.rb +0 -60
- data/test/fixtures/cities.yml +0 -12
- data/test/fixtures/geocodes.yml +0 -51
- data/test/fixtures/geocodings.yml +0 -15
- data/test/fixtures/vacations.yml +0 -15
- data/test/geocode_test.rb +0 -97
- data/test/test_helper.rb +0 -46
- data/uninstall.rb +0 -1
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -1,58 +1,53 @@
|
|
1
1
|
= acts_as_geocodable
|
2
2
|
|
3
|
-
acts_as_geocodable
|
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
|
-
|
17
|
+
event.distance_to "49423" #=> 1807.66560483205
|
24
18
|
|
25
|
-
|
19
|
+
Event.origin("97232", :within => 50)
|
26
20
|
|
27
|
-
|
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
|
-
|
25
|
+
Install as a gem
|
35
26
|
|
36
|
-
gem install
|
27
|
+
gem install acts_as_geocodable
|
37
28
|
|
38
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
88
|
-
@stores = Store.
|
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
|
data/lib/acts_as_geocodable.rb
CHANGED
@@ -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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
-
|
271
|
-
|
272
|
-
logger.warn e.message
|
219
|
+
elsif !new_geocode && self.geocoding
|
220
|
+
self.geocoding.destroy
|
273
221
|
end
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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.
|
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
|
-
|
294
|
-
|
295
|
-
|
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
|