From b25029376f6b175cf4d37ccc009369656eaca32a Mon Sep 17 00:00:00 2001
From: Steve Agalloco <steve.agalloco@gmail.com>
Date: Sun, 15 May 2011 22:15:03 -0400
Subject: [PATCH] added support for mongo_mapper

---
 lib/geocoder.rb                     |  1 +
 lib/geocoder/models/mongo_mapper.rb | 24 +++++++++
 lib/geocoder/models/mongodb_base.rb | 55 ++++++++++++++++++++
 lib/geocoder/models/mongoid.rb      | 27 +---------
 lib/geocoder/stores/mongo_mapper.rb | 79 +++++++++++++++++++++++++++++
 5 files changed, 161 insertions(+), 25 deletions(-)
 create mode 100644 lib/geocoder/models/mongo_mapper.rb
 create mode 100644 lib/geocoder/models/mongodb_base.rb
 create mode 100644 lib/geocoder/stores/mongo_mapper.rb

diff --git a/lib/geocoder.rb b/lib/geocoder.rb
index 928bc637..4e648617 100644
--- a/lib/geocoder.rb
+++ b/lib/geocoder.rb
@@ -4,6 +4,7 @@ require "geocoder/cache"
 require "geocoder/request"
 require "geocoder/models/active_record"
 require "geocoder/models/mongoid"
+require "geocoder/models/mongo_mapper"
 
 module Geocoder
   extend self
diff --git a/lib/geocoder/models/mongo_mapper.rb b/lib/geocoder/models/mongo_mapper.rb
new file mode 100644
index 00000000..89f1c7c7
--- /dev/null
+++ b/lib/geocoder/models/mongo_mapper.rb
@@ -0,0 +1,24 @@
+require 'geocoder/models/base'
+require 'geocoder/models/mongodb_base'
+
+module Geocoder
+  module Model
+    module MongoMapper
+      include Base
+      include MongoDBBase
+
+      def self.included(base); base.extend(self); end
+
+      private # --------------------------------------------------------------
+
+      def geocoder_file_name;   "mongo_mapper"; end
+      def geocoder_module_name; "MongoMapper"; end
+
+      def geocoder_init(options)
+        super(options)
+        ensure_index [[ geocoder_options[:coordinates], Mongo::GEO2D ]],
+          :min => -180, :max => 180 # create 2d index
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/models/mongodb_base.rb b/lib/geocoder/models/mongodb_base.rb
new file mode 100644
index 00000000..eee7ce4e
--- /dev/null
+++ b/lib/geocoder/models/mongodb_base.rb
@@ -0,0 +1,55 @@
+require 'geocoder'
+
+module Geocoder
+
+  ##
+  # Methods for invoking Geocoder in a model.
+  #
+  module Model
+    module MongoDBBase
+
+      ##
+      # Set attribute names and include the Geocoder module.
+      #
+      def geocoded_by(address_attr, options = {}, &block)
+        geocoder_init(
+          :geocode       => true,
+          :user_address  => address_attr,
+          :coordinates   => options[:coordinates] || :coordinates,
+          :geocode_block => block
+        )
+      end
+
+      ##
+      # Set attribute names and include the Geocoder module.
+      #
+      def reverse_geocoded_by(coordinates_attr, options = {}, &block)
+        geocoder_init(
+          :reverse_geocode => true,
+          :fetched_address => options[:address] || :address,
+          :coordinates     => coordinates_attr,
+          :reverse_block   => block
+        )
+      end
+
+      private # ----------------------------------------------------------------
+
+      def geocoder_init(options)
+        unless geocoder_initialized?
+          @geocoder_options = {}
+          require "geocoder/stores/#{geocoder_file_name}"
+          include eval("Geocoder::Store::" + geocoder_module_name)
+        end
+        @geocoder_options.merge! options
+      end
+
+      def geocoder_initialized?
+        begin
+          included_modules.include? eval("Geocoder::Store::" + geocoder_module_name)
+        rescue NameError
+          false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/models/mongoid.rb b/lib/geocoder/models/mongoid.rb
index a3d2b1ff..a6e40673 100644
--- a/lib/geocoder/models/mongoid.rb
+++ b/lib/geocoder/models/mongoid.rb
@@ -1,37 +1,14 @@
 require 'geocoder/models/base'
