├── .gitignore ├── .travis.yml ├── CHANGELOG.rdoc ├── CONFIG.markdown ├── Gemfile ├── MIT-LICENSE ├── README.markdown ├── Rakefile ├── geokit-rails3.gemspec ├── lib ├── geokit-rails3.rb └── geokit-rails3 │ ├── acts_as_mappable.rb │ ├── adapters │ ├── abstract.rb │ ├── mysql.rb │ ├── mysql2.rb │ ├── postgresql.rb │ ├── sqlite.rb │ └── sqlserver.rb │ ├── core_extensions.rb │ ├── defaults.rb │ ├── geocoder_control.rb │ ├── ip_geocode_lookup.rb │ ├── railtie.rb │ └── version.rb └── test ├── acts_as_mappable_test.rb ├── boot.rb ├── database.yml ├── fixtures ├── companies.yml ├── custom_locations.yml ├── locations.yml ├── mock_addresses.yml ├── mock_families.yml ├── mock_houses.yml ├── mock_organizations.yml ├── mock_people.yml └── stores.yml ├── ip_geocode_lookup_test.disabled.rb ├── models ├── company.rb ├── custom_location.rb ├── location.rb ├── mock_address.rb ├── mock_family.rb ├── mock_house.rb ├── mock_organization.rb ├── mock_person.rb └── store.rb ├── schema.rb ├── tasks.rake └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # Bundler 2 | # http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/ 3 | Gemfile.lock 4 | 5 | # Test log files 6 | test/*-debug.log 7 | 8 | # SQLite Test DB 9 | test/test.sqlite3 10 | 11 | # SVN 12 | .svn 13 | 14 | # RVM 15 | .rvmrc 16 | 17 | # TextMate 18 | *.tmproj 19 | 20 | # rcov generated 21 | coverage 22 | 23 | # rdoc generated 24 | rdoc 25 | 26 | # yard generated 27 | doc 28 | .yardoc 29 | 30 | # bundler 31 | .bundle 32 | 33 | # builds 34 | *.gem 35 | 36 | # For MacOS: 37 | .DS_Store 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | - 2.0.0 7 | script: "bundle exec rake coverage" 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.rdoc: -------------------------------------------------------------------------------- 1 | == 2009-10-02 / Version 1.2.0 2 | * Overhaul the test suite to be independent of a Rails project 3 | * Added concept of database adapter. Ported mysql/postgresql conditional code to their own adapter. 4 | * Added SQL Server support. THANKS http://github.com/brennandunn for all the improvements in this release 5 | 6 | == 2009-09-26 / Version 1.1.3 7 | * documentation updates and updated to work with Geokit gem v1.5.0 8 | * IMPORTANT: in the Geokit gem, Geokit::Geocoders::timeout became Geokit::Geocoders::request_timeout for jruby compatibility. 9 | The plugin sets this in config/initializers/geokit_config.rb. So if you've upgraded the gem to 1.5.0, you need to 10 | make the change manually from Geokit::Geocoders::timeout to Geokit::Geocoders::request_timeout in config/initializers/geokit_config.rb 11 | 12 | == 2009-06-08 / Version 1.1.2 13 | * Added support for hashes in :through. So you can do: acts_as_mappable :through => { :state => :country } (Thanks José Valim). 14 | 15 | == 2009-05-22 / Version 1.1.1 16 | * Support for multiple ip geocoders (Thanks dreamcat4) 17 | * Now checks if either :origin OR :bounds is passed, and proceeds with geokit query if this is true (Thanks Glenn Powell) 18 | * Raises a helpful error if someone uses through but the association does not exists or was not defined yet (Thanks José Valim) 19 | 20 | == 2009-04-11 / Version 1.1.0 21 | * Fixed :through usages so that the through model is only included in the query if there 22 | is an :origin passed in (Only if it is a geokit search) (Thanks Glenn Powell) 23 | * Move library initialisation into lib/geokit-rails. init.rb uses lib/geokit-rails now (thanks Alban Peignier) 24 | * Handle the case where a user passes a hash to the :conditions Finder option (thanks Adam Greene) 25 | * Added ability to specify domain-specific API keys (Thanks Glenn Powell) 26 | 27 | == 2009-02-20 28 | * More powerful assosciations in the Rails Plugin:You can now specify a model as mappable "through" an associated model. 29 | In other words, that associated model is the actual mappable model with "lat" and "lng" attributes, but this "through" model 30 | can still utilize all Geokit's "find by distance" finders. Also Rails 2.3 compatibility (thanks github/glennpow) 31 | 32 | == 2008-12-18 33 | * Split Rails plugin from geocoder gem 34 | * updated for Rails 2.2.2 35 | 36 | == 2008-08-20 37 | * Further fix of distance calculation, this time in SQL. Now uses least() function, which is available in MySQL version 3.22.5+ and postgres versions 8.1+ 38 | 39 | == 2008-01-16 40 | * fixed the "zero-distance" bug (calculating between two points that are the same) 41 | 42 | == 2007-11-12 43 | * fixed a small but with queries crossing meridian, and also fixed find(:closest) 44 | 45 | == 2007-10-11 46 | * Fixed Rails2/Edge compatability -------------------------------------------------------------------------------- /CONFIG.markdown: -------------------------------------------------------------------------------- 1 | # You can configure Geokit in your environment files 2 | 3 | These defaults are used in `Geokit::Mappable.distance_to` and in `acts_as_mappable` 4 | 5 | config.geokit.default_units = :miles 6 | config.geokit.default_formula = :sphere 7 | 8 | This is the timeout value in seconds to be used for calls to the geocoder web services. For no timeout at all, comment out the setting. The timeout unit is in seconds. 9 | 10 | config.geokit.geocoders.request_timeout = 3 11 | 12 | These settings are used if web service calls must be routed through a proxy. 13 | These setting can be `nil` if not needed, otherwise, addr and port must be filled in at a minimum. If the proxy requires authentication, the username and password can be provided as well. 14 | 15 | config.geokit.geocoders.proxy_addr = nil 16 | config.geokit.geocoders.proxy_port = nil 17 | config.geokit.geocoders.proxy_user = nil 18 | config.geokit.geocoders.proxy_pass = nil 19 | 20 | This is your yahoo application key for the Yahoo Geocoder. 21 | 22 | See [http://developer.yahoo.com/faq/index.html#appid](http://developer.yahoo.com/faq/index.html#appid) 23 | and [http://developer.yahoo.com/maps/rest/V1/geocode.html](http://developer.yahoo.com/maps/rest/V1/geocode.html) 24 | 25 | config.geokit.geocoders.yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY' 26 | 27 | This is your Google Maps geocoder key. 28 | 29 | See [http://www.google.com/apis/maps/signup.html](http://www.google.com/apis/maps/signup.html) 30 | and [http://www.google.com/apis/maps/documentation/#Geocoding_Examples](http://www.google.com/apis/maps/documentation/#Geocoding_Examples) 31 | 32 | config.geokit.geocoders.google = 'REPLACE_WITH_YOUR_GOOGLE_KEY' 33 | 34 | This is your username and password for **geocoder.us**. 35 | To use the free service, the value can be set to `nil` or `false`. 36 | For usage tied to an account, the value should be set to `username:password`. 37 | 38 | See [http://geocoder.us](http://geocoder.us) 39 | and [http://geocoder.us/user/signup](http://geocoder.us/user/signup) 40 | 41 | config.geokit.geocoders.geocoder_us = false 42 | 43 | This is your authorization key for **geocoder.ca**. 44 | To use the free service, the value can be set to `nil` or `false`. For usage tied to an account, set the value to the key obtained from 45 | **Geocoder.ca**. 46 | 47 | See [http://geocoder.ca](http://geocoder.ca) 48 | and [http://geocoder.ca/?register=1](http://geocoder.ca/?register=1) 49 | 50 | config.geokit.geocoders.geocoder_ca = false 51 | 52 | Add this to use a username with the Geonames geocoder 53 | 54 | config.geokitgeocoders.geonames="REPLACE_WITH_YOUR_GEONAMES_USERNAME" 55 | 56 | This is the order in which the geocoders are called in a failover scenario. 57 | If you only want to use a single geocoder, put a single symbol in the array. 58 | Valid symbols are `:google`, `:yahoo`, `:us`, and `:ca`. 59 | Be aware that there are **Terms of Use** restrictions on how you can use the various geocoders. Make sure you read up on relevant **Terms of Use** for each geocoder you are going to use. 60 | 61 | config.geokit.geocoders.provider_order = [:google,:us] 62 | 63 | The IP provider order. Valid symbols are `:ip`, `:geo_plugin`. 64 | As before, make sure you read up on relevant **Terms of Use** for each 65 | 66 | config.geokitgeocoders.ip_provider_order = [:geo_plugin,:ip] 67 | 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 Bill Eisenhauer & Andre Lewis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Geokit Rails 3 2 | ============== 3 | 4 | This project has been integrated into [**geokit-rails**](https://github.com/geokit/geokit-rails) 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rdoc/task' 5 | Rake::RDocTask.new do |rdoc| 6 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 7 | 8 | rdoc.rdoc_dir = 'rdoc' 9 | rdoc.title = "geokit-rails3 #{version}" 10 | rdoc.rdoc_files.include('README*') 11 | rdoc.rdoc_files.include('lib/**/*.rb') 12 | end 13 | 14 | load 'test/tasks.rake' 15 | 16 | desc 'Default: run unit tests.' 17 | task :default => :test -------------------------------------------------------------------------------- /geokit-rails3.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path("../lib/geokit-rails3/version", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "geokit-rails3" 6 | s.version = GeokitRails3::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.authors = ["Andre Lewis", "Bill Eisenhauer", "Jeremy Lecour"] 9 | s.email = ["andre@earthcode.com", "bill_eisenhauer@yahoo.com", "jeremy.lecour@gmail.com"] 10 | s.homepage = "http://github.com/jlecour/geokit-rails3" 11 | s.summary = "Integrate Geokit with Rails 3" 12 | s.description = "Port of the Rails plugin \"geokit-rails\" to Rails 3, as a gem" 13 | 14 | s.required_rubygems_version = ">= 1.3.6" 15 | # s.rubyforge_project = "test_gem" 16 | 17 | s.add_runtime_dependency 'rails', '>= 3.0' 18 | s.add_runtime_dependency 'geokit', '~> 1.5' 19 | 20 | s.add_development_dependency "bundler", "> 1.0" 21 | s.add_development_dependency "simplecov" 22 | s.add_development_dependency "simplecov-rcov" 23 | s.add_development_dependency "mocha", "~> 0.9" 24 | s.add_development_dependency "mysql", "~> 2.8" 25 | s.add_development_dependency "mysql2", "~> 0.2" 26 | s.add_development_dependency "pg", "~> 0.10" 27 | s.add_development_dependency "sqlite3" 28 | 29 | s.files = Dir.glob("lib/**/*.rb") 30 | s.test_files = Dir.glob("test/**/*") 31 | s.require_path = "lib" 32 | end 33 | -------------------------------------------------------------------------------- /lib/geokit-rails3.rb: -------------------------------------------------------------------------------- 1 | require 'geokit' 2 | 3 | require 'geokit-rails3/railtie' 4 | require 'geokit-rails3/core_extensions' 5 | 6 | require 'geokit-rails3/defaults' 7 | require 'geokit-rails3/adapters/abstract' 8 | require 'geokit-rails3/acts_as_mappable' 9 | require 'geokit-rails3/geocoder_control' 10 | require 'geokit-rails3/ip_geocode_lookup' -------------------------------------------------------------------------------- /lib/geokit-rails3/acts_as_mappable.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'active_support/concern' 3 | 4 | module Geokit 5 | module ActsAsMappable 6 | 7 | class UnsupportedAdapter < StandardError ; end 8 | 9 | # Add the +acts_as_mappable+ method into ActiveRecord subclasses 10 | module Glue # :nodoc: 11 | extend ActiveSupport::Concern 12 | 13 | module ClassMethods # :nodoc: 14 | def acts_as_mappable(options = {}) 15 | metaclass = (class << self; self; end) 16 | 17 | include Geokit::ActsAsMappable 18 | 19 | cattr_accessor :through 20 | self.through = options[:through] 21 | 22 | if reflection = Geokit::ActsAsMappable.end_of_reflection_chain(self.through, self) 23 | metaclass.instance_eval do 24 | [ :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name ].each do |method_name| 25 | define_method method_name do 26 | reflection.klass.send(method_name) 27 | end 28 | end 29 | end 30 | else 31 | cattr_accessor :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name 32 | 33 | self.distance_column_name = options[:distance_column_name] || 'distance' 34 | self.default_units = options[:default_units] || Geokit::default_units 35 | self.default_formula = options[:default_formula] || Geokit::default_formula 36 | self.lat_column_name = options[:lat_column_name] || 'lat' 37 | self.lng_column_name = options[:lng_column_name] || 'lng' 38 | self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}" 39 | self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}" 40 | 41 | if options.include?(:auto_geocode) && options[:auto_geocode] 42 | # if the form auto_geocode=>true is used, let the defaults take over by suppling an empty hash 43 | options[:auto_geocode] = {} if options[:auto_geocode] == true 44 | cattr_accessor :auto_geocode_field, :auto_geocode_error_message 45 | self.auto_geocode_field = options[:auto_geocode][:field] || 'address' 46 | self.auto_geocode_error_message = options[:auto_geocode][:error_message] || 'could not locate address' 47 | 48 | # set the actual callback here 49 | before_validation :auto_geocode_address, :on => :create 50 | end 51 | end 52 | end 53 | end 54 | end # Glue 55 | 56 | class Relation < ActiveRecord::Relation 57 | attr_accessor :distance_formula 58 | 59 | def where(opts, *rest) 60 | return self if opts.blank? 61 | relation = clone 62 | where_values = build_where(opts, rest) 63 | relation.where_values += substitute_distance_in_values(where_values) 64 | relation 65 | end 66 | 67 | def order(*args) 68 | return self if args.blank? 69 | relation = clone 70 | order_values = args.flatten 71 | relation.order_values += substitute_distance_in_values(order_values) 72 | relation 73 | end 74 | 75 | private 76 | def substitute_distance_in_values(values) 77 | return values unless @distance_formula 78 | # substitute distance with the actual distance equation 79 | pattern = Regexp.new("\\b#{@klass.distance_column_name}\\b") 80 | values.map {|value| value.is_a?(String) ? value.gsub(pattern, @distance_formula) : value } 81 | end 82 | end 83 | 84 | extend ActiveSupport::Concern 85 | 86 | included do 87 | include Geokit::Mappable 88 | end 89 | 90 | # Class methods included in models when +acts_as_mappable+ is called 91 | module ClassMethods 92 | 93 | # A proxy to an instance of a finder adapter, inferred from the connection's adapter. 94 | def adapter 95 | @adapter ||= begin 96 | require File.join(File.dirname(__FILE__), 'adapters', connection.adapter_name.downcase) 97 | klass = Adapters.const_get(connection.adapter_name.camelcase) 98 | klass.load(self) unless klass.loaded 99 | klass.new(self) 100 | rescue LoadError 101 | raise UnsupportedAdapter, "`#{connection.adapter_name.downcase}` is not a supported adapter." 102 | end 103 | end 104 | 105 | def within(distance, options = {}) 106 | options[:within] = distance 107 | geo_scope(options) 108 | end 109 | alias inside within 110 | 111 | def beyond(distance, options = {}) 112 | options[:beyond] = distance 113 | geo_scope(options) 114 | end 115 | alias outside beyond 116 | 117 | def in_range(range, options = {}) 118 | options[:range] = range 119 | geo_scope(options) 120 | end 121 | 122 | def in_bounds(bounds, options = {}) 123 | options[:bounds] = bounds 124 | geo_scope(options) 125 | end 126 | 127 | def by_distance(options = {}) 128 | geo_scope(options).order("#{distance_column_name} asc") 129 | end 130 | 131 | def closest(options = {}) 132 | by_distance(options).first(1) 133 | end 134 | alias nearest closest 135 | 136 | def farthest(options = {}) 137 | by_distance(options).last(1) 138 | end 139 | 140 | def geo_scope(options = {}) 141 | arel = self.is_a?(ActiveRecord::Relation) ? self : self.scoped 142 | 143 | origin = extract_origin_from_options(options) 144 | units = extract_units_from_options(options) 145 | formula = extract_formula_from_options(options) 146 | bounds = extract_bounds_from_options(options) 147 | 148 | if origin || bounds 149 | bounds = formulate_bounds_from_distance(options, origin, units) unless bounds 150 | 151 | if origin 152 | arel.distance_formula = distance_sql(origin, units, formula) 153 | 154 | if arel.select_values.blank? 155 | star_select = Arel::SqlLiteral.new(arel.quoted_table_name + '.*') 156 | arel = arel.select(star_select) 157 | end 158 | end 159 | 160 | if bounds 161 | bound_conditions = bound_conditions(bounds) 162 | arel = arel.where(bound_conditions) if bound_conditions 163 | end 164 | 165 | distance_conditions = distance_conditions(options) 166 | arel = arel.where(distance_conditions) if distance_conditions 167 | 168 | if self.through 169 | arel = arel.includes(self.through) 170 | end 171 | end 172 | 173 | arel 174 | end 175 | 176 | # Returns the distance calculation to be used as a display column or a condition. This 177 | # is provide for anyone wanting access to the raw SQL. 178 | def distance_sql(origin, units=default_units, formula=default_formula) 179 | case formula 180 | when :sphere 181 | sql = sphere_distance_sql(origin, units) 182 | when :flat 183 | sql = flat_distance_sql(origin, units) 184 | end 185 | sql 186 | end 187 | 188 | private 189 | 190 | # Override ActiveRecord::Base.relation to return an instance of Geokit::ActsAsMappable::Relation. 191 | # TODO: Do we need to override JoinDependency#relation too? 192 | def relation 193 | # NOTE: This cannot be @relation as ActiveRecord already uses this to 194 | # cache *its* Relation object 195 | @_geokit_relation ||= Relation.new(self, arel_table) 196 | finder_needs_type_condition? ? @_geokit_relation.where(type_condition) : @_geokit_relation 197 | end 198 | 199 | # If it's a :within query, add a bounding box to improve performance. 200 | # This only gets called if a :bounds argument is not otherwise supplied. 201 | def formulate_bounds_from_distance(options, origin, units) 202 | distance = options[:within] if options.has_key?(:within) 203 | distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range) 204 | if distance 205 | res=Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units) 206 | else 207 | nil 208 | end 209 | end 210 | 211 | def distance_conditions(options) 212 | res = if options.has_key?(:within) 213 | "#{distance_column_name} <= #{options[:within]}" 214 | elsif options.has_key?(:beyond) 215 | "#{distance_column_name} > #{options[:beyond]}" 216 | elsif options.has_key?(:range) 217 | "#{distance_column_name} >= #{options[:range].first} AND #{distance_column_name} <#{'=' unless options[:range].exclude_end?} #{options[:range].last}" 218 | end 219 | Arel::SqlLiteral.new("(#{res})") if res.present? 220 | end 221 | 222 | def bound_conditions(bounds) 223 | sw,ne = bounds.sw, bounds.ne 224 | lng_sql = bounds.crosses_meridian? ? "(#{qualified_lng_column_name}<#{ne.lng} OR #{qualified_lng_column_name}>#{sw.lng})" : "#{qualified_lng_column_name}>#{sw.lng} AND #{qualified_lng_column_name}<#{ne.lng}" 225 | res = "#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}" 226 | Arel::SqlLiteral.new("(#{res})") if res.present? 227 | end 228 | 229 | # Extracts the origin instance out of the options if it exists and returns 230 | # it. If there is no origin, looks for latitude and longitude values to 231 | # create an origin. The side-effect of the method is to remove these 232 | # option keys from the hash. 233 | def extract_origin_from_options(options) 234 | origin = options.delete(:origin) 235 | res = normalize_point_to_lat_lng(origin) if origin 236 | res 237 | end 238 | 239 | # Extract the units out of the options if it exists and returns it. If 240 | # there is no :units key, it uses the default. The side effect of the 241 | # method is to remove the :units key from the options hash. 242 | def extract_units_from_options(options) 243 | units = options[:units] || default_units 244 | options.delete(:units) 245 | units 246 | end 247 | 248 | # Extract the formula out of the options if it exists and returns it. If 249 | # there is no :formula key, it uses the default. The side effect of the 250 | # method is to remove the :formula key from the options hash. 251 | def extract_formula_from_options(options) 252 | formula = options[:formula] || default_formula 253 | options.delete(:formula) 254 | formula 255 | end 256 | 257 | def extract_bounds_from_options(options) 258 | bounds = options.delete(:bounds) 259 | bounds = Geokit::Bounds.normalize(bounds) if bounds 260 | end 261 | 262 | # Geocode IP address. 263 | def geocode_ip_address(origin) 264 | geo_location = Geokit::Geocoders::MultiGeocoder.geocode(origin) 265 | return geo_location if geo_location.success 266 | raise Geokit::Geocoders::GeocodeError 267 | end 268 | 269 | # Given a point in a variety of (an address to geocode, 270 | # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres) 271 | # this method will normalize it into a Geokit::LatLng instance. The only thing this 272 | # method adds on top of LatLng#normalize is handling of IP addresses 273 | def normalize_point_to_lat_lng(point) 274 | res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point) 275 | res = Geokit::LatLng.normalize(point) unless res 276 | res 277 | end 278 | 279 | # Looks for the distance column and replaces it with the distance sql. If an origin was not 280 | # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database. 281 | # Conditions are either a string or an array. In the case of an array, the first entry contains 282 | # the condition. 283 | def substitute_distance_in_where_values(arel, origin, units=default_units, formula=default_formula) 284 | pattern = Regexp.new("\\b#{distance_column_name}\\b") 285 | value = distance_sql(origin, units, formula) 286 | arel.where_values.map! do |where_value| 287 | if where_value.is_a?(String) 288 | where_value.gsub(pattern, value) 289 | else 290 | where_value 291 | end 292 | end 293 | arel 294 | end 295 | 296 | # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned 297 | # to the database in use. 298 | def sphere_distance_sql(origin, units) 299 | lat = deg2rad(origin.lat) 300 | lng = deg2rad(origin.lng) 301 | multiplier = units_sphere_multiplier(units) 302 | 303 | adapter.sphere_distance_sql(lat, lng, multiplier) if adapter 304 | end 305 | 306 | # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned 307 | # to the database in use. 308 | def flat_distance_sql(origin, units) 309 | lat_degree_units = units_per_latitude_degree(units) 310 | lng_degree_units = units_per_longitude_degree(origin.lat, units) 311 | 312 | adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units) 313 | end 314 | 315 | end # ClassMethods 316 | 317 | # this is the callback for auto_geocoding 318 | def auto_geocode_address 319 | address=self.send(auto_geocode_field).to_s 320 | geo=Geokit::Geocoders::MultiGeocoder.geocode(address) 321 | 322 | if geo.success 323 | self.send("#{lat_column_name}=", geo.lat) 324 | self.send("#{lng_column_name}=", geo.lng) 325 | else 326 | errors.add(auto_geocode_field, auto_geocode_error_message) 327 | end 328 | 329 | geo.success 330 | end 331 | 332 | def self.end_of_reflection_chain(through, klass) 333 | while through 334 | reflection = nil 335 | if through.is_a?(Hash) 336 | association, through = through.to_a.first 337 | else 338 | association, through = through, nil 339 | end 340 | 341 | if reflection = klass.reflect_on_association(association) 342 | klass = reflection.klass 343 | else 344 | raise ArgumentError, "You gave #{association} in :through, but I could not find it on #{klass}." 345 | end 346 | end 347 | 348 | reflection 349 | end 350 | 351 | end # ActsAsMappable 352 | end # Geokit 353 | 354 | 355 | 356 | # ActiveRecord::Base.extend Geokit::ActsAsMappable -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/abstract.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class Abstract 4 | class NotImplementedError < StandardError ; end 5 | 6 | cattr_accessor :loaded 7 | 8 | class << self 9 | def load(klass) ; end 10 | end 11 | 12 | def initialize(klass) 13 | @owner = klass 14 | end 15 | 16 | def method_missing(method, *args, &block) 17 | return @owner.send(method, *args, &block) if @owner.respond_to?(method) 18 | super 19 | end 20 | 21 | def sphere_distance_sql(lat, lng, multiplier) 22 | raise NotImplementedError, '#sphere_distance_sql is not implemented' 23 | end 24 | 25 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 26 | raise NotImplementedError, '#flat_distance_sql is not implemented' 27 | end 28 | 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/mysql.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class MySQL < Abstract 4 | 5 | def sphere_distance_sql(lat, lng, multiplier) 6 | %| 7 | (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+ 8 | COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+ 9 | SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier}) 10 | | 11 | end 12 | 13 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 14 | %| 15 | SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+ 16 | POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2)) 17 | | 18 | end 19 | 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/mysql2.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class Mysql2 < Abstract 4 | 5 | def sphere_distance_sql(lat, lng, multiplier) 6 | %| 7 | (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+ 8 | COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+ 9 | SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier}) 10 | | 11 | end 12 | 13 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 14 | %| 15 | SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+ 16 | POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2)) 17 | | 18 | end 19 | 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/postgresql.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class PostgreSQL < Abstract 4 | 5 | def sphere_distance_sql(lat, lng, multiplier) 6 | %| 7 | (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+ 8 | COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+ 9 | SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier}) 10 | | 11 | end 12 | 13 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 14 | %| 15 | SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+ 16 | POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2)) 17 | | 18 | end 19 | 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/sqlite.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class SQLite < Abstract 4 | 5 | def self.add_numeric(name) 6 | @@connection.create_function name, 1, :numeric do |func, *args| 7 | func.result = yield(*args) 8 | end 9 | end 10 | 11 | def self.add_math(name) 12 | add_numeric name do |*n| 13 | Math.send name, *n 14 | end 15 | end 16 | 17 | class << self 18 | def load(klass) 19 | @@connection = klass.connection.raw_connection 20 | # Define the functions needed 21 | add_math 'sqrt' 22 | add_math 'cos' 23 | add_math 'acos' 24 | add_math 'sin' 25 | 26 | add_numeric('pow') { |n, m| n**m } 27 | add_numeric('radians') { |n| n * Math::PI / 180 } 28 | add_numeric('least') { |*args| args.min } 29 | end 30 | end 31 | 32 | def sphere_distance_sql(lat, lng, multiplier) 33 | %| 34 | (CASE WHEN #{qualified_lat_column_name} IS NULL OR #{qualified_lng_column_name} IS NULL THEN NULL ELSE 35 | (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+ 36 | COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+ 37 | SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier}) 38 | END) 39 | | 40 | end 41 | 42 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 43 | %| 44 | (CASE WHEN #{qualified_lat_column_name} IS NULL OR #{qualified_lng_column_name} IS NULL THEN NULL ELSE 45 | SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+ 46 | POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2)) 47 | END) 48 | | 49 | end 50 | 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/adapters/sqlserver.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | module Adapters 3 | class SQLServer < Abstract 4 | 5 | class << self 6 | 7 | def load(klass) 8 | klass.connection.execute <<-EOS 9 | if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[geokit_least]') and xtype in (N'FN', N'IF', N'TF')) 10 | drop function [dbo].[geokit_least] 11 | EOS 12 | 13 | klass.connection.execute <<-EOS 14 | CREATE FUNCTION [dbo].geokit_least (@value1 float,@value2 float) RETURNS float AS BEGIN 15 | return (SELECT CASE WHEN @value1 < @value2 THEN @value1 ELSE @value2 END) END 16 | EOS 17 | self.loaded = true 18 | end 19 | 20 | end 21 | 22 | def initialize(*args) 23 | super(*args) 24 | end 25 | 26 | def sphere_distance_sql(lat, lng, multiplier) 27 | %| 28 | (ACOS([dbo].geokit_least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+ 29 | COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+ 30 | SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier}) 31 | | 32 | end 33 | 34 | def flat_distance_sql(origin, lat_degree_units, lng_degree_units) 35 | %| 36 | SQRT(POWER(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+ 37 | POWER(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2)) 38 | | 39 | end 40 | 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/core_extensions.rb: -------------------------------------------------------------------------------- 1 | # Extend Array with a sort_by_distance method. 2 | class Array 3 | # This method creates a "distance" attribute on each object, calculates the 4 | # distance from the passed origin, and finally sorts the array by the 5 | # resulting distance. 6 | def sort_by_distance_from(origin, opts={}) 7 | warn "[DEPRECATION] `Array#sort_by_distance_from(origin, opts)` is deprecated. Please use Array#sort_by{|e| e.distance_to(origin, opts)} instead which is not destructive" 8 | self[0..-1] = sort_by{|e| e.distance_to(origin, opts)} 9 | end 10 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/defaults.rb: -------------------------------------------------------------------------------- 1 | module Geokit 2 | # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable 3 | @@default_units = :miles 4 | @@default_formula = :sphere 5 | 6 | [:default_units, :default_formula].each do |sym| 7 | class_eval <<-EOS, __FILE__, __LINE__ 8 | def self.#{sym} 9 | if defined?(#{sym.to_s.upcase}) 10 | #{sym.to_s.upcase} 11 | else 12 | @@#{sym} 13 | end 14 | end 15 | 16 | def self.#{sym}=(obj) 17 | @@#{sym} = obj 18 | end 19 | EOS 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/geokit-rails3/geocoder_control.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/concern' 2 | 3 | module Geokit 4 | module GeocoderControl 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | if self.respond_to? :before_filter 9 | self.send :before_filter, :set_geokit_domain 10 | end 11 | end 12 | 13 | def set_geokit_domain 14 | Geokit::Geocoders::domain = request.domain 15 | logger.debug("Geokit is using the domain: #{Geokit::Geocoders::domain}") 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/ip_geocode_lookup.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'active_support/concern' 3 | 4 | module Geokit 5 | # Contains a class method geocode_ip_address which can be used to enable automatic geocoding 6 | # for request IP addresses. The geocoded information is stored in a cookie and in the 7 | # session to minimize web service calls. The point of the helper is to enable location-based 8 | # websites to have a best-guess for new visitors. 9 | module IpGeocodeLookup 10 | extend ActiveSupport::Concern 11 | 12 | # Class method to mix into active record. 13 | module ClassMethods # :nodoc: 14 | def geocode_ip_address(filter_options = {}) 15 | before_filter :store_ip_location, filter_options 16 | end 17 | end 18 | 19 | private 20 | 21 | # Places the IP address' geocode location into the session if it 22 | # can be found. Otherwise, looks for a geo location cookie and 23 | # uses that value. The last resort is to call the web service to 24 | # get the value. 25 | def store_ip_location 26 | session[:geo_location] ||= retrieve_location_from_cookie_or_service 27 | cookies[:geo_location] = { :value => session[:geo_location].to_yaml, :expires => 30.days.from_now } if session[:geo_location] 28 | end 29 | 30 | # Uses the stored location value from the cookie if it exists. If 31 | # no cookie exists, calls out to the web service to get the location. 32 | def retrieve_location_from_cookie_or_service 33 | return YAML.load(cookies[:geo_location]) if cookies[:geo_location] 34 | location = Geocoders::MultiGeocoder.geocode(get_ip_address) 35 | return location.success ? location : nil 36 | end 37 | 38 | # Returns the real ip address, though this could be the localhost ip 39 | # address. No special handling here anymore. 40 | def get_ip_address 41 | request.remote_ip 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/geokit-rails3/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'geokit-rails3' 2 | require 'rails' 3 | 4 | module Geokit 5 | 6 | class Railtie < ::Rails::Railtie 7 | 8 | config.geokit = ActiveSupport::OrderedOptions.new 9 | config.geokit.geocoders = ActiveSupport::OrderedOptions.new 10 | 11 | initializer 'geokit-rails3.insert_into_active_record' do 12 | ActiveSupport.on_load :active_record do 13 | ActiveRecord::Base.send(:include, Geokit::ActsAsMappable::Glue) 14 | Geokit::Geocoders.logger = ActiveRecord::Base.logger 15 | end 16 | end 17 | 18 | initializer 'geokit-rails3.insert_into_action_controller' do 19 | ActiveSupport.on_load :action_controller do 20 | ActionController::Base.send(:include, Geokit::GeocoderControl) 21 | ActionController::Base.send(:include, GeoKit::IpGeocodeLookup) 22 | end 23 | end 24 | 25 | config.after_initialize do |app| 26 | options = app.config.geokit 27 | geocoders_options = options.delete(:geocoders) 28 | 29 | options.each do |k,v| 30 | Geokit::send("#{k}=", v) 31 | end 32 | geocoders_options.each do |k,v| 33 | Geokit::Geocoders::send("#{k}=", v) 34 | end 35 | end 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /lib/geokit-rails3/version.rb: -------------------------------------------------------------------------------- 1 | module GeokitRails3 2 | VERSION = "0.1.5" 3 | end 4 | -------------------------------------------------------------------------------- /test/acts_as_mappable_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | Geokit::Geocoders::provider_order = [:google, :us] 4 | 5 | class ActsAsMappableTest < GeokitTestCase 6 | 7 | LOCATION_A_IP = "217.10.83.5" 8 | 9 | def setup 10 | @location_a = GeoKit::GeoLoc.new 11 | @location_a.lat = 32.918593 12 | @location_a.lng = -96.958444 13 | @location_a.city = "Irving" 14 | @location_a.state = "TX" 15 | @location_a.country_code = "US" 16 | @location_a.success = true 17 | 18 | @sw = GeoKit::LatLng.new(32.91663,-96.982841) 19 | @ne = GeoKit::LatLng.new(32.96302,-96.919495) 20 | @bounds_center=GeoKit::LatLng.new((@sw.lat+@ne.lat)/2,(@sw.lng+@ne.lng)/2) 21 | 22 | @starbucks = companies(:starbucks) 23 | @loc_a = locations(:a) 24 | @custom_loc_a = custom_locations(:a) 25 | @loc_e = locations(:e) 26 | @custom_loc_e = custom_locations(:e) 27 | 28 | @barnes_and_noble = mock_organizations(:barnes_and_noble) 29 | @address = mock_addresses(:address_barnes_and_noble) 30 | end 31 | 32 | def test_sort_by_distance_from 33 | locations = Location.all 34 | unsorted = [locations(:a), locations(:b), locations(:c), locations(:d), locations(:e), locations(:f)] 35 | sorted = [locations(:a), locations(:b), locations(:c), locations(:f), locations(:d), locations(:e)] 36 | assert_equal unsorted, locations 37 | assert_equal sorted, locations.sort_by{|l| l.distance_to(locations(:a))} 38 | assert_equal sorted, locations.sort_by_distance_from(locations(:a)) 39 | assert_equal sorted, locations # last action desctructive 40 | end 41 | 42 | def test_override_default_units_the_hard_way 43 | Location.default_units = :kms 44 | locations = Location.geo_scope(:origin => @loc_a).where("distance < 3.97") 45 | assert_equal 5, locations.all.size 46 | assert_equal 5, locations.count 47 | Location.default_units = :miles 48 | end 49 | 50 | def test_include 51 | locations = Location.geo_scope(:origin => @loc_a).includes(:company).where("company_id = 1").all 52 | assert !locations.empty? 53 | assert_equal 1, locations[0].company.id 54 | assert_equal 'Starbucks', locations[0].company.name 55 | end 56 | 57 | def test_distance_between_geocoded 58 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a) 59 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("San Francisco, CA").returns(@location_a) 60 | assert_equal 0, Location.distance_between("Irving, TX", "San Francisco, CA") 61 | end 62 | 63 | def test_distance_to_geocoded 64 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a) 65 | assert_equal 0, @custom_loc_a.distance_to("Irving, TX") 66 | end 67 | 68 | def test_distance_to_geocoded_error 69 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(GeoKit::GeoLoc.new) 70 | assert_raise(GeoKit::Geocoders::GeocodeError) { @custom_loc_a.distance_to("Irving, TX") } 71 | end 72 | 73 | def test_custom_attributes_distance_calculations 74 | assert_equal 0, @custom_loc_a.distance_to(@loc_a) 75 | assert_equal 0, CustomLocation.distance_between(@custom_loc_a, @loc_a) 76 | end 77 | 78 | def test_distance_column_in_select 79 | locations = Location.geo_scope(:origin => @loc_a).order("distance ASC") 80 | assert_equal 6, locations.all.size 81 | assert_equal 0, @loc_a.distance_to(locations.first) 82 | assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01 83 | end 84 | 85 | def test_find_with_distance_condition 86 | locations = Location.geo_scope(:origin => @loc_a, :within => 3.97) 87 | assert_equal 5, locations.all.size 88 | assert_equal 5, locations.count 89 | end 90 | 91 | def test_find_with_distance_condition_with_units_override 92 | locations = Location.geo_scope(:origin => @loc_a, :units => :kms, :within => 6.387) 93 | assert_equal 5, locations.all.size 94 | assert_equal 5, locations.count 95 | end 96 | 97 | def test_find_with_distance_condition_with_formula_override 98 | locations = Location.geo_scope(:origin => @loc_a, :formula => :flat, :within => 6.387) 99 | assert_equal 6, locations.all.size 100 | assert_equal 6, locations.count 101 | end 102 | 103 | def test_find_within 104 | locations = Location.within(3.97, :origin => @loc_a) 105 | assert_equal 5, locations.all.size 106 | assert_equal 5, locations.count 107 | end 108 | 109 | def test_find_within_with_coordinates 110 | locations = Location.within(3.97, :origin =>[@loc_a.lat,@loc_a.lng]) 111 | assert_equal 5, locations.all.size 112 | assert_equal 5, locations.count 113 | end 114 | 115 | def test_find_with_compound_condition 116 | locations = Location.geo_scope(:origin => @loc_a).where("distance < 5 and city = 'Coppell'") 117 | assert_equal 2, locations.all.size 118 | assert_equal 2, locations.count 119 | end 120 | 121 | def test_find_with_secure_compound_condition 122 | locations = Location.geo_scope(:origin => @loc_a).where(["distance < ? and city = ?", 5, 'Coppell']) 123 | assert_equal 2, locations.all.size 124 | assert_equal 2, locations.count 125 | end 126 | 127 | def test_find_beyond 128 | locations = Location.beyond(3.95, :origin => @loc_a) 129 | assert_equal 1, locations.all.size 130 | assert_equal 1, locations.count 131 | end 132 | 133 | def test_find_beyond_with_token 134 | # locations = Location.find(:all, :beyond => 3.95, :origin => @loc_a) 135 | locations = Location.geo_scope(:beyond => 3.95, :origin => @loc_a) 136 | assert_equal 1, locations.all.size 137 | assert_equal 1, locations.count 138 | end 139 | 140 | def test_find_beyond_with_coordinates 141 | locations = Location.beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng]) 142 | assert_equal 1, locations.all.size 143 | assert_equal 1, locations.count 144 | end 145 | 146 | def test_find_range_with_token 147 | locations = Location.geo_scope(:range => 0..10, :origin => @loc_a) 148 | assert_equal 6, locations.all.size 149 | assert_equal 6, locations.count 150 | end 151 | 152 | def test_find_range_with_token_with_conditions 153 | locations = Location.geo_scope(:origin => @loc_a, :range => 0..10).where(["city = ?", 'Coppell']) 154 | assert_equal 2, locations.all.size 155 | assert_equal 2, locations.count 156 | end 157 | 158 | def test_find_range_with_token_with_hash_conditions 159 | locations = Location.geo_scope(:origin => @loc_a, :range => 0..10).where(:city => 'Coppell') 160 | assert_equal 2, locations.all.size 161 | assert_equal 2, locations.count 162 | end 163 | 164 | def test_find_range_with_token_excluding_end 165 | locations = Location.geo_scope(:range => 0...10, :origin => @loc_a) 166 | assert_equal 6, locations.all.size 167 | assert_equal 6, locations.count 168 | end 169 | 170 | def test_find_nearest 171 | assert_equal @loc_a, Location.nearest(:origin => @loc_a).first 172 | end 173 | 174 | def test_find_nearest_with_coordinates 175 | assert_equal @loc_a, Location.nearest(:origin =>[@loc_a.lat, @loc_a.lng]).first 176 | end 177 | 178 | def test_find_farthest 179 | assert_equal @loc_e, Location.farthest(:origin => @loc_a).first 180 | end 181 | 182 | def test_find_farthest_with_coordinates 183 | assert_equal @loc_e, Location.farthest(:origin =>[@loc_a.lat, @loc_a.lng]).first 184 | end 185 | 186 | def test_scoped_distance_column_in_select 187 | locations = @starbucks.locations.geo_scope(:origin => @loc_a).order("distance ASC") 188 | assert_equal 5, locations.all.size 189 | assert_equal 0, @loc_a.distance_to(locations.first) 190 | assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01 191 | end 192 | 193 | def test_scoped_find_with_distance_condition 194 | locations = @starbucks.locations.geo_scope(:origin => @loc_a).where("distance < 3.97") 195 | assert_equal 4, locations.all.size 196 | assert_equal 4, locations.count 197 | end 198 | 199 | def test_scoped_find_within 200 | locations = @starbucks.locations.within(3.97, :origin => @loc_a) 201 | assert_equal 4, locations.all.size 202 | assert_equal 4, locations.count 203 | end 204 | 205 | def test_scoped_find_with_compound_condition 206 | locations = @starbucks.locations.geo_scope(:origin => @loc_a).where("distance < 5 and city = 'Coppell'") 207 | assert_equal 2, locations.all.size 208 | assert_equal 2, locations.count 209 | end 210 | 211 | def test_scoped_find_beyond 212 | locations = @starbucks.locations.beyond(3.95, :origin => @loc_a) 213 | assert_equal 1, locations.all.size 214 | assert_equal 1, locations.count 215 | end 216 | 217 | def test_scoped_find_nearest 218 | assert_equal @loc_a, @starbucks.locations.nearest(:origin => @loc_a).first 219 | end 220 | 221 | def test_scoped_find_farthest 222 | assert_equal @loc_e, @starbucks.locations.farthest(:origin => @loc_a).first 223 | end 224 | 225 | def test_ip_geocoded_distance_column_in_select 226 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 227 | locations = Location.geo_scope(:origin => LOCATION_A_IP).order("distance ASC") 228 | assert_equal 6, locations.all.size 229 | assert_equal 0, @loc_a.distance_to(locations.first) 230 | assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01 231 | end 232 | 233 | def test_ip_geocoded_find_with_distance_condition 234 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 235 | locations = Location.geo_scope(:origin => LOCATION_A_IP).where("distance < 3.97") 236 | assert_equal 5, locations.all.size 237 | assert_equal 5, locations.count 238 | end 239 | 240 | def test_ip_geocoded_find_within 241 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 242 | locations = Location.within(3.97, :origin => LOCATION_A_IP) 243 | assert_equal 5, locations.all.size 244 | assert_equal 5, locations.count 245 | end 246 | 247 | def test_ip_geocoded_find_with_compound_condition 248 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 249 | locations = Location.geo_scope(:origin => LOCATION_A_IP).where("distance < 5 and city = 'Coppell'") 250 | assert_equal 2, locations.all.size 251 | assert_equal 2, locations.count 252 | end 253 | 254 | def test_ip_geocoded_find_with_secure_compound_condition 255 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 256 | locations = Location.geo_scope(:origin => LOCATION_A_IP).where(["distance < ? and city = ?", 5, 'Coppell']) 257 | assert_equal 2, locations.all.size 258 | assert_equal 2, locations.count 259 | end 260 | 261 | def test_ip_geocoded_find_beyond 262 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 263 | locations = Location.beyond(3.95, :origin => LOCATION_A_IP) 264 | assert_equal 1, locations.all.size 265 | assert_equal 1, locations.count 266 | end 267 | 268 | def test_ip_geocoded_find_nearest 269 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 270 | assert_equal @loc_a, Location.nearest(:origin => LOCATION_A_IP).first 271 | end 272 | 273 | def test_ip_geocoded_find_farthest 274 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a) 275 | assert_equal @loc_e, Location.farthest(:origin => LOCATION_A_IP).first 276 | end 277 | 278 | def test_ip_geocoder_exception 279 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with('127.0.0.1').returns(GeoKit::GeoLoc.new) 280 | assert_raises GeoKit::Geocoders::GeocodeError do 281 | Location.farthest(:origin => '127.0.0.1').first 282 | end 283 | end 284 | 285 | def test_address_geocode 286 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with('Irving, TX').returns(@location_a) 287 | locations = Location.geo_scope(:origin => 'Irving, TX').where(["distance < ? and city = ?", 5, 'Coppell']) 288 | assert_equal 2, locations.all.size 289 | assert_equal 2, locations.count 290 | end 291 | 292 | def test_find_with_custom_distance_condition 293 | locations = CustomLocation.geo_scope(:origin => @loc_a).where("dist < 3.97") 294 | assert_equal 5, locations.all.size 295 | assert_equal 5, locations.count 296 | end 297 | 298 | def test_find_with_custom_distance_condition_using_custom_origin 299 | locations = CustomLocation.geo_scope(:origin => @custom_loc_a).where("dist < 3.97") 300 | assert_equal 5, locations.all.size 301 | assert_equal 5, locations.count 302 | end 303 | 304 | def test_find_within_with_custom 305 | locations = CustomLocation.within(3.97, :origin => @loc_a) 306 | assert_equal 5, locations.all.size 307 | assert_equal 5, locations.count 308 | end 309 | 310 | def test_find_within_with_coordinates_with_custom 311 | locations = CustomLocation.within(3.97, :origin =>[@loc_a.lat, @loc_a.lng]) 312 | assert_equal 5, locations.all.size 313 | assert_equal 5, locations.count 314 | end 315 | 316 | def test_find_with_compound_condition_with_custom 317 | locations = CustomLocation.geo_scope(:origin => @loc_a).where("dist < 5 and city = 'Coppell'") 318 | assert_equal 1, locations.all.size 319 | assert_equal 1, locations.count 320 | end 321 | 322 | def test_find_with_secure_compound_condition_with_custom 323 | locations = CustomLocation.geo_scope(:origin => @loc_a).where(["dist < ? and city = ?", 5, 'Coppell']) 324 | assert_equal 1, locations.all.size 325 | assert_equal 1, locations.count 326 | end 327 | 328 | def test_find_beyond_with_custom 329 | locations = CustomLocation.beyond(3.95, :origin => @loc_a) 330 | assert_equal 1, locations.all.size 331 | assert_equal 1, locations.count 332 | end 333 | 334 | def test_find_beyond_with_coordinates_with_custom 335 | locations = CustomLocation.beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng]) 336 | assert_equal 1, locations.all.size 337 | assert_equal 1, locations.count 338 | end 339 | 340 | def test_find_nearest_with_custom 341 | assert_equal @custom_loc_a, CustomLocation.nearest(:origin => @loc_a).first 342 | end 343 | 344 | def test_find_nearest_with_coordinates_with_custom 345 | assert_equal @custom_loc_a, CustomLocation.nearest(:origin =>[@loc_a.lat, @loc_a.lng]).first 346 | end 347 | 348 | def test_find_farthest_with_custom 349 | assert_equal @custom_loc_e, CustomLocation.farthest(:origin => @loc_a).first 350 | end 351 | 352 | def test_find_farthest_with_coordinates_with_custom 353 | assert_equal @custom_loc_e, CustomLocation.farthest(:origin =>[@loc_a.lat, @loc_a.lng]).first 354 | end 355 | 356 | def test_find_with_array_origin 357 | locations = Location.geo_scope(:origin =>[@loc_a.lat,@loc_a.lng]).where("distance < 3.97") 358 | assert_equal 5, locations.all.size 359 | assert_equal 5, locations.count 360 | end 361 | 362 | 363 | # Bounding box tests 364 | 365 | def test_find_within_bounds 366 | locations = Location.in_bounds([@sw,@ne]) 367 | assert_equal 2, locations.all.size 368 | assert_equal 2, locations.count 369 | end 370 | 371 | def test_find_within_bounds_ordered_by_distance 372 | locations = Location.in_bounds([@sw,@ne], :origin=>@bounds_center).order('distance asc') 373 | assert_equal locations[0], locations(:d) 374 | assert_equal locations[1], locations(:a) 375 | end 376 | 377 | def test_find_within_bounds_with_token 378 | locations = Location.geo_scope(:bounds=>[@sw,@ne]) 379 | assert_equal 2, locations.all.size 380 | assert_equal 2, locations.count 381 | end 382 | 383 | def test_find_within_bounds_with_string_conditions 384 | locations = Location.geo_scope(:bounds=>[@sw,@ne]).where("id !=#{locations(:a).id}") 385 | assert_equal 1, locations.all.size 386 | end 387 | 388 | def test_find_within_bounds_with_array_conditions 389 | locations = Location.geo_scope(:bounds=>[@sw,@ne]).where(["id != ?", locations(:a).id]) 390 | assert_equal 1, locations.all.size 391 | end 392 | 393 | def test_find_within_bounds_with_hash_conditions 394 | locations = Location.geo_scope(:bounds=>[@sw,@ne]).where({:id => locations(:a).id}) 395 | assert_equal 1, locations.all.size 396 | end 397 | 398 | def test_auto_geocode 399 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a) 400 | store=Store.new(:address=>'Irving, TX') 401 | store.save 402 | assert_equal store.lat,@location_a.lat 403 | assert_equal store.lng,@location_a.lng 404 | assert_equal 0, store.errors.size 405 | end 406 | 407 | def test_auto_geocode_failure 408 | GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("BOGUS").returns(GeoKit::GeoLoc.new) 409 | store=Store.new(:address=>'BOGUS') 410 | store.save 411 | assert store.new_record? 412 | assert_equal 1, store.errors.size 413 | end 414 | 415 | # Test :through 416 | 417 | def test_find_with_through 418 | organizations = MockOrganization.geo_scope(:origin => @location_a).order('distance ASC') 419 | assert_equal 2, organizations.all.size 420 | organizations = MockOrganization.geo_scope(:origin => @location_a).where("distance < 3.97") 421 | assert_equal 1, organizations.count 422 | end 423 | 424 | def test_find_with_through_with_hash 425 | people = MockPerson.geo_scope(:origin => @location_a).order('distance ASC') 426 | assert_equal 2, people.size 427 | assert_equal 2, people.count 428 | end 429 | end 430 | -------------------------------------------------------------------------------- /test/boot.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | require 'test/unit' 4 | require 'active_support/test_case' 5 | 6 | require 'active_record' 7 | require 'active_record/test_case' 8 | require 'active_record/fixtures' 9 | 10 | require 'action_controller' 11 | # require 'action_dispatch' 12 | # require 'action_dispatch/testing/test_process' 13 | 14 | pwd = Pathname.new(File.dirname(__FILE__)).expand_path 15 | 16 | PLUGIN_ROOT = pwd + '..' 17 | ADAPTER = ENV['DB'] || 'sqlite' 18 | 19 | $LOAD_PATH << (PLUGIN_ROOT + 'lib') 20 | $LOAD_PATH << (PLUGIN_ROOT + 'test/models') 21 | 22 | config_file = PLUGIN_ROOT + 'test/database.yml' 23 | db_config = YAML::load(IO.read(config_file)) 24 | logger_file = PLUGIN_ROOT + "test/#{ADAPTER}-debug.log" 25 | schema_file = PLUGIN_ROOT + 'test/schema.rb' 26 | 27 | ActiveRecord::Base.configurations = db_config 28 | ActiveRecord::Base.logger = Logger.new(logger_file) 29 | ActiveRecord::Base.establish_connection(db_config[ADAPTER]) 30 | 31 | ActiveRecord::Migration.verbose = false 32 | load schema_file -------------------------------------------------------------------------------- /test/database.yml: -------------------------------------------------------------------------------- 1 | base: &base 2 | host: localhost 3 | username: tests 4 | password: 5 | 6 | mysql: 7 | adapter: mysql 8 | database: geokit_rails_tests 9 | <<: *base 10 | 11 | postgresql: 12 | adapter: postgresql 13 | database: geokit_rails_tests 14 | <<: *base 15 | 16 | sqlserver: 17 | adapter: sqlserver 18 | mode: ODBC 19 | dsn: geokit_rails_tests 20 | username: tests 21 | 22 | sqlite: 23 | adapter: sqlite3 24 | database: test/test.sqlite3 25 | -------------------------------------------------------------------------------- /test/fixtures/companies.yml: -------------------------------------------------------------------------------- 1 | starbucks: 2 | id: 1 3 | name: Starbucks 4 | 5 | barnes_and_noble: 6 | id: 2 7 | name: Barnes & Noble -------------------------------------------------------------------------------- /test/fixtures/custom_locations.yml: -------------------------------------------------------------------------------- 1 | a: 2 | id: 1 3 | company_id: 1 4 | street: 7979 N MacArthur Blvd 5 | city: Irving 6 | state: TX 7 | postal_code: 75063 8 | latitude: 32.918593 9 | longitude: -96.958444 10 | b: 11 | id: 2 12 | company_id: 1 13 | street: 7750 N Macarthur Blvd # 160 14 | city: Irving 15 | state: TX 16 | postal_code: 75063 17 | latitude: 32.914144 18 | longitude: -96.958444 19 | c: 20 | id: 3 21 | company_id: 1 22 | street: 5904 N Macarthur Blvd # 160 23 | city: Irving 24 | state: TX 25 | postal_code: 75039 26 | latitude: 32.895155 27 | longitude: -96.958444 28 | d: 29 | id: 4 30 | company_id: 1 31 | street: 817 S Macarthur Blvd # 145 32 | city: Coppell 33 | state: TX 34 | postal_code: 75019 35 | latitude: 32.951613 36 | longitude: -96.958444 37 | e: 38 | id: 5 39 | company_id: 1 40 | street: 106 N Denton Tap Rd # 350 41 | city: Coppell 42 | state: TX 43 | postal_code: 75019 44 | latitude: 32.969527 45 | longitude: -96.990159 46 | f: 47 | id: 6 48 | company_id: 2 49 | street: 5904 N Macarthur Blvd # 160 50 | city: Irving 51 | state: TX 52 | postal_code: 75039 53 | latitude: 32.895155 54 | longitude: -96.958444 -------------------------------------------------------------------------------- /test/fixtures/locations.yml: -------------------------------------------------------------------------------- 1 | a: 2 | id: 1 3 | company_id: 1 4 | street: 7979 N MacArthur Blvd 5 | city: Irving 6 | state: TX 7 | postal_code: 75063 8 | lat: 32.918593 9 | lng: -96.958444 10 | b: 11 | id: 2 12 | company_id: 1 13 | street: 7750 N Macarthur Blvd # 160 14 | city: Irving 15 | state: TX 16 | postal_code: 75063 17 | lat: 32.914144 18 | lng: -96.958444 19 | c: 20 | id: 3 21 | company_id: 1 22 | street: 5904 N Macarthur Blvd # 160 23 | city: Irving 24 | state: TX 25 | postal_code: 75039 26 | lat: 32.895155 27 | lng: -96.958444 28 | d: 29 | id: 4 30 | company_id: 1 31 | street: 817 S Macarthur Blvd # 145 32 | city: Coppell 33 | state: TX 34 | postal_code: 75019 35 | lat: 32.951613 36 | lng: -96.958444 37 | e: 38 | id: 5 39 | company_id: 1 40 | street: 106 N Denton Tap Rd # 350 41 | city: Coppell 42 | state: TX 43 | postal_code: 75019 44 | lat: 32.969527 45 | lng: -96.990159 46 | f: 47 | id: 6 48 | company_id: 2 49 | street: 5904 N Macarthur Blvd # 160 50 | city: Irving 51 | state: TX 52 | postal_code: 75039 53 | lat: 32.895155 54 | lng: -96.958444 -------------------------------------------------------------------------------- /test/fixtures/mock_addresses.yml: -------------------------------------------------------------------------------- 1 | address_starbucks: 2 | addressable: starbucks (MockOrganization) 3 | street: 106 N Denton Tap Rd # 350 4 | city: Coppell 5 | state: TX 6 | postal_code: 75019 7 | lat: 32.969527 8 | lng: -96.990159 9 | 10 | address_barnes_and_noble: 11 | addressable: barnes_and_noble (MockOrganization) 12 | street: 5904 N Macarthur Blvd # 160 13 | city: Irving 14 | state: TX 15 | postal_code: 75039 16 | lat: 32.895155 17 | lng: -96.958444 -------------------------------------------------------------------------------- /test/fixtures/mock_families.yml: -------------------------------------------------------------------------------- 1 | my_family: 2 | mock_house: my_house 3 | -------------------------------------------------------------------------------- /test/fixtures/mock_houses.yml: -------------------------------------------------------------------------------- 1 | another_house: 2 | address: 106 N Denton Tap Rd # 350 3 | lat: 32.969527 4 | lng: -96.990159 5 | 6 | my_house: 7 | address: 5904 N Macarthur Blvd # 160 8 | lat: 32.895155 9 | lng: -96.958444 -------------------------------------------------------------------------------- /test/fixtures/mock_organizations.yml: -------------------------------------------------------------------------------- 1 | starbucks: 2 | name: Starbucks 3 | 4 | barnes_and_noble: 5 | name: Barnes & Noble -------------------------------------------------------------------------------- /test/fixtures/mock_people.yml: -------------------------------------------------------------------------------- 1 | father: 2 | mock_family: my_family 3 | 4 | mother: 5 | mock_family: my_family -------------------------------------------------------------------------------- /test/fixtures/stores.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geokit/geokit-rails3/93cc1febe79418055e8e5b3c18fc7dbf8b91f4be/test/fixtures/stores.yml -------------------------------------------------------------------------------- /test/ip_geocode_lookup_test.disabled.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module TestApp 4 | class Application < Rails::Application 5 | end 6 | end 7 | 8 | TestApp::Application.routes.draw do 9 | match ':controller(/:action(/:id(.:format)))' 10 | end 11 | 12 | class LocationAwareController < ActionController::Base #:nodoc: all 13 | geocode_ip_address 14 | 15 | def index 16 | render :nothing => true 17 | end 18 | 19 | def rescue_action(e) raise e end; 20 | end 21 | 22 | class ActionController::TestRequest #:nodoc: all 23 | attr_accessor :remote_ip 24 | end 25 | 26 | class IpGeocodeLookupTest < ActionController::TestCase 27 | tests LocationAwareController 28 | 29 | def setup 30 | @success = GeoKit::GeoLoc.new 31 | @success.provider = "hostip" 32 | @success.lat = 41.7696 33 | @success.lng = -88.4588 34 | @success.city = "Sugar Grove" 35 | @success.state = "IL" 36 | @success.country_code = "US" 37 | @success.success = true 38 | 39 | @failure = GeoKit::GeoLoc.new 40 | @failure.provider = "hostip" 41 | @failure.city = "(Private Address)" 42 | @failure.success = false 43 | end 44 | 45 | def test_no_location_in_cookie_or_session 46 | Geokit::Geocoders::MultiGeocoder.expects(:geocode).with("good ip").returns(@success) 47 | @request.remote_ip = "good ip" 48 | get :index 49 | verify 50 | end 51 | 52 | def test_location_in_cookie 53 | @request.remote_ip = "good ip" 54 | @request.cookies['geo_location'] = @success.to_yaml 55 | get :index 56 | verify 57 | end 58 | 59 | def test_location_in_session 60 | @request.remote_ip = "good ip" 61 | @request.session[:geo_location] = @success 62 | @request.cookies['geo_location'] = CGI::Cookie.new('geo_location', @success.to_yaml) 63 | get :index 64 | verify 65 | end 66 | 67 | def test_ip_not_located 68 | Geokit::Geocoders::MultiGeocoder.expects(:geocode).with("bad ip").returns(@failure) 69 | @request.remote_ip = "bad ip" 70 | get :index 71 | assert_nil @request.session[:geo_location] 72 | end 73 | 74 | private 75 | 76 | def verify 77 | assert_response :success 78 | assert_equal @success, @request.session[:geo_location] 79 | assert_not_nil cookies['geo_location'] 80 | assert_equal @success, YAML.load(cookies['geo_location']) 81 | end 82 | end -------------------------------------------------------------------------------- /test/models/company.rb: -------------------------------------------------------------------------------- 1 | class Company < ActiveRecord::Base 2 | has_many :locations 3 | end -------------------------------------------------------------------------------- /test/models/custom_location.rb: -------------------------------------------------------------------------------- 1 | class CustomLocation < ActiveRecord::Base 2 | belongs_to :company 3 | acts_as_mappable :distance_column_name => 'dist', 4 | :default_units => :kms, 5 | :default_formula => :flat, 6 | :lat_column_name => 'latitude', 7 | :lng_column_name => 'longitude' 8 | 9 | def to_s 10 | "lat: #{latitude} lng: #{longitude} dist: #{dist}" 11 | end 12 | end -------------------------------------------------------------------------------- /test/models/location.rb: -------------------------------------------------------------------------------- 1 | class Location < ActiveRecord::Base 2 | belongs_to :company 3 | acts_as_mappable 4 | end -------------------------------------------------------------------------------- /test/models/mock_address.rb: -------------------------------------------------------------------------------- 1 | class MockAddress < ActiveRecord::Base 2 | belongs_to :addressable, :polymorphic => true 3 | acts_as_mappable 4 | end -------------------------------------------------------------------------------- /test/models/mock_family.rb: -------------------------------------------------------------------------------- 1 | require 'models/mock_house' 2 | 3 | class MockFamily < ActiveRecord::Base 4 | belongs_to :mock_house 5 | end -------------------------------------------------------------------------------- /test/models/mock_house.rb: -------------------------------------------------------------------------------- 1 | class MockHouse < ActiveRecord::Base 2 | acts_as_mappable 3 | end -------------------------------------------------------------------------------- /test/models/mock_organization.rb: -------------------------------------------------------------------------------- 1 | require 'models/mock_address' 2 | 3 | class MockOrganization < ActiveRecord::Base 4 | has_one :mock_address, :as => :addressable 5 | acts_as_mappable :through => :mock_address 6 | end -------------------------------------------------------------------------------- /test/models/mock_person.rb: -------------------------------------------------------------------------------- 1 | require 'models/mock_family' 2 | require 'models/mock_house' 3 | 4 | class MockPerson < ActiveRecord::Base 5 | belongs_to :mock_family 6 | acts_as_mappable :through => { :mock_family => :mock_house } 7 | end -------------------------------------------------------------------------------- /test/models/store.rb: -------------------------------------------------------------------------------- 1 | class Store < ActiveRecord::Base 2 | acts_as_mappable :auto_geocode => true 3 | end -------------------------------------------------------------------------------- /test/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define(:version => 0) do 2 | create_table :companies, :force => true do |t| 3 | t.column :name, :string 4 | end 5 | 6 | create_table :locations, :force => true do |t| 7 | t.column :company_id, :integer, :default => 0, :null => false 8 | t.column :street, :string, :limit => 60 9 | t.column :city, :string, :limit => 60 10 | t.column :state, :string, :limit => 2 11 | t.column :postal_code, :string, :limit => 16 12 | t.column :lat, :decimal, :precision => 15, :scale => 10 13 | t.column :lng, :decimal, :precision => 15, :scale => 10 14 | end 15 | 16 | create_table :custom_locations, :force => true do |t| 17 | t.column :company_id, :integer, :default => 0, :null => false 18 | t.column :street, :string, :limit => 60 19 | t.column :city, :string, :limit => 60 20 | t.column :state, :string, :limit => 2 21 | t.column :postal_code, :string, :limit => 16 22 | t.column :latitude, :decimal, :precision => 15, :scale => 10 23 | t.column :longitude, :decimal, :precision => 15, :scale => 10 24 | end 25 | 26 | create_table :stores, :force=> true do |t| 27 | t.column :address, :string 28 | t.column :lat, :decimal, :precision => 15, :scale => 10 29 | t.column :lng, :decimal, :precision => 15, :scale => 10 30 | end 31 | 32 | create_table :mock_organizations, :force => true do |t| 33 | t.column :name, :string 34 | end 35 | 36 | create_table :mock_addresses, :force => true do |t| 37 | t.column :addressable_id, :integer, :null => false 38 | t.column :addressable_type, :string, :null => false 39 | t.column :street, :string, :limit => 60 40 | t.column :city, :string, :limit => 60 41 | t.column :state, :string, :limit => 2 42 | t.column :postal_code, :string, :limit => 16 43 | t.column :lat, :decimal, :precision => 15, :scale => 10 44 | t.column :lng, :decimal, :precision => 15, :scale => 10 45 | end 46 | 47 | create_table :mock_houses, :force=> true do |t| 48 | t.column :address, :string 49 | t.column :lat, :decimal, :precision => 15, :scale => 10 50 | t.column :lng, :decimal, :precision => 15, :scale => 10 51 | end 52 | 53 | create_table :mock_families, :force => true do |t| 54 | t.belongs_to :mock_house 55 | end 56 | 57 | create_table :mock_people, :force => true do |t| 58 | t.belongs_to :mock_family 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/tasks.rake: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | class EnvTestTask < Rake::TestTask 4 | attr_accessor :env 5 | 6 | def ruby(*args) 7 | env.each { |key, value| ENV[key] = value } if env 8 | super 9 | env.keys.each { |key| ENV.delete(key) } if env 10 | end 11 | 12 | end 13 | 14 | desc 'Test the GeoKit plugin.' 15 | Rake::TestTask.new(:test) do |test| 16 | test.libs << 'lib' << 'test' 17 | test.pattern = 'test/*_test.rb' 18 | test.verbose = true 19 | end 20 | 21 | %w(mysql postgresql sqlserver sqlite).each do |configuration| 22 | EnvTestTask.new("test_#{configuration}") do |t| 23 | t.pattern = 'test/*_test.rb' 24 | t.verbose = true 25 | t.env = { 'DB' => configuration } 26 | t.libs << 'test' 27 | end 28 | end 29 | 30 | desc 'Test available databases.' 31 | task :test_databases => %w(test_mysql test_postgresql test_sqlserver test_sqlite) 32 | 33 | desc "Generate SimpleCov test coverage and open in your browser" 34 | task :coverage do 35 | ENV['COVERAGE'] = 'true' 36 | Rake::Task['test'].invoke 37 | end 38 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | require 'boot' 4 | require 'mocha/setup' 5 | 6 | if ENV['COVERAGE'] 7 | COVERAGE_THRESHOLD = 49 8 | require 'simplecov' 9 | require 'simplecov-rcov' 10 | SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter 11 | SimpleCov.start do 12 | add_filter '/test/' 13 | add_group 'lib', 'lib' 14 | end 15 | SimpleCov.at_exit do 16 | SimpleCov.result.format! 17 | percent = SimpleCov.result.covered_percent 18 | unless percent >= COVERAGE_THRESHOLD 19 | puts "Coverage must be above #{COVERAGE_THRESHOLD}%. It is #{"%.2f" % percent}%" 20 | Kernel.exit(1) 21 | end 22 | end 23 | end 24 | 25 | require 'geokit' 26 | require 'geokit-rails3' 27 | 28 | ActiveRecord::Base.send(:include, Geokit::ActsAsMappable::Glue) 29 | ActionController::Base.send(:include, Geokit::GeocoderControl) 30 | ActionController::Base.send(:include, GeoKit::IpGeocodeLookup) 31 | 32 | class GeokitTestCase < ActiveSupport::TestCase 33 | begin 34 | include ActiveRecord::TestFixtures 35 | rescue NameError 36 | puts "You appear to be using a pre-2.3 version of Rails. No need to include ActiveRecord::TestFixtures." 37 | end 38 | 39 | self.fixture_path = (PLUGIN_ROOT + 'test/fixtures').to_s 40 | self.use_transactional_fixtures = true 41 | self.use_instantiated_fixtures = false 42 | 43 | fixtures :all 44 | end --------------------------------------------------------------------------------