geokit-rails 2.3.2 → 2.5.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +29 -3
  3. data/CHANGELOG.md +5 -0
  4. data/README.markdown +7 -7
  5. data/gemfiles/rails3.gemfile +1 -0
  6. data/gemfiles/rails4.gemfile +1 -1
  7. data/gemfiles/rails5.gemfile +0 -1
  8. data/gemfiles/rails6.0.gemfile +8 -0
  9. data/gemfiles/rails6.1.gemfile +8 -0
  10. data/geokit-rails.gemspec +6 -5
  11. data/lib/geokit-rails/acts_as_mappable/class_methods.rb +297 -0
  12. data/lib/geokit-rails/acts_as_mappable/glue.rb +54 -0
  13. data/lib/geokit-rails/acts_as_mappable.rb +0 -347
  14. data/lib/geokit-rails/{core_extensions.rb → distance_collection.rb} +0 -0
  15. data/lib/geokit-rails/ip_geocode_lookup.rb +4 -7
  16. data/lib/geokit-rails/version.rb +1 -1
  17. data/lib/geokit-rails.rb +3 -1
  18. data/test/distance_collection_test.rb +42 -0
  19. data/test/dummy/Rakefile +6 -0
  20. data/test/dummy/app/assets/images/.keep +0 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +1 -0
  22. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  23. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  24. data/test/dummy/app/controllers/application_controller.rb +3 -0
  25. data/test/dummy/app/controllers/concerns/.keep +0 -0
  26. data/test/dummy/app/controllers/location_aware_controller.rb +43 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/jobs/application_job.rb +7 -0
  29. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  30. data/test/dummy/app/models/application_record.rb +3 -0
  31. data/test/dummy/app/models/concerns/.keep +0 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  33. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  34. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  35. data/test/dummy/bin/rails +4 -0
  36. data/test/dummy/bin/rake +4 -0
  37. data/test/dummy/bin/setup +33 -0
  38. data/test/dummy/config/application.rb +22 -0
  39. data/test/dummy/config/boot.rb +5 -0
  40. data/test/dummy/config/cable.yml +10 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/development.rb +68 -0
  44. data/test/dummy/config/environments/production.rb +87 -0
  45. data/test/dummy/config/environments/test.rb +60 -0
  46. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  47. data/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  48. data/test/dummy/config/initializers/inflections.rb +16 -0
  49. data/test/dummy/config/initializers/permissions_policy.rb +11 -0
  50. data/test/dummy/config/locales/en.yml +33 -0
  51. data/test/dummy/config/puma.rb +43 -0
  52. data/test/dummy/config/routes.rb +6 -0
  53. data/test/dummy/config/storage.yml +34 -0
  54. data/test/dummy/config.ru +6 -0
  55. data/test/dummy/lib/assets/.keep +0 -0
  56. data/test/dummy/log/.keep +0 -0
  57. data/test/dummy/public/404.html +67 -0
  58. data/test/dummy/public/422.html +67 -0
  59. data/test/dummy/public/500.html +66 -0
  60. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  61. data/test/dummy/public/apple-touch-icon.png +0 -0
  62. data/test/dummy/public/favicon.ico +0 -0
  63. data/test/dummy/storage/.keep +0 -0
  64. data/test/dummy/tmp/.keep +0 -0
  65. data/test/dummy/tmp/pids/.keep +0 -0
  66. data/test/dummy/tmp/storage/.keep +0 -0
  67. data/test/ip_geocode_lookup_test.rb +49 -0
  68. data/test/test_helper.rb +17 -0
  69. metadata +129 -27
  70. data/test/ip_geocode_lookup_test.disabled.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8abd196ef01725dfea499611d751fe08d1f42da5473c7896d526b16044e5c7a
4
- data.tar.gz: aef079a740792c981bb86d07d48c3686b2d939d5ce1792dd50d7d5c4bfe6bc05
3
+ metadata.gz: c4bc1edb34957251330bf39e6c777d1b8e01fba0e23388b08b3f07918bb9d1ab
4
+ data.tar.gz: c4f0899abed4cbc930dba47b122851a8ac34debdf5e05bbc39a8143fc61a3d36
5
5
  SHA512:
