Skip to content
Browse files

fixed and added some stuff

  • Loading branch information...
1 parent 41960e3 commit 34856af2b02e43e2c3c99ec265002c0f7a9565d4 @peppyheppy peppyheppy committed Jan 26, 2010
Showing with 5,984 additions and 0 deletions.
  1. +4 −0 Capfile
  2. +50 −0 config/database.yml
  3. +21 −0 config/deploy.rb
  4. +61 −0 config/initializers/geokit_config.rb
  5. +9 −0 config/mongrel_cluster.yml
  6. 0 vendor/plugins/acts_as_tree/test/database.yml
  7. +46 −0 vendor/plugins/geokit-rails/CHANGELOG.rdoc
  8. +20 −0 vendor/plugins/geokit-rails/MIT-LICENSE
  9. +561 −0 vendor/plugins/geokit-rails/README.markdown
  10. +18 −0 vendor/plugins/geokit-rails/Rakefile
  11. +9 −0 vendor/plugins/geokit-rails/about.yml
  12. +61 −0 vendor/plugins/geokit-rails/assets/api_keys_template
  13. +1 −0 vendor/plugins/geokit-rails/init.rb
  14. +14 −0 vendor/plugins/geokit-rails/install.rb
  15. +26 −0 vendor/plugins/geokit-rails/lib/geokit-rails.rb
  16. +456 −0 vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb
  17. +31 −0 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/abstract.rb
  18. +22 −0 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/mysql.rb
  19. +22 −0 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/postgresql.rb
  20. +43 −0 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb
  21. +22 −0 vendor/plugins/geokit-rails/lib/geokit-rails/defaults.rb
  22. +16 −0 vendor/plugins/geokit-rails/lib/geokit-rails/geocoder_control.rb
  23. +46 −0 vendor/plugins/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb
  24. +474 −0 vendor/plugins/geokit-rails/test/acts_as_mappable_test.rb
  25. +25 −0 vendor/plugins/geokit-rails/test/boot.rb
  26. +20 −0 vendor/plugins/geokit-rails/test/database.yml
  27. +7 −0 vendor/plugins/geokit-rails/test/fixtures/companies.yml
  28. +54 −0 vendor/plugins/geokit-rails/test/fixtures/custom_locations.yml
  29. +54 −0 vendor/plugins/geokit-rails/test/fixtures/locations.yml
  30. +17 −0 vendor/plugins/geokit-rails/test/fixtures/mock_addresses.yml
  31. +2 −0 vendor/plugins/geokit-rails/test/fixtures/mock_families.yml
  32. +9 −0 vendor/plugins/geokit-rails/test/fixtures/mock_houses.yml
  33. +5 −0 vendor/plugins/geokit-rails/test/fixtures/mock_organizations.yml
  34. +5 −0 vendor/plugins/geokit-rails/test/fixtures/mock_people.yml
  35. 0 vendor/plugins/geokit-rails/test/fixtures/stores.yml
  36. +77 −0 vendor/plugins/geokit-rails/test/ip_geocode_lookup_test.rb
  37. +3 −0 vendor/plugins/geokit-rails/test/models/company.rb
  38. +12 −0 vendor/plugins/geokit-rails/test/models/custom_location.rb
  39. +4 −0 vendor/plugins/geokit-rails/test/models/location.rb
  40. +4 −0 vendor/plugins/geokit-rails/test/models/mock_address.rb
  41. +3 −0 vendor/plugins/geokit-rails/test/models/mock_family.rb
  42. +3 −0 vendor/plugins/geokit-rails/test/models/mock_house.rb
  43. +4 −0 vendor/plugins/geokit-rails/test/models/mock_organization.rb
  44. +4 −0 vendor/plugins/geokit-rails/test/models/mock_person.rb
  45. +3 −0 vendor/plugins/geokit-rails/test/models/store.rb
  46. +60 −0 vendor/plugins/geokit-rails/test/schema.rb
  47. +31 −0 vendor/plugins/geokit-rails/test/tasks.rake
  48. +23 −0 vendor/plugins/geokit-rails/test/test_helper.rb
  49. +43 −0 vendor/plugins/will_paginate/.manifest
  50. +110 −0 vendor/plugins/will_paginate/CHANGELOG.rdoc
  51. +18 −0 vendor/plugins/will_paginate/LICENSE
  52. +107 −0 vendor/plugins/will_paginate/README.rdoc
  53. +53 −0 vendor/plugins/will_paginate/Rakefile
  54. BIN vendor/plugins/will_paginate/examples/apple-circle.gif
  55. +69 −0 vendor/plugins/will_paginate/examples/index.haml
  56. +92 −0 vendor/plugins/will_paginate/examples/index.html
  57. +90 −0 vendor/plugins/will_paginate/examples/pagination.css
  58. +91 −0 vendor/plugins/will_paginate/examples/pagination.sass
  59. +1 −0 vendor/plugins/will_paginate/init.rb
  60. +90 −0 vendor/plugins/will_paginate/lib/will_paginate.rb
  61. +16 −0 vendor/plugins/will_paginate/lib/will_paginate/array.rb
  62. +146 −0 vendor/plugins/will_paginate/lib/will_paginate/collection.rb
  63. +43 −0 vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb
  64. +264 −0 vendor/plugins/will_paginate/lib/will_paginate/finder.rb
  65. +170 −0 vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb
  66. +37 −0 vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb
  67. +9 −0 vendor/plugins/will_paginate/lib/will_paginate/version.rb
  68. +404 −0 vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb
  69. +21 −0 vendor/plugins/will_paginate/test/boot.rb
  70. +143 −0 vendor/plugins/will_paginate/test/collection_test.rb
  71. +8 −0 vendor/plugins/will_paginate/test/console
  72. +22 −0 vendor/plugins/will_paginate/test/database.yml
  73. +473 −0 vendor/plugins/will_paginate/test/finder_test.rb
  74. +3 −0 vendor/plugins/will_paginate/test/fixtures/admin.rb
  75. +14 −0 vendor/plugins/will_paginate/test/fixtures/developer.rb
  76. +13 −0 vendor/plugins/will_paginate/test/fixtures/developers_projects.yml
  77. +15 −0 vendor/plugins/will_paginate/test/fixtures/project.rb
  78. +6 −0 vendor/plugins/will_paginate/test/fixtures/projects.yml
  79. +29 −0 vendor/plugins/will_paginate/test/fixtures/replies.yml
  80. +7 −0 vendor/plugins/will_paginate/test/fixtures/reply.rb
  81. +38 −0 vendor/plugins/will_paginate/test/fixtures/schema.rb
  82. +10 −0 vendor/plugins/will_paginate/test/fixtures/topic.rb
  83. +30 −0 vendor/plugins/will_paginate/test/fixtures/topics.yml
  84. +2 −0 vendor/plugins/will_paginate/test/fixtures/user.rb
  85. +35 −0 vendor/plugins/will_paginate/test/fixtures/users.yml
  86. +40 −0 vendor/plugins/will_paginate/test/helper.rb
  87. +43 −0 vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb
  88. +75 −0 vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb
  89. +11 −0 vendor/plugins/will_paginate/test/lib/load_fixtures.rb
  90. +179 −0 vendor/plugins/will_paginate/test/lib/view_test_process.rb
  91. +59 −0 vendor/plugins/will_paginate/test/tasks.rake
  92. +373 −0 vendor/plugins/will_paginate/test/view_test.rb
  93. +20 −0 vendor/plugins/will_paginate/will_paginate.gemspec
