diff --git a/lib/mongoid_spacial/contexts/mongo.rb b/lib/mongoid_spacial/contexts/mongo.rb index c098157..f94d0ed 100644 --- a/lib/mongoid_spacial/contexts/mongo.rb +++ b/lib/mongoid_spacial/contexts/mongo.rb @@ -53,19 +53,9 @@ def geo_near(center, opts = {}) opts[:page] ||= 1 opts[:paginator] ||= Mongoid::Spacial.paginator() - if opts[:per_page].blank? - opts[:per_page] = case opts[:paginator] - when :will_paginate - @document.per_page - when :kaminari - Kaminari.config.default_per_page - else - Mongoid::Spacial.default_per_page - end - opts[:per_page] = opts[:per_page].to_i - end - + opts[:per_page] = default_per_page(opts[:paginator]) if opts[:per_page].blank? end + opts[:query] = create_geo_near_query(center,opts) results = klass.db.command(opts[:query]) Mongoid::Spacial::GeoNearResults.new(klass,results,opts) @@ -73,12 +63,23 @@ def geo_near(center, opts = {}) private + def default_per_page(paginator) + case paginator + when :will_paginate + @document.per_page + when :kaminari + Kaminari.config.default_per_page + else + Mongoid::Spacial.default_per_page + end.to_i + end + def create_geo_near_query(center,opts) + query = BSON::OrderedHash.new + # minimum query - query = { - :geoNear => klass.collection_name, - :near => center, - } + query[:geoNear] = klass.collection_name + query[:near] = center # create limit and use skip if opts[:num] diff --git a/lib/mongoid_spacial/field_option.rb b/lib/mongoid_spacial/field_option.rb index 062e640..5975082 100644 --- a/lib/mongoid_spacial/field_option.rb +++ b/lib/mongoid_spacial/field_option.rb @@ -14,8 +14,10 @@ self.distance_from(field.name, *args) end + __field = field + define_method field.name do - output = self[field.name] || [nil,nil] + output = self[__field.name] || [nil,nil] output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array] return options[:class].new(output) if options[:class] output @@ -27,7 +29,7 @@ elsif arg.respond_to?(:to_lng_lat) arg = arg.to_lng_lat end - self[field.name]=arg + self[__field.name]=arg arg = [nil,nil] if arg.nil? return arg[0..1] if options[:return_array] h = {lng_meth => arg[0], lat_meth => arg[1]} diff --git a/lib/mongoid_spacial/spacial/document.rb b/lib/mongoid_spacial/spacial/document.rb index 354491e..1b0d7eb 100644 --- a/lib/mongoid_spacial/spacial/document.rb +++ b/lib/mongoid_spacial/spacial/document.rb @@ -6,8 +6,8 @@ module Document included do attr_accessor :geo cattr_accessor :spacial_fields, :spacial_fields_indexed - @@spacial_fields = [] - @@spacial_fields_indexed = [] + self.spacial_fields = [] + self.spacial_fields_indexed = [] end module ClassMethods #:nodoc: diff --git a/lib/mongoid_spacial/spacial/geo_near_results.rb b/lib/mongoid_spacial/spacial/geo_near_results.rb index 722fec8..ca5bc25 100644 --- a/lib/mongoid_spacial/spacial/geo_near_results.rb +++ b/lib/mongoid_spacial/spacial/geo_near_results.rb @@ -12,31 +12,10 @@ def initialize(document,results,opts = {}) @stats = results['stats'] || {} @opts[:skip] ||= 0 - @_original_array = results['results'].collect do |result| - res = Mongoid::Factory.from_db(@document, result.delete('obj')) - res.geo = {} - # camel case is awkward in ruby when using variables... - if result['dis'] - res.geo[:distance] = result.delete('dis').to_f - end - result.each do |key,value| - res.geo[key.snakecase.to_sym] = value - end - # dist_options[:formula] = opts[:formula] if opts[:formula] - @opts[:calculate] = @document.spacial_fields_indexed if @document.spacial_fields_indexed.kind_of?(Array) && @opts[:calculate] == true - if @opts[:calculate] - @opts[:calculate] = [@opts[:calculate]] unless @opts[:calculate].kind_of? Array - @opts[:calculate] = @opts[:calculate].map(&:to_sym) & geo_fields - if @document.spacial_fields_indexed.kind_of?(Array) && @document.spacial_fields_indexed.size == 1 - primary = @document.spacial_fields_indexed.first - end - @opts[:calculate].each do |key| - res.geo[(key.to_s+'_distance').to_sym] = res.distance_from(key,center,{:unit =>@opts[:unit] || @opts[:distance_multiplier], :spherical => @opts[:spherical]} ) - res.geo[:distance] = res.geo[key] if primary && key == primary - end - end - res - end + prepare_distance_calculation + + @_original_array = results['results'].collect { |result| fabricate_result result } + if @opts[:page] start = (@opts[:page]-1)*@opts[:per_page] # assuming current_page is 1 based. @_paginated_array = @_original_array.clone @@ -64,7 +43,7 @@ def page!(page, options = {}) self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value] || []) else @_paginated_array ||= self.to_a - self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value]) + self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value] || []) end true end @@ -113,7 +92,8 @@ def limit_value alias_method :per_page, :limit_value def num_pages - (self.total_entries && @opts[:per_page]) ? self.total_entries/@opts[:per_page] : nil + return nil unless total_entries && @opts[:per_page] + return (total_entries.to_f / @opts[:per_page]).ceil end alias_method :total_pages, :num_pages @@ -135,6 +115,45 @@ def next_page self.current_page < self.total_pages ? (self.current_page + 1) : nil end + if RUBY_VERSION.to_f < 1.9 + def sort_by! + replace sort_by { |obj| yield obj } + end + end + + private + + def prepare_distance_calculation + @calculate_fields = @opts[:calculate] + @calculate_fields = @document.spacial_fields_indexed if @calculate_fields == true + + if @calculate_fields + @calculate_fields = [@calculate_fields] unless @calculate_fields.kind_of? Array + @calculate_fields = @calculate_fields.map(&:to_sym) & geo_fields + @calculate_primary = @document.spacial_fields_indexed.first if @document.spacial_fields_indexed.size == 1 + end + end + + def fabricate_result(geo_near_result) + result = Mongoid::Factory.from_db(@document, geo_near_result.delete('obj')) + result.geo = {} + + result.geo[:distance] = geo_near_result.delete('dis').to_f if geo_near_result['dis'] + + geo_near_result.each do |key, value| + result.geo[key.snakecase.to_sym] = value + end + + if @calculate_fields + distance_from_opts = { :unit => @opts[:unit] || @opts[:distance_multiplier], :spherical => @opts[:spherical] } + @calculate_fields.each do |key| + result.geo["#{key}_distance".to_sym] = result.distance_from(key, center, distance_from_opts) + result.geo[:distance] = result.geo["#{@calculate_primary}_distance".to_sym] if @calculate_primary + end + end + + return result + end end end end diff --git a/spec/models/river.rb b/spec/models/river.rb index d208571..edbe43b 100644 --- a/spec/models/river.rb +++ b/spec/models/river.rb @@ -2,13 +2,13 @@ class River include Mongoid::Document include Mongoid::Spacial::Document - field :name, type: String - field :length, type: Integer - field :average_discharge, type: Integer - field :source, type: Array, spacial: true + field :name, :type => String + field :length, :type => Integer + field :average_discharge, :type => Integer + field :source, :type => Array, :spacial => true # set return_array to true if you do not want a hash returned all the time - field :mouth, type: Array, spacial: {lat: 'latitude', lng: 'longitude'} - field :mouth_array, type: Array, spacial: {return_array: true} + field :mouth, :type => Array, :spacial => {:lat => 'latitude', :lng => 'longitude'} + field :mouth_array, :type => Array, :spacial => {:return_array => true} # simplified spacial indexing # you can only index one field in mongodb < 1.9 diff --git a/spec/unit/mongoid/criterion/within_spacial_spec.rb b/spec/unit/mongoid/criterion/within_spacial_spec.rb index bc00f8e..2eccee7 100644 --- a/spec/unit/mongoid/criterion/within_spacial_spec.rb +++ b/spec/unit/mongoid/criterion/within_spacial_spec.rb @@ -14,14 +14,14 @@ :box => { 'Array of Arrays' => [[10,20], [15,25]], - 'Array of Hashes' => [{ x: 10, y: 20 }, { x: 15, y: 25 }], - 'Hash of Hashes' => { a: { x: 10, y: 20 }, b: { x: 15, y: 25 }} + 'Array of Hashes' => [{ :x => 10, :y => 20 }, { :x => 15, :y => 25 }], + 'Hash of Hashes' => { :a => { :x => 10, :y => 20 }, :b => { :x => 15, :y => 25 }} }, :polygon => { 'Array of Arrays' => [[10,20], [15,25]], - 'Array of Hashes' => [{ x: 10, y: 20 }, { x: 15, y: 25 }], - 'Hash of Hashes' => { a: { x: 10, y: 20 }, b: { x: 15, y: 25 }} + 'Array of Hashes' => [{ :x => 10, :y => 20 }, { :x => 15, :y => 25 }], + 'Hash of Hashes' => { :a => { :x => 10, :y => 20 }, :b => { :x => 15, :y => 25 }} }, :center => {