6
- metadata.gz: 7de113ad53d0ae12a3919337e70ec69d91a59f1ae9147235ee2eb42ccdd20e73438784bba5eb6122da7bb1185e28cd8d321af7a63c654b153ead266ac96d548b
7
- data.tar.gz: ad3e5699a610b9ef8ef451bd76bef841f36272a96896a04c2610d8cb21603f869a0c670a4754366d8b3df5dc13e3f03a3807f9a96848abe63ae0e30aa45b40e9
6
+ metadata.gz: d3f0175f5717e228e12cf36df832b792ec9da4ea8ba30bee8511222dfacd89a2634308d734c84b514e57d4055400e6899c62d774c1baff0d1d65e9d5b666dae3
7
+ data.tar.gz: e6939ec41db36ce5603606d3815952f32c2826390bac2a68752cc97c695094e15759ae1417150d5343bebdd606d29e6aa0397a9db62ad65b84bf11114795bc88
data/.travis.yml CHANGED
@@ -5,10 +5,15 @@ rvm:
5
5
  - 2.2
6
6
  - 2.3
7
7
  - 2.4
8
+ - 2.5
9
+ - 2.6
10
+ - ruby-head
8
11
  gemfile:
9
12
  - gemfiles/rails3.gemfile
10
13
  - gemfiles/rails4.gemfile
11
14
  - gemfiles/rails5.gemfile
15
+ - gemfiles/rails6.0.gemfile
16
+ - gemfiles/rails6.1.gemfile
12
17
  matrix:
13
18
  exclude:
14
19
  - rvm: 2.2
@@ -21,9 +26,30 @@ matrix:
21
26
  gemfile: gemfiles/rails5.gemfile
22
27
  - rvm: 2.1
23
28
  gemfile: gemfiles/rails5.gemfile
29
+ - rvm: 2.6
30
+ gemfile: gemfiles/rails5.gemfile
31
+ - rvm: ruby-head
32
+ gemfile: gemfiles/rails5.gemfile
33
+ - rvm: 2.0
34
+ gemfile: gemfiles/rails6.0.gemfile
35
+ - rvm: 2.0
36
+ gemfile: gemfiles/rails6.1.gemfile
37
+ - rvm: 2.1
38
+ gemfile: gemfiles/rails6.0.gemfile
39
+ - rvm: 2.1
40
+ gemfile: gemfiles/rails6.1.gemfile
41
+ - rvm: 2.2
42
+ gemfile: gemfiles/rails6.0.gemfile
43
+ - rvm: 2.2
44
+ gemfile: gemfiles/rails6.1.gemfile
45
+ - rvm: 2.3
46
+ gemfile: gemfiles/rails6.0.gemfile
47
+ - rvm: 2.3
48
+ gemfile: gemfiles/rails6.1.gemfile
49
+ - rvm: 2.4
50
+ gemfile: gemfiles/rails6.0.gemfile
51
+ - rvm: 2.4
52
+ gemfile: gemfiles/rails6.1.gemfile
24
53
  script: "bundle exec rake coverage"
25
- before_install:
26
- - gem install bundler
27
54
  bundler_args: --retry 5
28
- sudo: false
29
55
  cache: bundler
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 2.5.0
2
+
3
+ * Fixed dangerous YAML loading vulnerability
4
+ * Rebuilt integration tests
5
+
1
6
  ## 2.3.2
2
7
 
3
8
  * Fix sqlite3 adapter error
data/README.markdown CHANGED
@@ -1,11 +1,10 @@
1
1
  Geokit Rails
2
2
  ============
3
3
 