View
4 Capfile
@@ -0,0 +1,4 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
+
+load 'config/deploy' # remove this line to skip loading any of the default tasks
View
50 config/database.yml
@@ -0,0 +1,50 @@
+# PostgreSQL. Versions 7.4 and 8.x are supported.
+#
+# Install the ruby-postgres driver:
+# gem install ruby-postgres
+# On Mac OS X:
+# gem install ruby-postgres -- --include=/usr/local/pgsql
+# On Windows:
+# gem install ruby-postgres
+# Choose the win32 build.
+# Install PostgreSQL and put its /bin directory on your path.
+development:
+ adapter: mysql
+ encoding: utf8
+ database: api_example_development
+ pool: 5
+ username: root
+ password:
+
+ # Connect on a TCP socket. Omitted by default since the client uses a
+ # domain socket that doesn't need configuration. Windows does not have
+ # domain sockets, so uncomment these lines.
+ #host: localhost
+ #port: 5432
+
+ # Schema search path. The server defaults to $user,public
+ #schema_search_path: myapp,sharedapp,public
+
+ # Minimum log levels, in increasing order:
+ # debug5, debug4, debug3, debug2, debug1,
+ # log, notice, warning, error, fatal, and panic
+ # The server defaults to notice.
+ #min_messages: warning
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: mysql
+ encoding: utf8
+ database: api_example_test
+ pool: 5
+ username: root
+ password:
+
+production:
+ adapter: postgresql
+ database: api_example_production
+ host: 127.0.0.1
+ username: postgres
+ password:
View
21 config/deploy.rb
@@ -0,0 +1,21 @@
+require 'palmtree/recipes/mongrel_cluster'
+set :use_sudo, false
+set :application, 'example_api'
+set :scm, 'git'
+set :repository, 'ssh://git@git.sc.realtravel.com/git/example_api.git'
+ssh_options[:port] = 65522
+ssh_options[:username] = "capi"
+default_run_options[:pty] = true
+ssh_options[:forward_agent] = true
+#set :deploy_via, :remote_cache
+set :branch, "master"
+set :scm_password, "none"
+set(:mongrel_conf) { "#{current_path}/config/mongrel_cluster.yml" }
+
+puts "*** Deploying to the \033[6;41m PRODUCTION \033[0m servers!"
+set :deploy_to, "/home/web/#{application}"
+role :app, "67.207.136.186"
+role :web, "67.207.136.186"
+role :db, "67.207.136.186", :primary => true
+
+
View
61 config/initializers/geokit_config.rb
@@ -0,0 +1,61 @@
+if defined? Geokit
+
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
+ Geokit::default_units = :miles
+ Geokit::default_formula = :sphere
+
+ # 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.
+ Geokit::Geocoders::request_timeout = 3
+
+ # These settings are used if web service calls must be routed through a proxy.
+ # 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.
+ Geokit::Geocoders::proxy_addr = nil
+ Geokit::Geocoders::proxy_port = nil
+ Geokit::Geocoders::proxy_user = nil
+ Geokit::Geocoders::proxy_pass = nil
+
+ # This is your yahoo application key for the Yahoo Geocoder.
+ # See http://developer.yahoo.com/faq/index.html#appid
+ # and http://developer.yahoo.com/maps/rest/V1/geocode.html
+ Geokit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
+
+ # This is your Google Maps geocoder key.
+ # See http://www.google.com/apis/maps/signup.html
+ # and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
+ Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
+
+ # This is your username and password for geocoder.us.
+ # To use the free service, the value can be set to nil or false. For
+ # usage tied to an account, the value should be set to username:password.
+ # See http://geocoder.us
+ # and http://geocoder.us/user/signup
+ Geokit::Geocoders::geocoder_us = false
+
+ # This is your authorization key for geocoder.ca.
+ # 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
+ # Geocoder.ca.
+ # See http://geocoder.ca
+ # and http://geocoder.ca/?register=1
+ Geokit::Geocoders::geocoder_ca = false
+
+ # Uncomment to use a username with the Geonames geocoder
+ #Geokit::Geocoders::geonames="REPLACE_WITH_YOUR_GEONAMES_USERNAME"
+
+ # This is the order in which the geocoders are called in a failover scenario
+ # If you only want to use a single geocoder, put a single symbol in the array.
+ # Valid symbols are :google, :yahoo, :us, and :ca.
+ # 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.
+ Geokit::Geocoders::provider_order = [:google,:us]
+
+ # The IP provider order. Valid symbols are :ip,:geo_plugin.
+ # As before, make sure you read up on relevant Terms of Use for each
+ # Geokit::Geocoders::ip_provider_order = [:geo_plugin,:ip]
+
+end
View
9 config/mongrel_cluster.yml
@@ -0,0 +1,9 @@
+---
+cwd: /home/web/example_api/current
+port: "8000"
+environment: production
+address: 127.0.0.1
+pid_file: log/mongrel.pid
+servers: 1
+user: capi
+group: capi
View
0 vendor/plugins/acts_as_tree/test/database.yml
No changes.
View
46 vendor/plugins/geokit-rails/CHANGELOG.rdoc
@@ -0,0 +1,46 @@
+== 2009-10-02 / Version 1.2.0
+* Overhaul the test suite to be independent of a Rails project
+* Added concept of database adapter. Ported mysql/postgresql conditional code to their own adapter.
+* Added SQL Server support. THANKS http://github.com/brennandunn for all the improvements in this release
+
+== 2009-09-26 / Version 1.1.3
+* documentation updates and updated to work with Geokit gem v1.5.0
+* IMPORTANT: in the Geokit gem, Geokit::Geocoders::timeout became Geokit::Geocoders::request_timeout for jruby compatibility.
+The plugin sets this in config/initializers/geokit_config.rb. So if you've upgraded the gem to 1.5.0, you need to
+make the change manually from Geokit::Geocoders::timeout to Geokit::Geocoders::request_timeout in config/initializers/geokit_config.rb
+
+== 2009-06-08 / Version 1.1.2
+* Added support for hashes in :through. So you can do: acts_as_mappable :through => { :state => :country } (Thanks José Valim).
+
+== 2009-05-22 / Version 1.1.1
+* Support for multiple ip geocoders (Thanks dreamcat4)
+* Now checks if either :origin OR :bounds is passed, and proceeds with geokit query if this is true (Thanks Glenn Powell)
+* Raises a helpful error if someone uses through but the association does not exists or was not defined yet (Thanks José Valim)
+
+== 2009-04-11 / Version 1.1.0
+* Fixed :through usages so that the through model is only included in the query if there
+ is an :origin passed in (Only if it is a geokit search) (Thanks Glenn Powell)
+* Move library initialisation into lib/geokit-rails. init.rb uses lib/geokit-rails now (thanks Alban Peignier)
+* Handle the case where a user passes a hash to the :conditions Finder option (thanks Adam Greene)
+* Added ability to specify domain-specific API keys (Thanks Glenn Powell)
+
+== 2009-02-20
+* More powerful assosciations in the Rails Plugin:You can now specify a model as mappable "through" an associated model.
+In other words, that associated model is the actual mappable model with "lat" and "lng" attributes, but this "through" model
+can still utilize all Geokit's "find by distance" finders. Also Rails 2.3 compatibility (thanks github/glennpow)
+
+== 2008-12-18
+* Split Rails plugin from geocoder gem
+* updated for Rails 2.2.2
+
+== 2008-08-20
+* 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+
+
+== 2008-01-16
+* fixed the "zero-distance" bug (calculating between two points that are the same)
+
+== 2007-11-12
+* fixed a small but with queries crossing meridian, and also fixed find(:closest)
+
+== 2007-10-11
+* Fixed Rails2/Edge compatability
View
20 vendor/plugins/geokit-rails/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 Bill Eisenhauer & Andre Lewis
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
561 vendor/plugins/geokit-rails/README.markdown
@@ -0,0 +1,561 @@
+## INSTALLATION
+
+Geokit consists of a Gem ([geokit-gem](http://github.com/andre/geokit-gem/tree/master)) and a Rails plugin ([geokit-rails](http://github.com/andre/geokit-rails/tree/master)).
+
+#### 1. Install the Rails plugin:
+
+ cd [YOUR_RAILS_APP_ROOT]
+ script/plugin install git://github.com/andre/geokit-rails.git
+
+#### 2. Add this line to your environment.rb
+(inside the Rails::Initializer.run do |config| block)
+
+ config.gem "geokit"
+
+This informs Rails of the gem dependency.
+
+#### 3. Tell Rails to install the gem:
+
+ rake gems:install
+
+And you're good to go! If you're running an older verion of Rails, just install the gem manually: `sudo gem install rails`
+
+## FEATURE SUMMARY
+
+Geokit provides key functionality for location-oriented Rails applications:
+
+- Distance calculations, for both flat and spherical environments. For example,
+ given the location of two points on the earth, you can calculate the miles/KM
+ between them.
+- ActiveRecord distance-based finders. For example, you can find all the points
+ in your database within a 50-mile radius.
+- IP-based location lookup utilizing hostip.info. Provide an IP address, and get
+ city name and latitude/longitude in return
+- A before_filter helper to geocoder the user's location based on IP address,
+ and retain the location in a cookie.
+- Geocoding from multiple providers. It provides a fail-over mechanism, in case
+ your input fails to geocode in one service. Geocoding is provided buy the Geokit
+ gem, which you must have installed
+
+The goal of this plugin is to provide the common functionality for location-oriented
+applications (geocoding, location lookup, distance calculation) in an easy-to-use
+package.
+
+## A NOTE ON TERMINOLOGY
+
+Throughout the code and API, latitude and longitude are referred to as lat
+and lng. We've found over the long term the abbreviation saves lots of typing time.
+
+## LOCATION QUERIES
+
+To get started, just specify an ActiveRecord class as `acts_as_mappale`:
+
+ class Location < ActiveRecord::Base
+ acts_as_mappable
+ end
+
+There are some defaults you can override:
+
+ class Location < ActiveRecord::Base
+ acts_as_mappable :default_units => :miles,
+ :default_formula => :sphere,
+ :distance_field_name => :distance,
+ :lat_column_name => :lat,
+ :lng_column_name => :lng
+ end
+
+
+The optional parameters are :units, :formula, and distance_field_name.
+Values for :units can be :miles, :kms (kilometers), or :nms (nautical miles),
+with :miles as the default. Values for :formula can be :sphere or :flat with
+:sphere as the default. :sphere gives you Haversine calculations, while :flat
+gives the Pythagoreum Theory. These defaults persist through out the plug-in.
+
+The plug-in creates a calculated `distance` field on AR instances that have
+been retrieved throw a Geokit location query. By default, these fields are
+known as "distance" but this can be changed through the `:distance_field_name` key.
+
+You can also define alternative column names for latitude and longitude using
+the `:lat_column_name` and `:lng_column_name` keys. The defaults are 'lat' and
+'lng' respectively.
+
+Once you've specified acts_as_mappable, a set of distance-based
+finder methods are available:
+
+Origin as a two-element array of latititude/longitude:
+
+ find(:all, :origin => [37.792,-122.393])
+
+Origin as a geocodeable string:
+
+ find(:all, :origin => '100 Spear st, San Francisco, CA')
+
+Origin as an object which responds to lat and lng methods,
+or latitude and longitude methods, or whatever methods you have
+specified for `lng_column_name` and `lat_column_name`:
+
+ find(:all, :origin=>my_store) # my_store.lat and my_store.lng methods exist
+
+Often you will need to find within a certain distance. The prefered syntax is:
+
+ find(:all, :origin => @somewhere, :within => 5)
+
+. . . however these syntaxes will also work:
+
+ find_within(5, :origin => @somewhere)
+ find(:all, :origin => @somewhere, :conditions => "distance < 5")
+
+Note however that the third form should be avoided. With either of the first two,
+Geokit automatically adds a bounding box to speed up the radial query in the database.
+With the third form, it does not.
+
+If you need to combine distance conditions with other conditions, you should do
+so like this:
+
+ find(:all, :origin => @somewhere, :within => 5, :conditions=>['state=?',state])
+
+If :origin is not provided in the finder call, the find method
+works as normal. Further, the key is removed
+from the :options hash prior to invoking the superclass behavior.
+
+Other convenience methods work intuitively and are as follows:
+
+ find_within(distance, :origin => @somewhere)
+ find_beyond(distance, :origin => @somewhere)
+ find_closest(:origin => @somewhere)
+ find_farthest(:origin => @somewhere)
+
+where the options respect the defaults, but can be overridden if
+desired.
+
+Lastly, if all that is desired is the raw SQL for distance
+calculations, you can use the following:
+
+ distance_sql(origin, units=default_units, formula=default_formula)
+
+Thereafter, you are free to use it in find_by_sql as you wish.
+
+There are methods available to enable you to get the count based upon
+the find condition that you have provided. These all work similarly to
+the finders. So for instance:
+
+ count(:origin, :conditions => "distance < 5")
+ count_within(distance, :origin => @somewhere)
+ count_beyond(distance, :origin => @somewhere)
+
+## FINDING WITHIN A BOUNDING BOX
+
+If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
+
+ Store.find :all, :bounds=>[sw_point,ne_point]
+
+The input to :bounds can be 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.
+
+If you need to calculate the bounding box from a point and radius, you can do that:
+
+ bounds=Bounds.from_point_and_radius(home,5)
+ Store.find :all, :bounds=>bounds
+
+## USING INCLUDES
+
+You can use includes along with your distance finders:
+
+ stores=Store.find :all, :origin=>home, :include=>[:reviews,:cities] :within=>5, :order=>'distance'
+
+*However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
+use the distance column, you'll have to re-calculate it post-query in Ruby:
+
+ stores.sort_by_distance_from(home)
+
+In this case, you may want to just use the bounding box
+condition alone in your SQL (there's no use calculating the distance twice):
+
+ bounds=Bounds.from_point_and_radius(home,5)
+ stores=Store.find :all, :include=>[:reviews,:cities] :bounds=>bounds
+ stores.sort_by_distance_from(home)
+
+## USING :through
+
+You can also specify a model as mappable "through" another associated model.
+In other words, that associated model is the actual mappable model with
+"lat" and "lng" attributes, but this "through" model can still utilize
+all of the above find methods to search for records.
+
+ class Location < ActiveRecord::Base
+ belongs_to :locatable, :polymorphic => true
+ acts_as_mappable
+ end
+
+ class Company < ActiveRecord::Base
+ has_one :location, :as => :locatable # also works for belongs_to associations
+ acts_as_mappable :through => :location
+ end
+
+Then you can still call:
+
+ Company.find_within(distance, :origin => @somewhere)
+
+You can also give :through a hash if you location is nested deep. For example, given:
+
+ class House
+ acts_as_mappable
+ end
+
+ class Family
+ belongs_to :house
+ end
+
+ class Person
+ belongs_to :family
+ acts_as_mappable :through => { :family => :house }
+ end
+
+Remember that the notes above about USING INCLUDES apply to the results from
+this find, since an include is automatically used.
+
+## IP GEOCODING
+
+You can obtain the location for an IP at any time using the geocoder
+as in the following example:
+
+ location = IpGeocoder.geocode('12.215.42.19')
+
+where Location is a GeoLoc instance containing the latitude,
+longitude, city, state, and country code. Also, the success
+value is true.
+
+If the IP cannot be geocoded, a GeoLoc instance is returned with a
+success value of false.
+
+It should be noted that the IP address needs to be visible to the
+Rails application. In other words, you need to ensure that the
+requesting IP address is forwarded by any front-end servers that
+are out in front of the Rails app. Otherwise, the IP will always
+be that of the front-end server.
+
+The Multi-Geocoder will also geocode IP addresses and provide
+failover among multiple IP geocoders. Just pass in an IP address for the
+parameter instead of a street address. Eg:
+
+ location = Geocoders::MultiGeocoder.geocode('12.215.42.19')
+
+The MultiGeocoder class requires 2 configuration setting for the provider order.
+Ordering is done through `Geokit::Geocoders::provider_order` and
+`Geokit::Geocoders::ip_provider_order`, found in
+`config/initializers/geokit_config.rb`. If you don't already have a
+`geokit_config.rb` file, the plugin creates one when it is first installed.
+
+
+## IP GEOCODING HELPER
+
+A class method called geocode_ip_address has been mixed into the
+ActionController::Base. This enables before_filter style lookup of
+the IP address. Since it is a filter, it can accept any of the
+available filter options.
+
+Usage is as below:
+
+ class LocationAwareController < ActionController::Base
+ geocode_ip_address
+ end
+
+A first-time lookup will result in the GeoLoc class being stored
+in the session as `:geo_location` as well as in a cookie called
+`:geo_session`. Subsequent lookups will use the session value if it
+exists or the cookie value if it doesn't exist. The last resort is
+to make a call to the web service. Clients are free to manage the
+cookie as they wish.
+
+The intent of this feature is to be able to provide a good guess as
+to a new visitor's location.
+
+## INTEGRATED FIND AND GEOCODING
+
+Geocoding has been integrated with the finders enabling you to pass
+a physical address or an IP address. This would look the following:
+
+ Location.find_farthest(:origin => '217.15.10.9')
+ Location.find_farthest(:origin => 'Irving, TX')
+
+where the IP or physical address would be geocoded to a location and
+then the resulting latitude and longitude coordinates would be used
+in the find. This is not expected to be common usage, but it can be
+done nevertheless.
+
+## ADDRESS GEOCODING
+
+Geocoding is provided by the Geokit gem, which is required for this plugin.
+See the top of this file for instructions on installing the Geokit gem.
+
+Geokit can geocode addresses using multiple geocodeing web services.
+Geokit supports services like Google, Yahoo, and Geocoder.us, and more --
+see the Geokit gem API for a complete list.
+
+These geocoder services are made available through the following classes:
+GoogleGeocoder, YahooGeocoder, UsGeocoder, CaGeocoder, and GeonamesGeocoder.
+Further, an additional geocoder class called MultiGeocoder incorporates an ordered failover
+sequence to increase the probability of successful geocoding.
+
+All classes are called using the following signature:
+
+ include Geokit::Geocoders
+ location = XxxGeocoder.geocode(address)
+
+where you replace Xxx Geocoder with the appropriate class. A GeoLoc
+instance is the result of the call. This class has a "success"
+attribute which will be true if a successful geocoding occurred.
+If successful, the lat and lng properties will be populated.
+
+Geocoders are named with the convention NameGeocoder. This
+naming convention enables Geocoder to auto-detect its sub-classes
+in order to create methods called `name_geocoder(address)` so that
+all geocoders can be called through the base class. This is done
+purely for convenience; the individual geocoder classes are expected
+to be used independently.
+
+The MultiGeocoder class requires the configuration of a provider
+order which dictates what order to use the various geocoders. Ordering
+is done through `Geokit::Geocoders::provider_order`, found in
+`config/initializers/geokit_config.rb`.
+
+If you don't already have a `geokit_config.rb` file, the plugin creates one
+when it is first installed.
+
+Make sure your failover configuration matches the usage characteristics
+of your application -- for example, if you routinely get bogus input to
+geocode, your code will be much slower if you have to failover among
+multiple geocoders before determining that the input was in fact bogus.
+
+The Geocoder.geocode method returns a GeoLoc object. Basic usage:
+
+ loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
+ if loc.success
+ puts loc.lat
+ puts loc.lng
+ puts loc.full_address
+ end
+
+## REVERSE GEOCODING
+
+Currently, only the Google Geocoder supports reverse geocoding.
+Pass the lat/lng as a string, array or LatLng instance:
+
+ res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
+ => #<Geokit::GeoLoc:0x558ed0 ...
+ res.full_address
+ "101-115 Main St, San Francisco, CA 94105, USA"
+
+The address will usually appear as a range, as it does in the above example.
+
+
+## INTEGRATED FIND WITH ADDRESS GEOCODING
+
+Just has you can pass an IP address directly into an ActiveRecord finder
+as the origin, you can also pass a physical address as the origin:
+
+ Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
+
+where the physical address would be geocoded to a location and then the
+resulting latitude and longitude coordinates would be used in the
+find.
+
+Note that if the address fails to geocode, the find method will raise an
+ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
+You can geocoder the address beforehand, and pass the resulting lat/lng
+into the finder if successful.
+
+## Auto Geocoding
+
+If your geocoding needs are simple, you can tell your model to automatically
+geocode itself on create:
+
+ class Store < ActiveRecord::Base
+ acts_as_mappable :auto_geocode=>true
+ end
+
+It takes two optional params:
+
+ class Store < ActiveRecord::Base
+ acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
+ end
+
+. . . which is equivilent to:
+
+ class Store << ActiveRecord::Base
+ acts_as_mappable
+ before_validation_on_create :geocode_address
+
+ private
+ def geocode_address
+ geo=Geokit::Geocoders::MultiGeocoder.geocode (address)
+ errors.add(:address, "Could not Geocode address") if !geo.success
+ self.lat, self.lng = geo.lat,geo.lng if geo.success
+ end
+ end
+
+If you need any more complicated geocoding behavior for your model, you should roll your own
+`before_validate` callback.
+
+
+## Distances, headings, endpoints, and midpoints
+
+ distance=home.distance_from(work, :units=>:miles)
+ heading=home.heading_to(work) # result is in degrees, 0 is north
+ endpoint=home.endpoint(90,2) # two miles due east
+ midpoing=home.midpoint_to(work)
+
+## Cool stuff you can do with bounds
+
+ bounds=Bounds.new(sw_point,ne_point)
+ bounds.contains?(home)
+ puts bounds.center
+
+
+HOW TO . . .
+=================================================================================
+
+A few quick examples to get you started ....
+
+## How to install the Geokit Rails plugin
+(See the very top of this file)
+
+## How to find all stores within a 10-mile radius of a given lat/lng
+1. ensure your stores table has lat and lng columns with numeric or float
+ datatypes to store your latitude/longitude
+
+3. use `acts_as_mappable` on your store model:
+
+ class Store < ActiveRecord::Base
+ acts_as_mappable
+ ...
+ end
+
+3. finders now have extra capabilities:
+
+ Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
+
+## How to geocode an address
+
+1. configure your geocoder key(s) in `config/initializers/geokit_config.rb`
+
+2. also in `geokit_config.rb`, make sure that `Geokit::Geocoders::provider_order` reflects the
+ geocoder(s). If you only want to use one geocoder, there should
+ be only one symbol in the array. For example:
+
+ Geokit::Geocoders::provider_order=[:google]
+
+3. Test it out in script/console
+
+ include Geokit::Geocoders
+ res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
+ puts res.lat
+ puts res.lng
+ puts res.full_address
+ ... etc. The return type is GeoLoc, see the API for
+ all the methods you can call on it.
+
+## How to find all stores within 10 miles of a given address
+
+1. as above, ensure your table has the lat/lng columns, and you've
+ applied `acts_as_mappable` to the Store model.
+
+2. configure and test out your geocoder, as above
+
+3. pass the address in under the :origin key
+
+ Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
+ :within=>10)
+
+4. you can also use a zipcode, or anything else that's geocodable:
+
+ Store.find(:all, :origin=>'94117',
+ :conditions=>'distance<10')
+
+## How to sort a query by distance from an origin
+
+You now have access to a 'distance' column, and you can use it
+as you would any other column. For example:
+ Store.find(:all, :origin=>'94117', :order=>'distance')
+
+## How to elements of an array according to distance from a common point
+
+Usually, you can do your sorting in the database as part of your find call.
+If you need to sort things post-query, you can do so:
+
+ stores=Store.find :all
+ stores.sort_by_distance_from(home)
+ puts stores.first.distance
+
+Obviously, each of the items in the array must have a latitude/longitude so
+they can be sorted by distance.
+
+## Database indexes
+
+MySQL can't create indexes on a calculated field such as those Geokit uses to
+calculate distance based on latitude/longitude values for a record. However,
+indexing the lat and lng columns does improve Geokit distance calculation
+performance since the lat and lng columns are used in a straight comparison
+for distance calculation. Assuming a Page model that is incorporating the
+Geokit plugin the migration would be as follows.
+
+ class AddIndexOPageLatAndLng < ActiveRecord::Migration
+
+ def self.up
+ add_index :pages, [:lat, :lng]
+ end
+
+ def self.down
+ remove_index :pages, [:lat, :lng]
+ end
+ end
+
+## Database Compatability
+
+* Geokit works with MySQL (tested with version 5.0.41), PostgreSQL (tested with version 8.2.6) and Microsoft SQL Server (tested with 2000).
+* Geokit does *not* work with SQLite, as it lacks the necessary geometry functions.
+* Geokit is known to *not* work with Postgres versions under 8.1 -- it uses the least() funciton.
+
+
+## HIGH-LEVEL NOTES ON WHAT'S WHERE
+
+`acts_as_mappable.rb`, as you'd expect, contains the ActsAsMappable
+module which gets mixed into your models to provide the
+location-based finder goodness.
+
+`ip_geocode_lookup.rb` contains the before_filter helper method which
+enables auto lookup of the requesting IP address.
+
+### The Geokit gem provides the building blocks of distance-based operations:
+
+The Mappable module, which provides basic
+distance calculation methods, i.e., calculating the distance
+between two points.
+
+The LatLng class is a simple container for latitude and longitude, but
+it's made more powerful by mixing in the above-mentioned Mappable
+module -- therefore, you can calculate easily the distance between two
+LatLng ojbects with `distance = first.distance_to(other)`
+
+GeoLoc represents an address or location which
+has been geocoded. You can get the city, zipcode, street address, etc.
+from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
+AND the Mappable modeule goodness for free.
+
+## GOOGLE GROUP
+
+Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
+
+## IMPORTANT POST-INSTALLATION NOTES:
+
+*1. The configuration file*: Geokit for Rails uses a configuration file in config/initializers.
+You *must* add your own keys for the various geocoding services if you want to use geocoding.
+If you need to refer to the original template again, see the `assets/api_keys_template` file.
+
+*2. The gem dependency*: Geokit for Rails depends on the Geokit gem. Tell Rails about this
+dependency in `config/environment.rb`, within the initializer block:
+config.gem "geokit"
+
+*If you're having trouble with dependencies ....*
+
+Try installing the gem manually (sudo gem install geokit), then adding a `require 'geokit'` to the top of
+`vendor/plugins/geokit-rails/init.rb` and/or `config/geokit_config.rb`.
View
18 vendor/plugins/geokit-rails/Rakefile
@@ -0,0 +1,18 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+load 'test/tasks.rake'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Generate documentation for the GeoKit plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'GeoKit'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
View
9 vendor/plugins/geokit-rails/about.yml
@@ -0,0 +1,9 @@
+author:
+ name_1: Bill Eisenhauer
+ homepage_1: http://blog.billeisenhauer.com
+ name_2: Andre Lewis
+ homepage_2: http://www.earthcode.com
+summary: Geo distance calculations, distance calculation query support, geocoding for physical and ip addresses.
+version: 1.1.4
+rails_version: 1.0+
+license: MIT
View
61 vendor/plugins/geokit-rails/assets/api_keys_template
@@ -0,0 +1,61 @@
+if defined? Geokit
+
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
+ Geokit::default_units = :miles
+ Geokit::default_formula = :sphere
+
+ # 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.
+ Geokit::Geocoders::request_timeout = 3
+
+ # These settings are used if web service calls must be routed through a proxy.
+ # 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.
+ Geokit::Geocoders::proxy_addr = nil
+ Geokit::Geocoders::proxy_port = nil
+ Geokit::Geocoders::proxy_user = nil
+ Geokit::Geocoders::proxy_pass = nil
+
+ # This is your yahoo application key for the Yahoo Geocoder.
+ # See http://developer.yahoo.com/faq/index.html#appid
+ # and http://developer.yahoo.com/maps/rest/V1/geocode.html
+ Geokit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
+
+ # This is your Google Maps geocoder key.
+ # See http://www.google.com/apis/maps/signup.html
+ # and http://www.google.com/apis/maps/documentation/#Geocoding_Examples
+ Geokit::Geocoders::google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
+
+ # This is your username and password for geocoder.us.
+ # To use the free service, the value can be set to nil or false. For
+ # usage tied to an account, the value should be set to username:password.
+ # See http://geocoder.us
+ # and http://geocoder.us/user/signup
+ Geokit::Geocoders::geocoder_us = false
+
+ # This is your authorization key for geocoder.ca.
+ # 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
+ # Geocoder.ca.
+ # See http://geocoder.ca
+ # and http://geocoder.ca/?register=1
+ Geokit::Geocoders::geocoder_ca = false
+
+ # Uncomment to use a username with the Geonames geocoder
+ #Geokit::Geocoders::geonames="REPLACE_WITH_YOUR_GEONAMES_USERNAME"
+
+ # This is the order in which the geocoders are called in a failover scenario
+ # If you only want to use a single geocoder, put a single symbol in the array.
+ # Valid symbols are :google, :yahoo, :us, and :ca.
+ # 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.
+ Geokit::Geocoders::provider_order = [:google,:us]
+
+ # The IP provider order. Valid symbols are :ip,:geo_plugin.
+ # As before, make sure you read up on relevant Terms of Use for each
+ # Geokit::Geocoders::ip_provider_order = [:geo_plugin,:ip]
+
+end
View
1 vendor/plugins/geokit-rails/init.rb
@@ -0,0 +1 @@
+require 'geokit-rails'
View
14 vendor/plugins/geokit-rails/install.rb
@@ -0,0 +1,14 @@
+# Display to the console the contents of the README file.
+puts IO.read(File.join(File.dirname(__FILE__), 'README.markdown'))
+
+# place the api_keys_template in the application's /config/initializers/geokit_config.rb
+path=File.expand_path(File.join(File.dirname(__FILE__), '../../../config/initializers/geokit_config.rb'))
+template_path=File.join(File.dirname(__FILE__), '/assets/api_keys_template')
+if File.exists?(path)
+ puts "It looks like you already have a configuration file at #{path}. We've left it as-is. Recommended: check #{template_path} to see if anything has changed, and update config file accordingly."
+else
+ File.open(path, "w") do |f|
+ f.puts IO.read(template_path)
+ puts "We created a configuration file for you in config/initializers/geokit_config.rb. Add your Google API keys, etc there."
+ end
+end
View
26 vendor/plugins/geokit-rails/lib/geokit-rails.rb
@@ -0,0 +1,26 @@
+# Load modules and classes needed to automatically mix in ActiveRecord and
+# ActionController helpers. All other functionality must be explicitly
+# required.
+#
+# Note that we don't explicitly require the geokit gem.
+# You should specify gem dependencies in your config/environment.rb: config.gem "geokit"
+#
+if defined? Geokit
+ require 'geokit-rails/defaults'
+ require 'geokit-rails/adapters/abstract'
+ require 'geokit-rails/acts_as_mappable'
+ require 'geokit-rails/ip_geocode_lookup'
+
+ # Automatically mix in distance finder support into ActiveRecord classes.
+ ActiveRecord::Base.send :include, GeoKit::ActsAsMappable
+
+ # Automatically mix in ip geocoding helpers into ActionController classes.
+ ActionController::Base.send :include, GeoKit::IpGeocodeLookup
+else
+ message=%q(WARNING: geokit-rails requires the Geokit gem. You either don't have the gem installed,
+or you haven't told Rails to require it. If you're using a recent version of Rails:
+ config.gem "geokit" # in config/environment.rb
+and of course install the gem: sudo gem install geokit)
+ puts message
+ Rails.logger.error message
+end
View
456 vendor/plugins/geokit-rails/lib/geokit-rails/acts_as_mappable.rb
@@ -0,0 +1,456 @@
+module Geokit
+ # Contains the class method acts_as_mappable targeted to be mixed into ActiveRecord.
+ # When mixed in, augments find services such that they provide distance calculation
+ # query services. The find method accepts additional options:
+ #
+ # * :origin - can be
+ # 1. a two-element array of latititude/longitude -- :origin=>[37.792,-122.393]
+ # 2. a geocodeable string -- :origin=>'100 Spear st, San Francisco, CA'
+ # 3. an object which responds to lat and lng methods, or latitude and longitude methods,
+ # or whatever methods you have specified for lng_column_name and lat_column_name
+ #
+ # Other finder methods are provided for specific queries. These are:
+ #
+ # * find_within (alias: find_inside)
+ # * find_beyond (alias: find_outside)
+ # * find_closest (alias: find_nearest)
+ # * find_farthest
+ #
+ # Counter methods are available and work similarly to finders.
+ #
+ # If raw SQL is desired, the distance_sql method can be used to obtain SQL appropriate
+ # to use in a find_by_sql call.
+ module ActsAsMappable
+ class UnsupportedAdapter < StandardError ; end
+
+ # Mix below class methods into ActiveRecord.
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ # Class method to mix into active record.
+ module ClassMethods # :nodoc:
+
+ # Class method to bring distance query support into ActiveRecord models. By default
+ # uses :miles for distance units and performs calculations based upon the Haversine
+ # (sphere) formula. These can be changed by setting Geokit::default_units and
+ # Geokit::default_formula. Also, by default, uses lat, lng, and distance for respective
+ # column names. All of these can be overridden using the :default_units, :default_formula,
+ # :lat_column_name, :lng_column_name, and :distance_column_name hash keys.
+ #
+ # Can also use to auto-geocode a specific column on create. Syntax;
+ #
+ # acts_as_mappable :auto_geocode=>true
+ #
+ # By default, it tries to geocode the "address" field. Or, for more customized behavior:
+ #
+ # acts_as_mappable :auto_geocode=>{:field=>:address,:error_message=>'bad address'}
+ #
+ # In both cases, it creates a before_validation_on_create callback to geocode the given column.
+ # For anything more customized, we recommend you forgo the auto_geocode option
+ # and create your own AR callback to handle geocoding.
+ def acts_as_mappable(options = {})
+ metaclass = (class << self; self; end)
+
+ # Mix in the module, but ensure to do so just once.
+ return if !defined?(Geokit::Mappable) || metaclass.included_modules.include?(Geokit::ActsAsMappable::SingletonMethods)
+
+ send :extend, Geokit::ActsAsMappable::SingletonMethods
+ send :include, Geokit::Mappable
+
+ cattr_accessor :through
+ self.through = options[:through]
+
+ if reflection = Geokit::ActsAsMappable.end_of_reflection_chain(self.through, self)
+ metaclass.instance_eval do
+ [ :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|
+ define_method method_name do
+ reflection.klass.send(method_name)
+ end
+ end
+ end
+ else
+ cattr_accessor :distance_column_name, :default_units, :default_formula, :lat_column_name, :lng_column_name, :qualified_lat_column_name, :qualified_lng_column_name
+
+ self.distance_column_name = options[:distance_column_name] || 'distance'
+ self.default_units = options[:default_units] || Geokit::default_units
+ self.default_formula = options[:default_formula] || Geokit::default_formula
+ self.lat_column_name = options[:lat_column_name] || 'lat'
+ self.lng_column_name = options[:lng_column_name] || 'lng'
+ self.qualified_lat_column_name = "#{table_name}.#{lat_column_name}"
+ self.qualified_lng_column_name = "#{table_name}.#{lng_column_name}"
+
+ if options.include?(:auto_geocode) && options[:auto_geocode]
+ # if the form auto_geocode=>true is used, let the defaults take over by suppling an empty hash
+ options[:auto_geocode] = {} if options[:auto_geocode] == true
+ cattr_accessor :auto_geocode_field, :auto_geocode_error_message
+ self.auto_geocode_field = options[:auto_geocode][:field] || 'address'
+ self.auto_geocode_error_message = options[:auto_geocode][:error_message] || 'could not locate address'
+
+ # set the actual callback here
+ before_validation_on_create :auto_geocode_address
+ end
+ end
+ end
+ end
+
+ # this is the callback for auto_geocoding
+ def auto_geocode_address
+ address=self.send(auto_geocode_field).to_s
+ geo=Geokit::Geocoders::MultiGeocoder.geocode(address)
+
+ if geo.success
+ self.send("#{lat_column_name}=", geo.lat)
+ self.send("#{lng_column_name}=", geo.lng)
+ else
+ errors.add(auto_geocode_field, auto_geocode_error_message)
+ end
+
+ geo.success
+ end
+
+ def self.end_of_reflection_chain(through, klass)
+ while through
+ reflection = nil
+ if through.is_a?(Hash)
+ association, through = through.to_a.first
+ else
+ association, through = through, nil
+ end
+
+ if reflection = klass.reflect_on_association(association)
+ klass = reflection.klass
+ else
+ raise ArgumentError, "You gave #{association} in :through, but I could not find it on #{klass}."
+ end
+ end
+
+ reflection
+ end
+
+ # Instance methods to mix into ActiveRecord.
+ module SingletonMethods #:nodoc:
+
+ # A proxy to an instance of a finder adapter, inferred from the connection's adapter.
+ def adapter
+ @adapter ||= begin
+ require File.join(File.dirname(__FILE__), 'adapters', connection.adapter_name.downcase)
+ klass = Adapters.const_get(connection.adapter_name.camelcase)
+ klass.load(self) unless klass.loaded
+ klass.new(self)
+ rescue LoadError
+ raise UnsupportedAdapter, "`#{connection.adapter_name.downcase}` is not a supported adapter."
+ end
+ end
+
+ # Extends the existing find method in potentially two ways:
+ # - If a mappable instance exists in the options, adds a distance column.
+ # - If a mappable instance exists in the options and the distance column exists in the
+ # conditions, substitutes the distance sql for the distance column -- this saves
+ # having to write the gory SQL.
+ def find(*args)
+ prepare_for_find_or_count(:find, args)
+ super(*args)
+ end
+
+ # Extends the existing count method by:
+ # - If a mappable instance exists in the options and the distance column exists in the
+ # conditions, substitutes the distance sql for the distance column -- this saves
+ # having to write the gory SQL.
+ def count(*args)
+ prepare_for_find_or_count(:count, args)
+ super(*args)
+ end
+
+ # Finds within a distance radius.
+ def find_within(distance, options={})
+ options[:within] = distance
+ find(:all, options)
+ end
+ alias find_inside find_within
+
+ # Finds beyond a distance radius.
+ def find_beyond(distance, options={})
+ options[:beyond] = distance
+ find(:all, options)
+ end
+ alias find_outside find_beyond
+
+ # Finds according to a range. Accepts inclusive or exclusive ranges.
+ def find_by_range(range, options={})
+ options[:range] = range
+ find(:all, options)
+ end
+
+ # Finds the closest to the origin.
+ def find_closest(options={})
+ find(:nearest, options)
+ end
+ alias find_nearest find_closest
+
+ # Finds the farthest from the origin.
+ def find_farthest(options={})
+ find(:farthest, options)
+ end
+
+ # Finds within rectangular bounds (sw,ne).
+ def find_within_bounds(bounds, options={})
+ options[:bounds] = bounds
+ find(:all, options)
+ end
+
+ # counts within a distance radius.
+ def count_within(distance, options={})
+ options[:within] = distance
+ count(options)
+ end
+ alias count_inside count_within
+
+ # Counts beyond a distance radius.
+ def count_beyond(distance, options={})
+ options[:beyond] = distance
+ count(options)
+ end
+ alias count_outside count_beyond
+
+ # Counts according to a range. Accepts inclusive or exclusive ranges.
+ def count_by_range(range, options={})
+ options[:range] = range
+ count(options)
+ end
+
+ # Finds within rectangular bounds (sw,ne).
+ def count_within_bounds(bounds, options={})
+ options[:bounds] = bounds
+ count(options)
+ end
+
+ # Returns the distance calculation to be used as a display column or a condition. This
+ # is provide for anyone wanting access to the raw SQL.
+ def distance_sql(origin, units=default_units, formula=default_formula)
+ case formula
+ when :sphere
+ sql = sphere_distance_sql(origin, units)
+ when :flat
+ sql = flat_distance_sql(origin, units)
+ end
+ sql
+ end
+
+ private
+
+ # Prepares either a find or a count action by parsing through the options and
+ # conditionally adding to the select clause for finders.
+ def prepare_for_find_or_count(action, args)
+ options = args.extract_options!
+ #options = defined?(args.extract_options!) ? args.extract_options! : extract_options_from_args!(args)
+ # Obtain items affecting distance condition.
+ origin = extract_origin_from_options(options)
+ units = extract_units_from_options(options)
+ formula = extract_formula_from_options(options)
+ bounds = extract_bounds_from_options(options)
+
+ # Only proceed if this is a geokit-related query
+ if origin || bounds
+ # if no explicit bounds were given, try formulating them from the point and distance given
+ bounds = formulate_bounds_from_distance(options, origin, units) unless bounds
+ # Apply select adjustments based upon action.
+ add_distance_to_select(options, origin, units, formula) if origin && action == :find
+ # Apply the conditions for a bounding rectangle if applicable
+ apply_bounds_conditions(options,bounds) if bounds
+ # Apply distance scoping and perform substitutions.
+ apply_distance_scope(options)
+ substitute_distance_in_conditions(options, origin, units, formula) if origin && options.has_key?(:conditions)
+ # Order by scoping for find action.
+ apply_find_scope(args, options) if action == :find
+ # Handle :through
+ apply_include_for_through(options)
+ # Unfortunatley, we need to do extra work if you use an :include. See the method for more info.
+ handle_order_with_include(options,origin,units,formula) if options.include?(:include) && options.include?(:order) && origin
+ end
+
+ # Restore options minus the extra options that we used for the
+ # Geokit API.
+ args.push(options)
+ end
+
+ def apply_include_for_through(options)
+ if self.through
+ case options[:include]
+ when Array
+ options[:include] << self.through
+ when Hash, String, Symbol
+ options[:include] = [ self.through, options[:include] ]
+ else
+ options[:include] = [ self.through ]
+ end
+ end
+ end
+
+ # If we're here, it means that 1) an origin argument, 2) an :include, 3) an :order clause were supplied.
+ # Now we have to sub some SQL into the :order clause. The reason is that when you do an :include,
+ # ActiveRecord drops the psuedo-column (specificically, distance) which we supplied for :select.
+ # So, the 'distance' column isn't available for the :order clause to reference when we use :include.
+ def handle_order_with_include(options, origin, units, formula)
+ # replace the distance_column_name with the distance sql in order clause
+ options[:order].sub!(distance_column_name, distance_sql(origin, units, formula))
+ end
+
+ # Looks for mapping-specific tokens and makes appropriate translations so that the
+ # original finder has its expected arguments. Resets the the scope argument to
+ # :first and ensures the limit is set to one.
+ def apply_find_scope(args, options)
+ case args.first
+ when :nearest, :closest
+ args[0] = :first
+ options[:limit] = 1
+ options[:order] = "#{distance_column_name} ASC"
+ when :farthest
+ args[0] = :first
+ options[:limit] = 1
+ options[:order] = "#{distance_column_name} DESC"
+ end
+ end
+
+ # If it's a :within query, add a bounding box to improve performance.
+ # This only gets called if a :bounds argument is not otherwise supplied.
+ def formulate_bounds_from_distance(options, origin, units)
+ distance = options[:within] if options.has_key?(:within)
+ distance = options[:range].last-(options[:range].exclude_end?? 1 : 0) if options.has_key?(:range)
+ if distance
+ res=Geokit::Bounds.from_point_and_radius(origin,distance,:units=>units)
+ else
+ nil
+ end
+ end
+
+ # Replace :within, :beyond and :range distance tokens with the appropriate distance
+ # where clauses. Removes these tokens from the options hash.
+ def apply_distance_scope(options)
+ distance_condition = if options.has_key?(:within)
+ "#{distance_column_name} <= #{options[:within]}"
+ elsif options.has_key?(:beyond)
+ "#{distance_column_name} > #{options[:beyond]}"
+ elsif options.has_key?(:range)
+ "#{distance_column_name} >= #{options[:range].first} AND #{distance_column_name} <#{'=' unless options[:range].exclude_end?} #{options[:range].last}"
+ end
+
+ if distance_condition
+ [:within, :beyond, :range].each { |option| options.delete(option) }
+ options[:conditions] = merge_conditions(options[:conditions], distance_condition)
+ end
+ end
+
+ # Alters the conditions to include rectangular bounds conditions.
+ def apply_bounds_conditions(options,bounds)
+ sw,ne = bounds.sw, bounds.ne
+ 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}"
+ bounds_sql = "#{qualified_lat_column_name}>#{sw.lat} AND #{qualified_lat_column_name}<#{ne.lat} AND #{lng_sql}"
+ options[:conditions] = merge_conditions(options[:conditions], bounds_sql)
+ end
+
+ # Extracts the origin instance out of the options if it exists and returns
+ # it. If there is no origin, looks for latitude and longitude values to
+ # create an origin. The side-effect of the method is to remove these
+ # option keys from the hash.
+ def extract_origin_from_options(options)
+ origin = options.delete(:origin)
+ res = normalize_point_to_lat_lng(origin) if origin
+ res
+ end
+
+ # Extract the units out of the options if it exists and returns it. If
+ # there is no :units key, it uses the default. The side effect of the
+ # method is to remove the :units key from the options hash.
+ def extract_units_from_options(options)
+ units = options[:units] || default_units
+ options.delete(:units)
+ units
+ end
+
+ # Extract the formula out of the options if it exists and returns it. If
+ # there is no :formula key, it uses the default. The side effect of the
+ # method is to remove the :formula key from the options hash.
+ def extract_formula_from_options(options)
+ formula = options[:formula] || default_formula
+ options.delete(:formula)
+ formula
+ end
+
+ def extract_bounds_from_options(options)
+ bounds = options.delete(:bounds)
+ bounds = Geokit::Bounds.normalize(bounds) if bounds
+ end
+
+ # Geocode IP address.
+ def geocode_ip_address(origin)
+ geo_location = Geokit::Geocoders::MultiGeocoder.geocode(origin)
+ return geo_location if geo_location.success
+ raise Geokit::Geocoders::GeocodeError
+ end
+
+ # Given a point in a variety of (an address to geocode,
+ # an array of [lat,lng], or an object with appropriate lat/lng methods, an IP addres)
+ # this method will normalize it into a Geokit::LatLng instance. The only thing this
+ # method adds on top of LatLng#normalize is handling of IP addresses
+ def normalize_point_to_lat_lng(point)
+ res = geocode_ip_address(point) if point.is_a?(String) && /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/.match(point)
+ res = Geokit::LatLng.normalize(point) unless res
+ res
+ end
+
+ # Augments the select with the distance SQL.
+ def add_distance_to_select(options, origin, units=default_units, formula=default_formula)
+ if origin
+ distance_selector = distance_sql(origin, units, formula) + " AS #{distance_column_name}"
+ selector = options.has_key?(:select) && options[:select] ? options[:select] : "*"
+ options[:select] = "#{selector}, #{distance_selector}"
+ end
+ end
+
+ # Looks for the distance column and replaces it with the distance sql. If an origin was not
+ # passed in and the distance column exists, we leave it to be flagged as bad SQL by the database.
+ # Conditions are either a string or an array. In the case of an array, the first entry contains
+ # the condition.
+ def substitute_distance_in_conditions(options, origin, units=default_units, formula=default_formula)
+ condition = options[:conditions].is_a?(String) ? options[:conditions] : options[:conditions].first
+ pattern = Regexp.new("\\b#{distance_column_name}\\b")
+ condition.gsub!(pattern, distance_sql(origin, units, formula))
+ end
+
+ # Returns the distance SQL using the spherical world formula (Haversine). The SQL is tuned
+ # to the database in use.
+ def sphere_distance_sql(origin, units)
+ lat = deg2rad(origin.lat)
+ lng = deg2rad(origin.lng)
+ multiplier = units_sphere_multiplier(units)
+
+ adapter.sphere_distance_sql(lat, lng, multiplier) if adapter
+ end
+
+ # Returns the distance SQL using the flat-world formula (Phythagorean Theory). The SQL is tuned
+ # to the database in use.
+ def flat_distance_sql(origin, units)
+ lat_degree_units = units_per_latitude_degree(units)
+ lng_degree_units = units_per_longitude_degree(origin.lat, units)
+
+ adapter.flat_distance_sql(origin, lat_degree_units, lng_degree_units)
+ end
+ end
+ end
+end
+
+# Extend Array with a sort_by_distance method.
+class Array
+ # This method creates a "distance" attribute on each object, calculates the
+ # distance from the passed origin, and finally sorts the array by the
+ # resulting distance.
+ def sort_by_distance_from(origin, opts={})
+ distance_attribute_name = opts.delete(:distance_attribute_name) || 'distance'
+ self.each do |e|
+ e.class.send(:attr_accessor, distance_attribute_name) if !e.respond_to?("#{distance_attribute_name}=")
+ e.send("#{distance_attribute_name}=", e.distance_to(origin,opts))
+ end
+ self.sort!{|a,b|a.send(distance_attribute_name) <=> b.send(distance_attribute_name)}
+ end
+end
View
31 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/abstract.rb
@@ -0,0 +1,31 @@
+module Geokit
+ module Adapters
+ class Abstract
+ class NotImplementedError < StandardError ; end
+
+ cattr_accessor :loaded
+
+ class << self
+ def load(klass) ; end
+ end
+
+ def initialize(klass)
+ @owner = klass
+ end
+
+ def method_missing(method, *args, &block)
+ return @owner.send(method, *args, &block) if @owner.respond_to?(method)
+ super
+ end
+
+ def sphere_distance_sql(lat, lng, multiplier)
+ raise NotImplementedError, '#sphere_distance_sql is not implemented'
+ end
+
+ def flat_distance_sql(origin, lat_degree_units, lng_degree_units)
+ raise NotImplementedError, '#flat_distance_sql is not implemented'
+ end
+
+ end
+ end
+end
View
22 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/mysql.rb
@@ -0,0 +1,22 @@
+module Geokit
+ module Adapters
+ class MySQL < Abstract
+
+ def sphere_distance_sql(lat, lng, multiplier)
+ %|
+ (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
+ COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
+ SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})
+ |
+ end
+
+ def flat_distance_sql(origin, lat_degree_units, lng_degree_units)
+ %|
+ SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+
+ POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2))
+ |
+ end
+
+ end
+ end
+end
View
22 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/postgresql.rb
@@ -0,0 +1,22 @@
+module Geokit
+ module Adapters
+ class PostgreSQL < Abstract
+
+ def sphere_distance_sql(lat, lng, multiplier)
+ %|
+ (ACOS(least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
+ COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
+ SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})
+ |
+ end
+
+ def flat_distance_sql(origin, lat_degree_units, lng_degree_units)
+ %|
+ SQRT(POW(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+
+ POW(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2))
+ |
+ end
+
+ end
+ end
+end
View
43 vendor/plugins/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb
@@ -0,0 +1,43 @@
+module Geokit
+ module Adapters
+ class SQLServer < Abstract
+
+ class << self
+
+ def load(klass)
+ klass.connection.execute <<-EOS
+ if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[geokit_least]') and xtype in (N'FN', N'IF', N'TF'))
+ drop function [dbo].[geokit_least]
+ EOS
+
+ klass.connection.execute <<-EOS
+ CREATE FUNCTION [dbo].geokit_least (@value1 float,@value2 float) RETURNS float AS BEGIN
+ return (SELECT CASE WHEN @value1 < @value2 THEN @value1 ELSE @value2 END) END
+ EOS
+ self.loaded = true
+ end
+
+ end
+
+ def initialize(*args)
+ super(*args)
+ end
+
+ def sphere_distance_sql(lat, lng, multiplier)
+ %|
+ (ACOS([dbo].geokit_least(1,COS(#{lat})*COS(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*COS(RADIANS(#{qualified_lng_column_name}))+
+ COS(#{lat})*SIN(#{lng})*COS(RADIANS(#{qualified_lat_column_name}))*SIN(RADIANS(#{qualified_lng_column_name}))+
+ SIN(#{lat})*SIN(RADIANS(#{qualified_lat_column_name}))))*#{multiplier})
+ |
+ end
+
+ def flat_distance_sql(origin, lat_degree_units, lng_degree_units)
+ %|
+ SQRT(POWER(#{lat_degree_units}*(#{origin.lat}-#{qualified_lat_column_name}),2)+
+ POWER(#{lng_degree_units}*(#{origin.lng}-#{qualified_lng_column_name}),2))
+ |
+ end
+
+ end
+ end
+end
View
22 vendor/plugins/geokit-rails/lib/geokit-rails/defaults.rb
@@ -0,0 +1,22 @@
+module Geokit
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
+ @@default_units = :miles
+ @@default_formula = :sphere
+
+ [:default_units, :default_formula].each do |sym|
+ class_eval <<-EOS, __FILE__, __LINE__
+ def self.#{sym}
+ if defined?(#{sym.to_s.upcase})
+ #{sym.to_s.upcase}
+ else
+ @@#{sym}
+ end
+ end
+
+ def self.#{sym}=(obj)
+ @@#{sym} = obj
+ end
+ EOS
+ end
+ Geokit::Geocoders.logger = ActiveRecord::Base.logger
+end
View
16 vendor/plugins/geokit-rails/lib/geokit-rails/geocoder_control.rb
@@ -0,0 +1,16 @@
+module Geokit
+ module GeocoderControl
+ def set_geokit_domain
+ Geokit::Geocoders::domain = request.domain
+ logger.debug("Geokit is using the domain: #{Geokit::Geocoders::domain}")
+ end
+
+ def self.included(base)
+ if base.respond_to? :before_filter
+ base.send :before_filter, :set_geokit_domain
+ end
+ end
+ end
+end
+
+ActionController::Base.send(:include, Geokit::GeocoderControl) if defined?(ActionController::Base)
View
46 vendor/plugins/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb
@@ -0,0 +1,46 @@
+require 'yaml'
+
+module Geokit
+ # Contains a class method geocode_ip_address which can be used to enable automatic geocoding
+ # for request IP addresses. The geocoded information is stored in a cookie and in the
+ # session to minimize web service calls. The point of the helper is to enable location-based
+ # websites to have a best-guess for new visitors.
+ module IpGeocodeLookup
+ # Mix below class methods into ActionController.
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ # Class method to mix into active record.
+ module ClassMethods # :nodoc:
+ def geocode_ip_address(filter_options = {})
+ before_filter :store_ip_location, filter_options
+ end
+ end
+
+ private
+
+ # Places the IP address' geocode location into the session if it
+ # can be found. Otherwise, looks for a geo location cookie and
+ # uses that value. The last resort is to call the web service to
+ # get the value.
+ def store_ip_location
+ session[:geo_location] ||= retrieve_location_from_cookie_or_service
+ cookies[:geo_location] = { :value => session[:geo_location].to_yaml, :expires => 30.days.from_now } if session[:geo_location]
+ end
+
+ # Uses the stored location value from the cookie if it exists. If
+ # no cookie exists, calls out to the web service to get the location.
+ def retrieve_location_from_cookie_or_service
+ return YAML.load(cookies[:geo_location]) if cookies[:geo_location]
+ location = Geocoders::MultiGeocoder.geocode(get_ip_address)
+ return location.success ? location : nil
+ end
+
+ # Returns the real ip address, though this could be the localhost ip
+ # address. No special handling here anymore.
+ def get_ip_address
+ request.remote_ip
+ end
+ end
+end
View
474 vendor/plugins/geokit-rails/test/acts_as_mappable_test.rb
@@ -0,0 +1,474 @@
+require 'test_helper'
+
+
+Geokit::Geocoders::provider_order = [:google, :us]
+
+class ActsAsMappableTest < GeokitTestCase
+
+ LOCATION_A_IP = "217.10.83.5"
+
+ def setup
+ @location_a = GeoKit::GeoLoc.new
+ @location_a.lat = 32.918593
+ @location_a.lng = -96.958444
+ @location_a.city = "Irving"
+ @location_a.state = "TX"
+ @location_a.country_code = "US"
+ @location_a.success = true
+
+ @sw = GeoKit::LatLng.new(32.91663,-96.982841)
+ @ne = GeoKit::LatLng.new(32.96302,-96.919495)
+ @bounds_center=GeoKit::LatLng.new((@sw.lat+@ne.lat)/2,(@sw.lng+@ne.lng)/2)
+
+ @starbucks = companies(:starbucks)
+ @loc_a = locations(:a)
+ @custom_loc_a = custom_locations(:a)
+ @loc_e = locations(:e)
+ @custom_loc_e = custom_locations(:e)
+
+ @barnes_and_noble = mock_organizations(:barnes_and_noble)
+ @address = mock_addresses(:address_barnes_and_noble)
+ end
+
+ def test_override_default_units_the_hard_way
+ Location.default_units = :kms
+ locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 5, locations.size
+ locations = Location.count(:origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 5, locations
+ Location.default_units = :miles
+ end
+
+ def test_include
+ locations = Location.find(:all, :origin => @loc_a, :include => :company, :conditions => "company_id = 1")
+ assert !locations.empty?
+ assert_equal 1, locations[0].company.id
+ assert_equal 'Starbucks', locations[0].company.name
+ end
+
+ def test_distance_between_geocoded
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("San Francisco, CA").returns(@location_a)
+ assert_equal 0, Location.distance_between("Irving, TX", "San Francisco, CA")
+ end
+
+ def test_distance_to_geocoded
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
+ assert_equal 0, @custom_loc_a.distance_to("Irving, TX")
+ end
+
+ def test_distance_to_geocoded_error
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(GeoKit::GeoLoc.new)
+ assert_raise(GeoKit::Geocoders::GeocodeError) { @custom_loc_a.distance_to("Irving, TX") }
+ end
+
+ def test_custom_attributes_distance_calculations
+ assert_equal 0, @custom_loc_a.distance_to(@loc_a)
+ assert_equal 0, CustomLocation.distance_between(@custom_loc_a, @loc_a)
+ end
+
+ def test_distance_column_in_select
+ locations = Location.find(:all, :origin => @loc_a, :order => "distance ASC")
+ assert_equal 6, locations.size
+ assert_equal 0, @loc_a.distance_to(locations.first)
+ assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
+ end
+
+ def test_find_with_distance_condition
+ locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 5, locations.size
+ locations = Location.count(:origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 5, locations
+ end
+
+ def test_find_with_distance_condition_with_units_override
+ locations = Location.find(:all, :origin => @loc_a, :units => :kms, :conditions => "distance < 6.387")
+ assert_equal 5, locations.size
+ locations = Location.count(:origin => @loc_a, :units => :kms, :conditions => "distance < 6.387")
+ assert_equal 5, locations
+ end
+
+ def test_find_with_distance_condition_with_formula_override
+ locations = Location.find(:all, :origin => @loc_a, :formula => :flat, :conditions => "distance < 6.387")
+ assert_equal 6, locations.size
+ locations = Location.count(:origin => @loc_a, :formula => :flat, :conditions => "distance < 6.387")
+ assert_equal 6, locations
+ end
+
+ def test_find_within
+ locations = Location.find_within(3.97, :origin => @loc_a)
+ assert_equal 5, locations.size
+ locations = Location.count_within(3.97, :origin => @loc_a)
+ assert_equal 5, locations
+ end
+
+ def test_find_within_with_token
+ locations = Location.find(:all, :within => 3.97, :origin => @loc_a)
+ assert_equal 5, locations.size
+ locations = Location.count(:within => 3.97, :origin => @loc_a)
+ assert_equal 5, locations
+ end
+
+ def test_find_within_with_coordinates
+ locations = Location.find_within(3.97, :origin =>[@loc_a.lat,@loc_a.lng])
+ assert_equal 5, locations.size
+ locations = Location.count_within(3.97, :origin =>[@loc_a.lat,@loc_a.lng])
+ assert_equal 5, locations
+ end
+
+ def test_find_with_compound_condition
+ locations = Location.find(:all, :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations.size
+ locations = Location.count(:origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations
+ end
+
+ def test_find_with_secure_compound_condition
+ locations = Location.find(:all, :origin => @loc_a, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
+ assert_equal 2, locations.size
+ locations = Location.count(:origin => @loc_a, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
+ assert_equal 2, locations
+ end
+
+ def test_find_beyond
+ locations = Location.find_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations.size
+ locations = Location.count_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations
+ end
+
+ def test_find_beyond_with_token
+ locations = Location.find(:all, :beyond => 3.95, :origin => @loc_a)
+ assert_equal 1, locations.size
+ locations = Location.count(:beyond => 3.95, :origin => @loc_a)
+ assert_equal 1, locations
+ end
+
+ def test_find_beyond_with_coordinates
+ locations = Location.find_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 1, locations.size
+ locations = Location.count_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 1, locations
+ end
+
+ def test_find_range_with_token
+ locations = Location.find(:all, :range => 0..10, :origin => @loc_a)
+ assert_equal 6, locations.size
+ locations = Location.count(:range => 0..10, :origin => @loc_a)
+ assert_equal 6, locations
+ end
+
+ def test_find_range_with_token_with_conditions
+ locations = Location.find(:all, :origin => @loc_a, :range => 0..10, :conditions => ["city = ?", 'Coppell'])
+ assert_equal 2, locations.size
+ locations = Location.count(:origin => @loc_a, :range => 0..10, :conditions => ["city = ?", 'Coppell'])
+ assert_equal 2, locations
+ end
+
+ def test_find_range_with_token_with_hash_conditions
+ locations = Location.find(:all, :origin => @loc_a, :range => 0..10, :conditions => {:city => 'Coppell'})
+ assert_equal 2, locations.size
+ locations = Location.count(:origin => @loc_a, :range => 0..10, :conditions => {:city => 'Coppell'})
+ assert_equal 2, locations
+ end
+
+ def test_find_range_with_token_excluding_end
+ locations = Location.find(:all, :range => 0...10, :origin => @loc_a)
+ assert_equal 6, locations.size
+ locations = Location.count(:range => 0...10, :origin => @loc_a)
+ assert_equal 6, locations
+ end
+
+ def test_find_nearest
+ assert_equal @loc_a, Location.find_nearest(:origin => @loc_a)
+ end
+
+ def test_find_nearest_through_find
+ assert_equal @loc_a, Location.find(:nearest, :origin => @loc_a)
+ end
+
+ def test_find_nearest_with_coordinates
+ assert_equal @loc_a, Location.find_nearest(:origin =>[@loc_a.lat, @loc_a.lng])
+ end
+
+ def test_find_farthest
+ assert_equal @loc_e, Location.find_farthest(:origin => @loc_a)
+ end
+
+ def test_find_farthest_through_find
+ assert_equal @loc_e, Location.find(:farthest, :origin => @loc_a)
+ end
+
+ def test_find_farthest_with_coordinates
+ assert_equal @loc_e, Location.find_farthest(:origin =>[@loc_a.lat, @loc_a.lng])
+ end
+
+ def test_scoped_distance_column_in_select
+ locations = @starbucks.locations.find(:all, :origin => @loc_a, :order => "distance ASC")
+ assert_equal 5, locations.size
+ assert_equal 0, @loc_a.distance_to(locations.first)
+ assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
+ end
+
+ def test_scoped_find_with_distance_condition
+ locations = @starbucks.locations.find(:all, :origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 4, locations.size
+ locations = @starbucks.locations.count(:origin => @loc_a, :conditions => "distance < 3.97")
+ assert_equal 4, locations
+ end
+
+ def test_scoped_find_within
+ locations = @starbucks.locations.find_within(3.97, :origin => @loc_a)
+ assert_equal 4, locations.size
+ locations = @starbucks.locations.count_within(3.97, :origin => @loc_a)
+ assert_equal 4, locations
+ end
+
+ def test_scoped_find_with_compound_condition
+ locations = @starbucks.locations.find(:all, :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations.size
+ locations = @starbucks.locations.count( :origin => @loc_a, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations
+ end
+
+ def test_scoped_find_beyond
+ locations = @starbucks.locations.find_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations.size
+ locations = @starbucks.locations.count_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations
+ end
+
+ def test_scoped_find_nearest
+ assert_equal @loc_a, @starbucks.locations.find_nearest(:origin => @loc_a)
+ end
+
+ def test_scoped_find_farthest
+ assert_equal @loc_e, @starbucks.locations.find_farthest(:origin => @loc_a)
+ end
+
+ def test_ip_geocoded_distance_column_in_select
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find(:all, :origin => LOCATION_A_IP, :order => "distance ASC")
+ assert_equal 6, locations.size
+ assert_equal 0, @loc_a.distance_to(locations.first)
+ assert_in_delta 3.97, @loc_a.distance_to(locations.last, :units => :miles, :formula => :sphere), 0.01
+ end
+
+ def test_ip_geocoded_find_with_distance_condition
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => "distance < 3.97")
+ assert_equal 5, locations.size
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.count(:origin => LOCATION_A_IP, :conditions => "distance < 3.97")
+ assert_equal 5, locations
+ end
+
+ def test_ip_geocoded_find_within
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find_within(3.97, :origin => LOCATION_A_IP)
+ assert_equal 5, locations.size
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.count_within(3.97, :origin => LOCATION_A_IP)
+ assert_equal 5, locations
+ end
+
+ def test_ip_geocoded_find_with_compound_condition
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations.size
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.count(:origin => LOCATION_A_IP, :conditions => "distance < 5 and city = 'Coppell'")
+ assert_equal 2, locations
+ end
+
+ def test_ip_geocoded_find_with_secure_compound_condition
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find(:all, :origin => LOCATION_A_IP, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
+ assert_equal 2, locations.size
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.count(:origin => LOCATION_A_IP, :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
+ assert_equal 2, locations
+ end
+
+ def test_ip_geocoded_find_beyond
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.find_beyond(3.95, :origin => LOCATION_A_IP)
+ assert_equal 1, locations.size
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ locations = Location.count_beyond(3.95, :origin => LOCATION_A_IP)
+ assert_equal 1, locations
+ end
+
+ def test_ip_geocoded_find_nearest
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ assert_equal @loc_a, Location.find_nearest(:origin => LOCATION_A_IP)
+ end
+
+ def test_ip_geocoded_find_farthest
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with(LOCATION_A_IP).returns(@location_a)
+ assert_equal @loc_e, Location.find_farthest(:origin => LOCATION_A_IP)
+ end
+
+ def test_ip_geocoder_exception
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with('127.0.0.1').returns(GeoKit::GeoLoc.new)
+ assert_raises GeoKit::Geocoders::GeocodeError do
+ Location.find_farthest(:origin => '127.0.0.1')
+ end
+ end
+
+ def test_address_geocode
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with('Irving, TX').returns(@location_a)
+ locations = Location.find(:all, :origin => 'Irving, TX', :conditions => ["distance < ? and city = ?", 5, 'Coppell'])
+ assert_equal 2, locations.size
+ end
+
+ def test_find_with_custom_distance_condition
+ locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => "dist < 3.97")
+ assert_equal 5, locations.size
+ locations = CustomLocation.count(:origin => @loc_a, :conditions => "dist < 3.97")
+ assert_equal 5, locations
+ end
+
+ def test_find_with_custom_distance_condition_using_custom_origin
+ locations = CustomLocation.find(:all, :origin => @custom_loc_a, :conditions => "dist < 3.97")
+ assert_equal 5, locations.size
+ locations = CustomLocation.count(:origin => @custom_loc_a, :conditions => "dist < 3.97")
+ assert_equal 5, locations
+ end
+
+ def test_find_within_with_custom
+ locations = CustomLocation.find_within(3.97, :origin => @loc_a)
+ assert_equal 5, locations.size
+ locations = CustomLocation.count_within(3.97, :origin => @loc_a)
+ assert_equal 5, locations
+ end
+
+ def test_find_within_with_coordinates_with_custom
+ locations = CustomLocation.find_within(3.97, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 5, locations.size
+ locations = CustomLocation.count_within(3.97, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 5, locations
+ end
+
+ def test_find_with_compound_condition_with_custom
+ locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => "dist < 5 and city = 'Coppell'")
+ assert_equal 1, locations.size
+ locations = CustomLocation.count(:origin => @loc_a, :conditions => "dist < 5 and city = 'Coppell'")
+ assert_equal 1, locations
+ end
+
+ def test_find_with_secure_compound_condition_with_custom
+ locations = CustomLocation.find(:all, :origin => @loc_a, :conditions => ["dist < ? and city = ?", 5, 'Coppell'])
+ assert_equal 1, locations.size
+ locations = CustomLocation.count(:origin => @loc_a, :conditions => ["dist < ? and city = ?", 5, 'Coppell'])
+ assert_equal 1, locations
+ end
+
+ def test_find_beyond_with_custom
+ locations = CustomLocation.find_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations.size
+ locations = CustomLocation.count_beyond(3.95, :origin => @loc_a)
+ assert_equal 1, locations
+ end
+
+ def test_find_beyond_with_coordinates_with_custom
+ locations = CustomLocation.find_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 1, locations.size
+ locations = CustomLocation.count_beyond(3.95, :origin =>[@loc_a.lat, @loc_a.lng])
+ assert_equal 1, locations
+ end
+
+ def test_find_nearest_with_custom
+ assert_equal @custom_loc_a, CustomLocation.find_nearest(:origin => @loc_a)
+ end
+
+ def test_find_nearest_with_coordinates_with_custom
+ assert_equal @custom_loc_a, CustomLocation.find_nearest(:origin =>[@loc_a.lat, @loc_a.lng])
+ end
+
+ def test_find_farthest_with_custom
+ assert_equal @custom_loc_e, CustomLocation.find_farthest(:origin => @loc_a)
+ end
+
+ def test_find_farthest_with_coordinates_with_custom
+ assert_equal @custom_loc_e, CustomLocation.find_farthest(:origin =>[@loc_a.lat, @loc_a.lng])
+ end
+
+ def test_find_with_array_origin
+ locations = Location.find(:all, :origin =>[@loc_a.lat,@loc_a.lng], :conditions => "distance < 3.97")
+ assert_equal 5, locations.size
+ locations = Location.count(:origin =>[@loc_a.lat,@loc_a.lng], :conditions => "distance < 3.97")
+ assert_equal 5, locations
+ end
+
+
+ # Bounding box tests
+
+ def test_find_within_bounds
+ locations = Location.find_within_bounds([@sw,@ne])
+ assert_equal 2, locations.size
+ locations = Location.count_within_bounds([@sw,@ne])
+ assert_equal 2, locations
+ end
+
+ def test_find_within_bounds_ordered_by_distance
+ locations = Location.find_within_bounds([@sw,@ne], :origin=>@bounds_center, :order=>'distance asc')
+ assert_equal locations[0], locations(:d)
+ assert_equal locations[1], locations(:a)
+ end
+
+ def test_find_within_bounds_with_token
+ locations = Location.find(:all, :bounds=>[@sw,@ne])
+ assert_equal 2, locations.size
+ locations = Location.count(:bounds=>[@sw,@ne])
+ assert_equal 2, locations
+ end
+
+ def test_find_within_bounds_with_string_conditions
+ locations = Location.find(:all, :bounds=>[@sw,@ne], :conditions=>"id !=#{locations(:a).id}")
+ assert_equal 1, locations.size
+ end
+
+ def test_find_within_bounds_with_array_conditions
+ locations = Location.find(:all, :bounds=>[@sw,@ne], :conditions=>["id != ?", locations(:a).id])
+ assert_equal 1, locations.size
+ end
+
+ def test_find_within_bounds_with_hash_conditions
+ locations = Location.find(:all, :bounds=>[@sw,@ne], :conditions=>{:id => locations(:a).id})
+ assert_equal 1, locations.size
+ end
+
+ def test_auto_geocode
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("Irving, TX").returns(@location_a)
+ store=Store.new(:address=>'Irving, TX')
+ store.save
+ assert_equal store.lat,@location_a.lat
+ assert_equal store.lng,@location_a.lng
+ assert_equal 0, store.errors.size
+ end
+
+ def test_auto_geocode_failure
+ GeoKit::Geocoders::MultiGeocoder.expects(:geocode).with("BOGUS").returns(GeoKit::GeoLoc.new)
+ store=Store.new(:address=>'BOGUS')
+ store.save
+ assert store.new_record?
+ assert_equal 1, store.errors.size
+ end
+
+ # Test :through
+
+ def test_find_with_through
+ organizations = MockOrganization.find(:all, :origin => @location_a, :order => 'distance ASC')
+ assert_equal 2, organizations.size
+ organizations = MockOrganization.count(:origin => @location_a, :conditions => "distance < 3.97")
+ assert_equal 1, organizations
+ end
+
+ def test_find_with_through_with_hash
+ people = MockPerson.find(:all, :origin => @location_a, :order => 'distance ASC')
+ assert_equal 2, people.size
+ people = MockPerson.count(:origin => @location_a, :conditions => "distance < 3.97")
+ assert_equal 2, people
+ end
+end
View
25 vendor/plugins/geokit-rails/test/boot.rb
@@ -0,0 +1,25 @@
+require 'active_support'
+require 'active_support/test_case'
+
+require 'test/unit'
+require 'test/unit/testcase'
+require 'active_support/testing/setup_and_teardown'
+
+require 'active_record'
+require 'active_record/fixtures'
+
+require 'action_controller'
+require 'action_controller/test_process'
+
+PLUGIN_ROOT = File.join(File.dirname(__FILE__), '..')
+ADAPTER = ENV['DB'] || 'mysql'
+
+$LOAD_PATH << File.join(PLUGIN_ROOT, 'lib') << File.join(PLUGIN_ROOT, 'test', 'models')
+
+FIXTURES_PATH = File.join(PLUGIN_ROOT, 'test', 'fixtures')
+ActiveRecord::Base.configurations = config = YAML::load(IO.read(File.join(PLUGIN_ROOT, 'test', 'database.yml')))
+ActiveRecord::Base.logger = Logger.new(File.join(PLUGIN_ROOT, 'test', "#{ADAPTER}-debug.log"))
+ActiveRecord::Base.establish_connection(config[ADAPTER])
+
+ActiveRecord::Migration.verbose = false
+load File.join(PLUGIN_ROOT, 'test', 'schema.rb')
View
20 vendor/plugins/geokit-rails/test/database.yml
@@ -0,0 +1,20 @@
+baes: &base
+ host: localhost
+ username: root
+ password:
+
+mysql:
+ adapter: mysql
+ database: geokit_plugin_test
+ <<: *base
+
+postgresql:
+ adapter: postgresql
+ database: geokit_plugin_test
+ <<: *base
+
+sqlserver:
+ adapter: sqlserver
+ mode: ODBC
+ dsn: geokit_tests
+ username: ruby
View
7 vendor/plugins/geokit-rails/test/fixtures/companies.yml
@@ -0,0 +1,7 @@
+starbucks:
+ id: 1
+ name: Starbucks
+
+barnes_and_noble:
+ id: 2
+ name: Barnes & Noble
View
54 vendor/plugins/geokit-rails/test/fixtures/custom_locations.yml
@@ -0,0 +1,54 @@
+a:
+ id: 1
+ company_id: 1
+ street: 7979 N MacArthur Blvd
+ city: Irving
+ state: TX
+ postal_code: 75063
+ latitude: 32.918593
+ longitude: -96.958444
+b:
+ id: 2
+ company_id: 1
+ street: 7750 N Macarthur Blvd # 160