+require 'geocoder/models/mongodb_base'
 
 module Geocoder
   module Model
     module Mongoid
       include Base
+      include MongoDBBase
 
       def self.included(base); base.extend(self); end
 
-      ##
-      # Set attribute names and include the Geocoder module.
-      #
-      def geocoded_by(address_attr, options = {}, &block)
-        geocoder_init(
-          :geocode       => true,
-          :user_address  => address_attr,
-          :coordinates   => options[:coordinates] || :coordinates,
-          :geocode_block => block
-        )
-      end
-
-      ##
-      # Set attribute names and include the Geocoder module.
-      #
-      def reverse_geocoded_by(coordinates_attr, options = {}, &block)
-        geocoder_init(
-          :reverse_geocode => true,
-          :fetched_address => options[:address] || :address,
-          :coordinates     => coordinates_attr,
-          :reverse_block   => block
-        )
-      end
-
-
       private # --------------------------------------------------------------
 
       def geocoder_file_name;   "mongoid"; end
diff --git a/lib/geocoder/stores/mongo_mapper.rb b/lib/geocoder/stores/mongo_mapper.rb
new file mode 100644
index 00000000..0b1b44e6
--- /dev/null
+++ b/lib/geocoder/stores/mongo_mapper.rb
@@ -0,0 +1,79 @@
+require 'geocoder/stores/base'
+
+module Geocoder::Store
+  module MongoMapper
+    include Base
+
+    def self.included(base)
+      base.class_eval do
+
+        scope :geocoded, lambda {
+          where(geocoder_options[:coordinates].ne => nil)
+        }
+
+        scope :not_geocoded, lambda {
+          where(geocoder_options[:coordinates] => nil)
+        }
+
+        scope :near, lambda{ |location, *args|
+          coords  = Geocoder::Calculations.extract_coordinates(location)
+          radius  = args.size > 0 ? args.shift : 20
+          options = args.size > 0 ? args.shift : {}
+
+          # Use BSON::OrderedHash if Ruby's hashes are unordered.
+          # Conditions must be in order required by indexes (see mongo gem).
+          empty = RUBY_VERSION.split('.')[1].to_i < 9 ? BSON::OrderedHash.new : {}
+
+          conds = empty.clone
+          conds[:coordinates] = empty.clone
+          conds[:coordinates]["$nearSphere"]  = coords.reverse
+          conds[:coordinates]["$maxDistance"] = \
+            Geocoder::Calculations.distance_to_radians(radius, options[:units] || :mi)
+
+          if obj = options[:exclude]
+            conds[:_id.ne] = obj.id
+          end
+          where(conds)
+        }
+      end
+    end
+
+    ##
+    # Coordinates [lat,lon] of the object.
+    # This method always returns coordinates in lat,lon order,
+    # even though internally they are stored in the opposite order.
+    #
+    def to_coordinates
+      coords = send(self.class.geocoder_options[:coordinates])
+      coords.is_a?(Array) ? coords.reverse : []
+    end
+
+    ##
+    # Look up coordinates and assign to +latitude+ and +longitude+ attributes
+    # (or other as specified in +geocoded_by+). Returns coordinates (array).
+    #
+    def geocode
+      do_lookup(false) do |o,rs|
+        r = rs.first
+        unless r.coordinates.nil?
+          o.send :write_attribute, self.class.geocoder_options[:coordinates], r.coordinates.reverse
+        end
+        r.coordinates
+      end
+    end
+
+    ##
+    # Look up address and assign to +address+ attribute (or other as specified
+    # in +reverse_geocoded_by+). Returns address (string).
+    #
+    def reverse_geocode
+      do_lookup(true) do |o,rs|
+        r = rs.first
+        unless r.address.nil?
+          o.send :write_attribute, self.class.geocoder_options[:fetched_address], r.address
+        end
+        r.address
+      end
+    end
+  end
+end
-- 
GitLab