4
- [![Gem Version](https://badge.fury.io/rb/geokit-rails.png)](http://badge.fury.io/rb/geokit-rails)
5
- [![Build Status](https://travis-ci.org/geokit/geokit-rails.png?branch=master)](https://travis-ci.org/geokit/geokit-rails)
6
- [![Coverage Status](https://coveralls.io/repos/geokit/geokit-rails/badge.png?branch=master)](https://coveralls.io/r/geokit/geokit-rails)
7
- [![Dependency Status](https://gemnasium.com/geokit/geokit-rails.png?travis)](https://gemnasium.com/geokit/geokit-rails)
8
- [![Code Climate](https://codeclimate.com/github/geokit/geokit-rails.png)](https://codeclimate.com/github/geokit/geokit-rails)
4
+ [![Gem Version](https://badge.fury.io/rb/geokit-rails.svg)](http://badge.fury.io/rb/geokit-rails)
5
+ [![Build Status](https://travis-ci.org/geokit/geokit-rails.svg?branch=master)](https://travis-ci.org/geokit/geokit-rails)
6
+ [![Coverage Status](https://coveralls.io/repos/geokit/geokit-rails/badge.svg?branch=master)](https://coveralls.io/r/geokit/geokit-rails)
7
+ [![Code Climate](https://codeclimate.com/github/geokit/geokit-rails.svg)](https://codeclimate.com/github/geokit/geokit-rails)
9
8
 
10
9
  ## COMMUNICATION
11
10
 
@@ -498,8 +497,9 @@ Pass the lat/lng as a string, array or LatLng instance:
498
497
 
499
498
  ```ruby
500
499
  res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
501
- => #<Geokit::GeoLoc:0x558ed0 ...
502
- res.full_address "101-115 Main St, San Francisco, CA 94105, USA"
500
+ # => #<Geokit::GeoLoc:0x558ed0 ...
501
+ res.full_address
502
+ # => "101-115 Main St, San Francisco, CA 94105, USA"
503
503
  ```
504
504
 
505
505
  The address will usually appear as a range, as it does in the above example.
@@ -2,6 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  group :development, :test do
4
4
  gem 'activerecord', '~> 3.2.0'
5
+ gem 'sqlite3', '~> 1.3.5'
5
6
  end
6
7
 
7
8
  gemspec :path => "../"
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  group :development, :test do
4
4
  gem 'activerecord', '~> 4.0'
5
- gem 'test-unit'
5
+ gem 'sqlite3', '1.3.7'
6
6
  end
7
7
 
8
8
  gemspec :path => "../"
@@ -2,7 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  group :development, :test do
4
4
  gem 'activerecord', '~> 5.0'
5
- gem 'test-unit'
6
5
  end
7
6
 
8
7
  gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'activerecord', '~> 6.0'
5
+ gem 'test-unit'
6
+ end
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'activerecord', '~> 6.1'
5
+ gem 'test-unit'
6
+ end
7
+
8
+ gemspec :path => "../"
data/geokit-rails.gemspec CHANGED
@@ -20,14 +20,15 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency 'rails', '>= 3.0'
21
21
  spec.add_dependency 'geokit', '~> 1.5'
22
22
  spec.add_development_dependency "bundler", "> 1.0"
23
- spec.add_development_dependency "simplecov", "~> 0.16.1"
23
+ spec.add_development_dependency "simplecov", ">= 0.16.1"
24
24
  spec.add_development_dependency "simplecov-rcov"
25
+ spec.add_development_dependency 'net-http'
25
26
  spec.add_development_dependency 'rake'
26
27
  spec.add_development_dependency 'test-unit'
27
28
  spec.add_development_dependency "mocha", "~> 0.9"
28
- spec.add_development_dependency 'coveralls'
29
- spec.add_development_dependency "mysql2", "~> 0.2"
30
- spec.add_development_dependency "activerecord-mysql2spatial-adapter"
31
- spec.add_development_dependency "pg", "~> 0.10"
29
+ spec.add_development_dependency 'coveralls_reborn'
30
+ spec.add_development_dependency "mysql2", ">= 0.2"
31
+ # spec.add_development_dependency "activerecord-mysql2spatial-adapter"
32
+ spec.add_development_dependency "pg", ">= 0.10"
32
33
  spec.add_development_dependency "sqlite3"
33
34
  end
@@ -0,0 +1,297 @@
1
+ module Geokit
2
+ module ActsAsMappable
3
+
4
+ # Class methods included in models when +acts_as_mappable+ is called
5
+ module ClassMethods
6
+
7
+ # A proxy to an instance of a finder adapter, inferred from the connection's adapter.
8
+ def geokit_finder_adapter
9
+ @geokit_finder_adapter ||= begin
10
+ unless Adapters.const_defined?(connection.adapter_name.camelcase)
11
+ filename = connection.adapter_name.downcase
12
+ require File.join("geokit-rails", "adapters", filename)
13
+ end
14
+ klass = Adapters.const_get(connection.adapter_name.camelcase)
15
+ if klass.class == Module
16
+ # For some reason Mysql2 adapter was defined in Adapters.constants but was Module instead of a Class
17
+ filename = connection.adapter_name.downcase
18
+ require File.join("geokit-rails", "adapters", filename)
19
+ # Re-init the klass after require
20
+ klass = Adapters.const_get(connection.adapter_name.camelcase)
21
+ end
22
+ klass.load(self) unless klass.loaded || skip_loading
23
+ klass.new(self)
24
+ rescue LoadError
25
+ raise UnsupportedAdapter, "`#{connection.adapter_name.downcase}` is not a supported adapter."
26
+ end
27
+ end
28
+
29
+ def within(distance, options = {})
30
+ options[:within] = distance
31
+ # Add bounding box to speed up SQL request.
32
+ bounds = formulate_bounds_from_distance(
33
+ options,
34
+ normalize_point_to_lat_lng(options[:origin]),
35
+ options[:units] || default_units)
36
+ with_latlng.where(bound_conditions(bounds)).
37
+ where(distance_conditions(options))
38
+ end
39
+ alias inside within
40
+
41
+ def beyond(distance, options = {})
42
+ options[:beyond] = distance
43
+ #geo_scope(options)
44
+ where(distance_conditions(options))
45
+ end
46
+ alias outside beyond
47
+
48
+ def in_range(range, options = {})
49
+ options[:range] = range
50
+ #geo_scope(options)
51
+ where(distance_conditions(options))
52
+ end
53
+
54
+ def in_bounds(bounds, options = {})
55
+ inclusive = options.delete(:inclusive) || false
56
+ options[:bounds] = bounds
57
+ #geo_scope(options)
58
+ #where(distance_conditions(options))
59
+ bounds = extract_bounds_from_options(options)
60
+ where(bound_conditions(bounds, inclusive))
61
+ end
62
+
63
+ def by_distance(options = {})
64
+ origin = extract_origin_from_options(options)
65
+ units = extract_units_from_options(options)
66
+ formula = extract_formula_from_options(options)
67
+ distance_column_name = distance_sql(origin, units, formula)
68
+ with_latlng.order(
69
+ Arel.sql(distance_column_name).send(options[:reverse] ? 'desc' : 'asc')
70
+ )
71
+ end
72
+
73
+ def with_latlng
74
+ where("#{qualified_lat_column_name} IS NOT NULL AND #{qualified_lng_column_name} IS NOT NULL")
75
+ end
76
+
77
+ def closest(options = {})
78
+ by_distance(options).limit(1)
79
+ end
80
+ alias nearest closest
81
+
82
+ def farthest(options = {})
83
+ by_distance({:reverse => true}.merge(options)).limit(1)
84
+ end
85
+
86
+ #def geo_scope(options = {})
87
+ # arel = self.is_a?(ActiveRecord::Relation) ? self : self.scoped
88
+
89
+ # origin = extract_origin_from_options(options)
90
+ # units = extract_units_from_options(options)
91
+ # formula = extract_formula_from_options(options)
92
+ # bounds = extract_bounds_from_options(options)
93
+
94
+ # if origin || bounds
95
+ # bounds = formulate_bounds_from_distance(options, origin, units) unless bounds
96
+
97
+ # if origin
98
+ # arel.distance_formula = distance_sql(origin, units, formula)
99
+ #
100
+ # if arel.select_values.blank?
101
+ # star_select = Arel::Nodes::SqlLiteral.new(arel.quoted_table_name + '.*')
102
+ # arel = arel.select(star_select)
103
+ # end
104
+ # end
105
+
106
+ # if bounds
107
+ # bound_conditions = bound_conditions(bounds)
108
+ # arel = arel.where(bound_conditions) if bound_conditions
109
+ # end
110
+
111
+ # distance_conditions = distance_conditions(options)
112
+ # arel = arel.where(distance_conditions) if distance_conditions
113
+
114
+ # if self.through
115
+ # arel = arel.includes(self.through)
116
+ # end
117
+ # end
118
+
119
+ # arel
120
+ #end
121
+
122
+ # Returns the distance calculation to be used as a display column or a condition. This
123
+ # is provide for anyone wanting access to the raw SQL.
124
+ def distance_sql(origin, units=default_units, formula=default_formula)
125
+ case formula
126
+ when :sphere
127
+ sql = sphere_distance_sql(origin, units)
128
+ when :flat
129
+ sql = flat_distance_sql(origin, units)
130
+ end
131
+ sql
132
+ end
133
+
134
+ private
135
+
136
+ # Override ActiveRecord::Base.relation to return an instance of Geokit::ActsAsMappable::Relation.
137
+ # TODO: Do we need to override JoinDependency#relation too?
138
+ #def relation
139
+ # # NOTE: This cannot be @relation as ActiveRecord already uses this to
140
+ # # cache *its* Relation object
141
+ # @_geokit_relation ||= Relation.new(self, arel_table)
142
+ # finder_needs_type_condition? ? @_geokit_relation.where(type_condition) : @_geokit_relation
143
+ #end
144
+
145
+ # If it's a :within query, add a bounding box to improve performance.
146
+ # This only gets called if a :bounds argument is not otherwise supplied.
147
+ def formulate_bounds_from_distance(options, origin, units)
148
+ distance = options[:within] if options.has_key?(:within) && options[:within].is_a?(Numeric)
149
+ distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range)
150
+ if distance
151
+ Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units)
152
+ else
153
+ nil
154
+ end
155
+ end
156
+
157
+ def distance_conditions(options)
158
+ origin = extract_origin_from_options(options)
159
+ units = extract_units_from_options(options)
160
+ formula = extract_formula_from_options(options)
161
+ distance_column_name = distance_sql(origin, units, formula)
162
+
163
+ if options.has_key?(:within)
164
+ Arel.sql(distance_column_name).lteq(options[:within])
165
+ elsif options.has_key?(:beyond)
166
+ Arel.sql(distance_column_name).gt(options[:beyond])
167
+ elsif options.has_key?(:range)
168
+ min_condition = Arel.sql(distance_column_name).gteq(options[:range].begin)
169
+ max_condition = if options[:range].exclude_end?
170
+ Arel.sql(distance_column_name).lt(options[:range].end)
171
+ else
172
+ Arel.sql(distance_column_name).lteq(options[:range].end)
173
+ end
174
+ min_condition.and(max_condition)
175
+ end
176
+ end
177
+
178
+ def bound_conditions(bounds, inclusive = false)
179
+ return nil unless bounds
180
+ if inclusive
181
+ lt_operator = :lteq
182
+ gt_operator = :gteq
183
+ else
184
+ lt_operator = :lt
185
+ gt_operator = :gt
186
+ end
187
+ sw,ne = bounds.sw, bounds.ne
188
+ lat, lng = Arel.sql(qualified_lat_column_name), Arel.sql(qualified_lng_column_name)
189
+ lat.send(gt_operator, sw.lat).and(lat.send(lt_operator, ne.lat)).and(
190
+ if bounds.crosses_meridian?
191
+ lng.send(lt_operator, ne.lng).or(lng.send(gt_operator, sw.lng))
192
+ else
193
+ lng.send(gt_operator, sw.lng).and(lng.send(lt_operator, ne.lng))
194
+ end
195
+ )
196
+ end
197
+
198
+ # Extracts the origin instance out of the options if it exists and returns
199
+ # it. If there is no origin, looks for latitude and longitude values to
200
+ # create an origin. The side-effect of the method is to remove these
201
+ # option keys from the hash.
202
+ def extract_origin_from_options(options)
203
+ origin = options.delete(:origin)
204
+ res = normalize_point_to_lat_lng(origin) if origin
205
+ res
206
+ end
207
+
208
+ # Extract the units out of the options if it exists and returns it. If
209
+ # there is no :units key, it uses the default. The side effect of the
210
+ # method is to remove the :units key from the options hash.
211
+ def extract_units_from_options(options)
212
+ units = options[:units] || default_units
213
+ options.delete(:units)
214
+ units
215
+ end
216
+
217
+ # Extract the formula out of the options if it exists and returns it. If
218
+ # there is no :formula key, it uses the default. The side effect of the
219
+ # method is to remove the :formula key from the options hash.
220
+ def extract_formula_from_options(options)
221
+ formula = options[:formula] || default_formula
222
+ options.delete(:formula)
223
+ formula
224
+ end
225
+
226
+ def extract_bounds_from_options(options)
227
+ bounds = options.delete(:bounds)
228
+ bounds = Geokit::Bounds.normalize(bounds) if bounds
229
+ end
230
+
231
+ # Geocode IP address.
232
+ def geocode_ip_address(origin)
233
+ geo_location = Geokit::Geocoders::MultiGeocoder.geocode(origin)
234
+ return geo_location if geo_location.success
235
+ raise Geokit::Geocoders::GeocodeError
236
+ end
237
+
238
+ # Given a point in a variety of (an address to geocode,
239
+ # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres)
240
+ # this method will normalize it into a Geokit::LatLng instance. The only thing this
241
+ # method adds on top of LatLng#normalize is handling of IP addresses
242
+ def normalize_point_to_lat_lng(point)
243
+ res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point)
244
+ res = Geokit::LatLng.normalize(point) unless res
245
+ res
246
+ end
247
+
248
+ # Looks for the distance column and replaces it with the distance sql. If an origin was not
249
+ # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database.
250
+ # Conditions are either a string or an array. In the case of an array, the first entry contains
251
+ # the condition.
252
+ def substitute_distance_in_where_values(arel, origin, units=default_units, formula=default_formula)
253
+ pattern = Regexp.new("\\b#{distance_column_name}\\b")
254
+ value = distance_sql(origin, units, formula)
255
+ arel.where_values.map! do |where_value|
256
+ if where_value.is_a?(String)
257
+ where_value.gsub(pattern, value)
258
+ else
259
+ where_value
260
+ end
261
+ end
262
+ arel
263
+ end
264
+
265
+ # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
266
+ # to the database in use.
267
+ def sphere_distance_sql(origin, units)
268
+ # "origin" can be a Geokit::LatLng (with :lat and :lng methods), e.g.
269
+ # when using geo_scope or it can be an ActsAsMappable with customized
270
+ # latitude and longitude methods, e.g. when using distance_sql.
271
+ lat = deg2rad(get_lat(origin))
272
+ lng = deg2rad(get_lng(origin))
273
+ multiplier = units_sphere_multiplier(units)
274
+ geokit_finder_adapter.sphere_distance_sql(lat, lng, multiplier) if geokit_finder_adapter
275
+ end
276
+
277
+ # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
278
+ # to the database in use.
279
+ def flat_distance_sql(origin, units)
280
+ lat_degree_units = units_per_latitude_degree(units)
281
+ lng_degree_units = units_per_longitude_degree(get_lat(origin), units)
282
+ geokit_finder_adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
283
+ end
284
+
285
+ def get_lat(origin)
286
+ origin.respond_to?(:lat) ? origin.lat \
287
+ : origin.send(:"#{lat_column_name}")
288
+ end
289
+
290
+ def get_lng(origin)
291
+ origin.respond_to?(:lng) ? origin.lng \
292
+ : origin.send(:"#{lng_column_name}")
293
+ end
294
+
295
+ end # ClassMethods
296
+ end
297
+ end
@@ -0,0 +1,54 @@
1
+ module Geokit
2
+ module ActsAsMappable
3
+
4
+ # Add the +acts_as_mappable+ method into ActiveRecord subclasses
5
+ module Glue # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods # :nodoc:
9
+ OPTION_SYMBOLS = [ :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name, :skip_loading ]
10
+
11
+ def acts_as_mappable(options = {})
12
+ metaclass = (class << self; self; end)
13
+
14
+ include Geokit::ActsAsMappable
15
+
16
+ cattr_accessor :through
17
+ self.through = options[:through]
18
+
19
+ if reflection = Geokit::ActsAsMappable.end_of_reflection_chain(self.through, self)
20
+ metaclass.instance_eval do
21
+ OPTION_SYMBOLS.each do |method_name|
22
+ define_method method_name do
23
+ reflection.klass.send(method_name)
24
+ end
25
+ end
26
+ end
27
+ else
28
+ cattr_accessor *OPTION_SYMBOLS
29
+
30
+ self.distance_column_name = options[:distance_column_name] || 'distance'
31
+ self.default_units = options[:default_units] || Geokit::default_units
32
+ self.default_formula = options[:default_formula] || Geokit::default_formula
33
+ self.lat_column_name = options[:lat_column_name] || 'lat'
34
+ self.lng_column_name = options[:lng_column_name] || 'lng'
35
+ self.skip_loading = options[:skip_loading]
36
+ self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}"
37
+ self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}"
38
+
39
+ if options.include?(:auto_geocode) && options[:auto_geocode]
40
+ # if the form auto_geocode=>true is used, let the defaults take over by suppling an empty hash
41
+ options[:auto_geocode] = {} if options[:auto_geocode] == true
42
+ cattr_accessor :auto_geocode_field, :auto_geocode_error_message
43
+ self.auto_geocode_field = options[:auto_geocode][:field] || 'address'
44
+ self.auto_geocode_error_message = options[:auto_geocode][:error_message] || 'could not locate address'
45
+
46
+ # set the actual callback here
47
+ before_validation :auto_geocode_address, :on => :create
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end # Glue
53
+ end
54
+ end