geokit-rails 2.3.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +29 -3
  3. data/CHANGELOG.md +9 -0
  4. data/README.markdown +13 -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 -329
  14. data/lib/geokit-rails/adapters/sqlite.rb +2 -2
  15. data/lib/geokit-rails/{core_extensions.rb → distance_collection.rb} +0 -0
  16. data/lib/geokit-rails/ip_geocode_lookup.rb +4 -7
  17. data/lib/geokit-rails/version.rb +1 -1
  18. data/lib/geokit-rails.rb +3 -1
  19. data/test/acts_as_mappable_test.rb +16 -0
  20. data/test/distance_collection_test.rb +42 -0
  21. data/test/dummy/Rakefile +6 -0
  22. data/test/dummy/app/assets/images/.keep +0 -0
  23. data/test/dummy/app/assets/stylesheets/application.css +1 -0
  24. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  25. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  26. data/test/dummy/app/controllers/application_controller.rb +3 -0
  27. data/test/dummy/app/controllers/concerns/.keep +0 -0
  28. data/test/dummy/app/controllers/location_aware_controller.rb +43 -0
  29. data/test/dummy/app/helpers/application_helper.rb +2 -0
  30. data/test/dummy/app/jobs/application_job.rb +7 -0
  31. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  32. data/test/dummy/app/models/application_record.rb +3 -0
  33. data/test/dummy/app/models/concerns/.keep +0 -0
  34. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  35. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  36. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  37. data/test/dummy/bin/rails +4 -0
  38. data/test/dummy/bin/rake +4 -0
  39. data/test/dummy/bin/setup +33 -0
  40. data/test/dummy/config/application.rb +22 -0
  41. data/test/dummy/config/boot.rb +5 -0
  42. data/test/dummy/config/cable.yml +10 -0
  43. data/test/dummy/config/database.yml +25 -0
  44. data/test/dummy/config/environment.rb +5 -0
  45. data/test/dummy/config/environments/development.rb +68 -0
  46. data/test/dummy/config/environments/production.rb +87 -0
  47. data/test/dummy/config/environments/test.rb +60 -0
  48. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  49. data/test/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  50. data/test/dummy/config/initializers/inflections.rb +16 -0
  51. data/test/dummy/config/initializers/permissions_policy.rb +11 -0
  52. data/test/dummy/config/locales/en.yml +33 -0
  53. data/test/dummy/config/puma.rb +43 -0
  54. data/test/dummy/config/routes.rb +6 -0
  55. data/test/dummy/config/storage.yml +34 -0
  56. data/test/dummy/config.ru +6 -0
  57. data/test/dummy/lib/assets/.keep +0 -0
  58. data/test/dummy/log/.keep +0 -0
  59. data/test/dummy/public/404.html +67 -0
  60. data/test/dummy/public/422.html +67 -0
  61. data/test/dummy/public/500.html +66 -0
  62. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  63. data/test/dummy/public/apple-touch-icon.png +0 -0
  64. data/test/dummy/public/favicon.ico +0 -0
  65. data/test/dummy/storage/.keep +0 -0
  66. data/test/dummy/tmp/.keep +0 -0
  67. data/test/dummy/tmp/pids/.keep +0 -0
  68. data/test/dummy/tmp/storage/.keep +0 -0
  69. data/test/fixtures/companies.yml +3 -1
  70. data/test/ip_geocode_lookup_test.rb +49 -0
  71. data/test/schema.rb +1 -0
  72. data/test/test_helper.rb +17 -0
  73. metadata +128 -27
  74. data/test/ip_geocode_lookup_test.disabled.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 60cc7291a94e6384a48947ecb040a472b26c0ac6
4
- data.tar.gz: e067b31c96bf53bbe4a3be1f6acf9f490e0c2c16
2
+ SHA256:
3
+ metadata.gz: c4bc1edb34957251330bf39e6c777d1b8e01fba0e23388b08b3f07918bb9d1ab
4
+ data.tar.gz: c4f0899abed4cbc930dba47b122851a8ac34debdf5e05bbc39a8143fc61a3d36
5
5
  SHA512:
6
- metadata.gz: 06e80fa935a623e414e484df78abbc3fb8795c59babe7c40acc152a415b0ca12563be5c3bbaca82be6b36701cea934a8d5d9ae79b1a77defd9a5dd7735e71eb3
7
- data.tar.gz: 617c4629c9a48b244dbaa9c8afed2cfb7315de6309e11c1d775892174e5d4166c23158d150a506f6f8f7084c0fd1f42e7549baddc8194d83f4c8e62cf40e59b3
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,12 @@
1
+ ## 2.5.0
2
+
3
+ * Fixed dangerous YAML loading vulnerability
4
+ * Rebuilt integration tests
5
+
6
+ ## 2.3.2
7
+
8
+ * Fix sqlite3 adapter error
9
+
1
10
  ## 2.3.1
2
11
 
3
12
  * Fix Rails 5.2 deprecation warning
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
 
@@ -264,6 +263,12 @@ If you are displaying points on a map, you probably need to query for whatever f
264
263
  Store.in_bounds([sw_point,ne_point]).all
265
264
  ```
266
265
 
266
+ If you want the query to return things that are located on the rectangular bounds, specify the `inclusive` option set to true:
267
+
268
+ ```ruby
269
+ Store.in_bounds([sw_point,ne_point], :inclusive => true).all
270
+ ```
271
+
267
272
  The input to `bounds` can be an array with the two points or a Bounds object. However you provide them, the order should always be the southwest corner, northeast corner of the rectangle. Typically, you will be getting the sw\_point and ne\_point from a map that is displayed on a web page.
268
273
 
269
274
  If you need to calculate the bounding box from a point and radius, you can do that:
@@ -492,8 +497,9 @@ Pass the lat/lng as a string, array or LatLng instance:
492
497
 
493
498
  ```ruby
494
499
  res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
495
- => #<Geokit::GeoLoc:0x558ed0 ...
496
- 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"
497
503
  ```
498
504
 
499
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"
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