cb_mongoid_spacial 0.2.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.document +5 -0
  2. data/.gitignore +49 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +180 -0
  7. data/Rakefile +18 -0
  8. data/lib/mongoid_spacial.rb +11 -0
  9. data/lib/mongoid_spacial/contexts/mongo.rb +115 -0
  10. data/lib/mongoid_spacial/criteria.rb +5 -0
  11. data/lib/mongoid_spacial/criterion.rb +3 -0
  12. data/lib/mongoid_spacial/criterion/complex.rb +19 -0
  13. data/lib/mongoid_spacial/criterion/inclusion.rb +14 -0
  14. data/lib/mongoid_spacial/criterion/near_spacial.rb +50 -0
  15. data/lib/mongoid_spacial/criterion/within_spacial.rb +60 -0
  16. data/lib/mongoid_spacial/extentions/hash/criteria_helpers.rb +22 -0
  17. data/lib/mongoid_spacial/extentions/symbol/inflections.rb +46 -0
  18. data/lib/mongoid_spacial/field_option.rb +38 -0
  19. data/lib/mongoid_spacial/finders.rb +5 -0
  20. data/lib/mongoid_spacial/spacial.rb +56 -0
  21. data/lib/mongoid_spacial/spacial/core_ext.rb +27 -0
  22. data/lib/mongoid_spacial/spacial/document.rb +29 -0
  23. data/lib/mongoid_spacial/spacial/formulas.rb +52 -0
  24. data/lib/mongoid_spacial/spacial/geo_near_results.rb +140 -0
  25. data/lib/mongoid_spacial/spacial/version.rb +5 -0
  26. data/mongoid_spacial.gemspec +27 -0
  27. data/spec/config/mongod.conf +3 -0
  28. data/spec/config/mongoid.yml +18 -0
  29. data/spec/functional/mongoid/contexts/mongo_spec.rb +125 -0
  30. data/spec/functional/mongoid/criterion/inclusion_spec.rb +356 -0
  31. data/spec/functional/mongoid/spacial/geo_near_results_spec.rb +73 -0
  32. data/spec/functional/mongoid/spacial_spec.rb +18 -0
  33. data/spec/models/account.rb +19 -0
  34. data/spec/models/acolyte.rb +9 -0
  35. data/spec/models/address.rb +62 -0
  36. data/spec/models/address_component.rb +5 -0
  37. data/spec/models/agent.rb +10 -0
  38. data/spec/models/alert.rb +5 -0
  39. data/spec/models/animal.rb +21 -0
  40. data/spec/models/answer.rb +4 -0
  41. data/spec/models/bar.rb +9 -0
  42. data/spec/models/birthday.rb +13 -0
  43. data/spec/models/book.rb +5 -0
  44. data/spec/models/business.rb +7 -0
  45. data/spec/models/callbacks.rb +57 -0
  46. data/spec/models/category.rb +13 -0
  47. data/spec/models/circus.rb +7 -0
  48. data/spec/models/comment.rb +13 -0
  49. data/spec/models/country_code.rb +6 -0
  50. data/spec/models/description.rb +11 -0
  51. data/spec/models/division.rb +5 -0
  52. data/spec/models/drug.rb +5 -0
  53. data/spec/models/employer.rb +5 -0
  54. data/spec/models/entry.rb +6 -0
  55. data/spec/models/event.rb +20 -0
  56. data/spec/models/favorite.rb +6 -0
  57. data/spec/models/fruits.rb +11 -0
  58. data/spec/models/game.rb +18 -0
  59. data/spec/models/ghost.rb +7 -0
  60. data/spec/models/house.rb +4 -0
  61. data/spec/models/inheritance.rb +90 -0
  62. data/spec/models/league.rb +5 -0
  63. data/spec/models/location.rb +5 -0
  64. data/spec/models/login.rb +6 -0
  65. data/spec/models/membership.rb +4 -0
  66. data/spec/models/mixed_drink.rb +4 -0
  67. data/spec/models/name.rb +13 -0
  68. data/spec/models/namespacing.rb +11 -0
  69. data/spec/models/observed.rb +41 -0
  70. data/spec/models/override.rb +16 -0
  71. data/spec/models/owner.rb +6 -0
  72. data/spec/models/page.rb +5 -0
  73. data/spec/models/page_question.rb +4 -0
  74. data/spec/models/paranoid_post.rb +18 -0
  75. data/spec/models/parents.rb +32 -0
  76. data/spec/models/patient.rb +15 -0
  77. data/spec/models/person.rb +146 -0
  78. data/spec/models/pet.rb +7 -0
  79. data/spec/models/pet_owner.rb +6 -0
  80. data/spec/models/phone.rb +7 -0
  81. data/spec/models/player.rb +23 -0
  82. data/spec/models/post.rb +26 -0
  83. data/spec/models/preference.rb +9 -0
  84. data/spec/models/question.rb +8 -0
  85. data/spec/models/quiz.rb +6 -0
  86. data/spec/models/rating.rb +8 -0
  87. data/spec/models/river.rb +20 -0
  88. data/spec/models/role.rb +5 -0
  89. data/spec/models/service.rb +6 -0
  90. data/spec/models/shelf.rb +5 -0
  91. data/spec/models/slave_address_numbers.rb +14 -0
  92. data/spec/models/survey.rb +5 -0
  93. data/spec/models/tag.rb +6 -0
  94. data/spec/models/tracking_id_validation_history.rb +25 -0
  95. data/spec/models/translation.rb +5 -0
  96. data/spec/models/tree.rb +9 -0
  97. data/spec/models/user.rb +9 -0
  98. data/spec/models/user_account.rb +10 -0
  99. data/spec/models/vet_visit.rb +5 -0
  100. data/spec/models/video.rb +9 -0
  101. data/spec/models/wiki_page.rb +6 -0
  102. data/spec/spec_helper.rb +43 -0
  103. data/spec/support/authentication.rb +29 -0
  104. data/spec/unit/mongoid/criterion/complex_spec.rb +15 -0
  105. data/spec/unit/mongoid/criterion/inclusion_spec.rb +0 -0
  106. data/spec/unit/mongoid/criterion/near_spacial_spec.rb +39 -0
  107. data/spec/unit/mongoid/criterion/within_spacial_spec.rb +52 -0
  108. data/spec/unit/mongoid/spacial/formulas_spec.rb +37 -0
  109. data/spec/unit/mongoid/spacial_spec.rb +6 -0
  110. metadata +374 -0
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+ module Inclusion
5
+ def near(attributes = {})
6
+ update_selector(attributes, "$near")
7
+ end
8
+
9
+ def near_sphere(attributes = {})
10
+ update_selector(attributes, "$near")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+
5
+ # NearSpecial criterion is used when performing #near with symbols to get
6
+ # get a shorthand syntax for where clauses.
7
+ #
8
+ # @example Coninputersion of a simple to complex criterion.
9
+ # { :field => { "$nearSphere" => => [20,30]}, '$maxDistance' => 5 }
10
+ # becomes:
11
+ # { :field.near(:sphere) => {:point => [20,30], :max => 5, :unit => :km} }
12
+ class NearSpacial < Complex
13
+
14
+ # Coninputert input to query for near or nearSphere
15
+ #
16
+ # @example
17
+ # near = NearSpacial.new(:key => :field, :operator => "near")
18
+ # near.to_mongo_query({:point => [:50,50], :max => 5, :unit => :km}) => { '$near : [50,50]' , '$maxDistance' : 5 }
19
+ #
20
+ # @param [Hash,Array] input input to coninputer to query
21
+ def to_mongo_query(input)
22
+ if input.kind_of?(Hash)
23
+ raise ':point required to make valid query' unless input[:point]
24
+ input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
25
+ query = {"$#{operator}" => input[:point] }
26
+ if input[:max]
27
+ query['$maxDistance'] = input[:max].to_f
28
+
29
+ if unit = Mongoid::Spacial.earth_radius[input[:unit]]
30
+ unit *= Mongoid::Spacial::RAD_PER_DEG unless operator =~ /sphere/i
31
+ input[:unit] = unit
32
+ end
33
+
34
+ query['$maxDistance'] = query['$maxDistance']/input[:unit].to_f if input[:unit]
35
+ end
36
+ query
37
+ elsif input.kind_of? Array
38
+ if input.first.kind_of? Numeric
39
+ {"$#{operator}" => input }
40
+ else
41
+ input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
42
+ {"$#{operator}" => input[0], '$maxDistance' => input[1] }
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Criterion #:nodoc:
4
+
5
+ # WithinSpecial criterion is used when performing #within with symbols to get
6
+ # get a shorthand syntax for where clauses.
7
+ #
8
+ # @example Conversion of a simple to complex criterion.
9
+ # { :field => { "$within" => {'$center' => [20,30]} } }
10
+ # becomes:
11
+ # { :field.within(:center) => [20,30] }
12
+ class WithinSpacial < Complex
13
+
14
+ # Convert input to query for box, polygon, center, and centerSphere
15
+ #
16
+ # @example
17
+ # within = WithinSpacial.new(opts[:key] => 'point', :operator => 'center')
18
+ # within.to_mongo_query({:point => [20,30], :max => 5, :unit => :km}) #=>
19
+ #
20
+ # @param [Hash,Array] input Variable to conver to query
21
+ def to_mongo_query(input)
22
+ if ['box','polygon'].index(@operator)
23
+ input = input.values if input.kind_of?(Hash)
24
+ if input.respond_to?(:map)
25
+ input.map!{ |v| (v.respond_to?(:to_lng_lat)) ? v.to_lng_lat : v }
26
+ else
27
+ input
28
+ end
29
+ elsif ['center','centerSphere'].index(@operator)
30
+
31
+ if input.kind_of?(Hash) || input.kind_of?(ActiveSupport::OrderedHash)
32
+ raise ':point required to make valid query' unless input[:point]
33
+ input[:point] = input[:point].to_lng_lat if input[:point].respond_to?(:to_lng_lat)
34
+ if input[:max]
35
+ input[:max] = input[:max].to_f
36
+
37
+ if unit = Mongoid::Spacial.earth_radius[input[:unit]]
38
+ unit *= Mongoid::Spacial::RAD_PER_DEG unless operator =~ /sphere/i
39
+ input[:unit] = unit
40
+ end
41
+
42
+ input[:max] = input[:max]/input[:unit].to_f if input[:unit]
43
+
44
+ input = [input[:point],input[:max]]
45
+ else
46
+ input = input[:point]
47
+ end
48
+ end
49
+
50
+ if input.kind_of? Array
51
+ input[0] = input[0].to_lng_lat if input[0].respond_to?(:to_lng_lat)
52
+ end
53
+
54
+ end
55
+ {'$within' => {"$#{@operator}"=>input} }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Hash #:nodoc:
5
+ module CriteriaHelpers #:nodoc:
6
+ def expand_complex_criteria
7
+ hsh = {}
8
+ each_pair do |k,v|
9
+ if k.respond_to?(:key) && k.respond_to?(:to_mongo_query)
10
+ hsh[k.key] ||= {}
11
+ hsh[k.key].merge!(k.to_mongo_query(v))
12
+ else
13
+ hsh[k] = v
14
+ end
15
+ end
16
+ hsh
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Extensions #:nodoc:
4
+ module Symbol #:nodoc:
5
+ module Inflections #:nodoc:
6
+
7
+ # return a class that will accept a value to convert the query correctly for near
8
+ #
9
+ # @param [Symbol] calc This accepts :sphere
10
+ #
11
+ # @return [Criterion::NearSpacial]
12
+
13
+ def near(calc = :flat)
14
+ Criterion::NearSpacial.new(:operator => get_op('near',calc), :key => self)
15
+ end
16
+
17
+ # alias for self.near(:sphere)
18
+ #
19
+ # @return [Criterion::NearSpacial]
20
+ def near_sphere
21
+ self.near(:sphere)
22
+ end
23
+
24
+ # @param [Symbol] shape :box,:polygon,:center,:center_sphere
25
+ #
26
+ # @return [Criterion::WithinSpacial]
27
+ def within(shape)
28
+ shape = get_op(:center,:sphere) if shape == :center_sphere
29
+ Criterion::WithinSpacial.new(:operator => shape.to_s , :key => self)
30
+ end
31
+
32
+ private
33
+
34
+ def get_op operator, calc
35
+ if calc.to_sym == :sphere && Mongoid.master.connection.server_version >= '1.7'
36
+ "#{operator}Sphere"
37
+ elsif calc.to_sym == :sphere
38
+ raise "MongoDB Server version #{Mongoid.master.connection.server_version} does not have Spherical Calculation"
39
+ else
40
+ operator.to_s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ require 'ostruct'
2
+ # Field changes to Fields from mongoid 2.0 to mongoid 2.1
3
+ field = (defined?(Mongoid::Field)) ? Mongoid::Field : Mongoid::Fields
4
+
5
+ field.option :spacial do |model,field,options|
6
+ options = {} unless options.kind_of?(Hash)
7
+ lat_meth = options[:lat] || :lat
8
+ lng_meth = options[:lng] || :lng
9
+ model.class_eval do
10
+ self.spacial_fields ||= []
11
+ self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
12
+
13
+ define_method "distance_from_#{field.name}" do |*args|
14
+ self.distance_from(field.name, *args)
15
+ end
16
+
17
+ define_method field.name do
18
+ output = self[field.name] || [nil,nil]
19
+ output = {lng_meth => output[0], lat_meth => output[1]} unless options[:return_array]
20
+ return options[:class].new(output) if options[:class]
21
+ output
22
+ end
23
+
24
+ define_method "#{field.name}=" do |arg|
25
+ if arg.kind_of?(Hash) && arg[lng_meth] && arg[lat_meth]
26
+ arg = [arg[lng_meth].to_f, arg[lat_meth].to_f]
27
+ elsif arg.respond_to?(:to_lng_lat)
28
+ arg = arg.to_lng_lat
29
+ end
30
+ self[field.name]=arg
31
+ arg = [nil,nil] if arg.nil?
32
+ return arg[0..1] if options[:return_array]
33
+ h = {lng_meth => arg[0], lat_meth => arg[1]}
34
+ return h if options[:class].blank?
35
+ options[:class].new(h)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ module Mongoid #:nodoc:
2
+ module Finders
3
+ delegate :geo_near, :to => :criteria
4
+ end
5
+ end
@@ -0,0 +1,56 @@
1
+ require 'mongoid_spacial/spacial/core_ext'
2
+ require 'mongoid_spacial/spacial/formulas'
3
+ require 'mongoid_spacial/spacial/document'
4
+ require 'mongoid_spacial/spacial/geo_near_results'
5
+ module Mongoid
6
+ module Spacial
7
+
8
+ EARTH_RADIUS_KM = 6371 # taken directly from mongodb
9
+
10
+ EARTH_RADIUS = {
11
+ :km => EARTH_RADIUS_KM,
12
+ :m => EARTH_RADIUS_KM*1000,
13
+ :mi => EARTH_RADIUS_KM*0.621371192, # taken directly from mongodb
14
+ :ft => EARTH_RADIUS_KM*5280*0.621371192,
15
+ }
16
+
17
+ RAD_PER_DEG = Math::PI/180
18
+
19
+ LNG_SYMBOLS = [:x, :lon, :long, :lng, :longitude]
20
+ LAT_SYMBOLS = [:y, :lat, :latitude]
21
+
22
+ def self.distance(p1,p2,opts = {})
23
+ opts[:formula] ||= (opts[:spherical]) ? @@spherical_distance_formula : :pythagorean_theorem
24
+ p1 = p1.to_lng_lat if p1.respond_to?(:to_lng_lat)
25
+ p2 = p2.to_lng_lat if p2.respond_to?(:to_lng_lat)
26
+
27
+ rads = Formulas.send(opts[:formula], p1, p2)
28
+
29
+ if unit = earth_radius[opts[:unit]]
30
+ opts[:unit] = (rads.instance_variable_get("@radian")) ? unit : unit * RAD_PER_DEG
31
+ end
32
+
33
+ rads *= opts[:unit].to_f if opts[:unit]
34
+ rads
35
+ end
36
+
37
+ mattr_accessor :lng_symbols
38
+ @@lng_symbols = LNG_SYMBOLS.dup
39
+
40
+ mattr_accessor :lat_symbols
41
+ @@lat_symbols = LAT_SYMBOLS.dup
42
+
43
+ mattr_accessor :earth_radius
44
+ @@earth_radius = EARTH_RADIUS.dup
45
+
46
+ mattr_accessor :paginator
47
+ @@paginator = :array
48
+
49
+ mattr_accessor :default_per_page
50
+ @@default_per_page = 25
51
+
52
+ mattr_accessor :spherical_distance_formula
53
+ @@spherical_distance_formula = :n_vector
54
+
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ class Array
2
+ def to_lng_lat
3
+ self[0..1].map(&:to_f)
4
+ end
5
+ end
6
+
7
+ class Hash
8
+ def to_lng_lat
9
+ raise "Hash must have at least 2 items" if self.size < 2
10
+ [to_lng, to_lat]
11
+ end
12
+
13
+ def to_lat
14
+ v = (Mongoid::Spacial.lat_symbols & self.keys).first
15
+ return self[v].to_f if !v.nil? && self[v]
16
+ raise "Hash must contain #{Mongoid::Spacial.lat_symbols.inspect} if ruby version is less than 1.9" if RUBY_VERSION.to_f < 1.9
17
+ raise "Hash cannot contain #{Mongoid::Spacial.lng_symbols.inspect} as the second item if there is no #{Mongoid::Spacial.lat_symbols.inspect}" if Mongoid::Spacial.lng_symbols.index(self.keys[1])
18
+ self.values[1].to_f
19
+ end
20
+
21
+ def to_lng
22
+ v = (Mongoid::Spacial.lng_symbols & self.keys).first
23
+ return self[v].to_f if !v.nil? && self[v]
24
+ raise "Hash cannot contain #{Mongoid::Spacial.lat_symbols.inspect} as the first item if there is no #{Mongoid::Spacial.lng_symbols.inspect}" if Mongoid::Spacial.lat_symbols.index(self.keys[0])
25
+ self.values[0].to_f
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module Mongoid
2
+ module Spacial
3
+ module Document
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :geo
8
+ cattr_accessor :spacial_fields, :spacial_fields_indexed
9
+ @@spacial_fields = []
10
+ @@spacial_fields_indexed = []
11
+ end
12
+
13
+ module ClassMethods #:nodoc:
14
+ # create spacial index for given field
15
+ # @param [String,Symbol] name
16
+ # @param [Hash] options options for spacial_index
17
+ def spacial_index name, *options
18
+ self.spacial_fields_indexed << name
19
+ index [[ name, Mongo::GEO2D ]], *options
20
+ end
21
+ end
22
+
23
+ def distance_from(key,p2, opts = {})
24
+ p1 = self.send(key)
25
+ Mongoid::Spacial.distance(p1, p2, opts)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ module Mongoid
2
+ module Spacial
3
+ module Formulas
4
+ class << self
5
+ def n_vector(point1,point2)
6
+ p1 = point1.map{|deg| deg * RAD_PER_DEG}
7
+ p2 = point2.map{|deg| deg * RAD_PER_DEG}
8
+
9
+ sin_x1 = Math.sin(p1[0])
10
+ cos_x1 = Math.cos(p1[0])
11
+
12
+ sin_y1 = Math.sin(p1[1])
13
+ cos_y1 = Math.cos(p1[1])
14
+
15
+ sin_x2 = Math.sin(p2[0])
16
+ cos_x2 = Math.cos(p2[0])
17
+
18
+ sin_y2 = Math.sin(p2[1])
19
+ cos_y2 = Math.cos(p2[1])
20
+
21
+ cross_prod = (cos_y1*cos_x1 * cos_y2*cos_x2) +
22
+ (cos_y1*sin_x1 * cos_y2*sin_x2) +
23
+ (sin_y1 * sin_y2)
24
+
25
+ return cross_prod > 0 ? 0 : Math::PI if (cross_prod >= 1 || cross_prod <= -1)
26
+
27
+ d = Math.acos(cross_prod)
28
+ d.instance_variable_set("@radian", true)
29
+ d
30
+ end
31
+
32
+ def haversine(point1,point2)
33
+ p1 = point1.map{|deg| deg * RAD_PER_DEG}
34
+ p2 = point2.map{|deg| deg * RAD_PER_DEG}
35
+
36
+ dlon = p2[0] - p1[0]
37
+ dlat = p2[1] - p1[1]
38
+
39
+ a = (Math.sin(dlat/2))**2 + Math.cos(p1[1]) * Math.cos(p2[1]) * (Math.sin(dlon/2))**2
40
+
41
+ d = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
42
+ d.instance_variable_set("@radian", true)
43
+ d
44
+ end
45
+
46
+ def pythagorean_theorem(p1, p2)
47
+ Math.sqrt(((p2[0] - p1[0]) ** 2) + ((p2[1] - p1[1]) ** 2))
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,140 @@
1
+ module Mongoid
2
+ module Spacial
3
+ class GeoNearResults < Array
4
+ attr_reader :stats, :document, :_original_array, :_original_opts
5
+ attr_accessor :opts
6
+
7
+ def initialize(document,results,opts = {})
8
+ raise "#{document.name} class must include Mongoid::Spacial::Document" unless document.respond_to?(:spacial_fields_indexed)
9
+ @document = document
10
+ @opts = opts
11
+ @_original_opts = opts.clone
12
+ @stats = results['stats'] || {}
13
+ @opts[:skip] ||= 0
14
+
15
+ @_original_array = results['results'].collect do |result|
16
+ res = Mongoid::Factory.from_db(@document, result.delete('obj'))
17
+ res.geo = {}
18
+ # camel case is awkward in ruby when using variables...
19
+ if result['dis']
20
+ res.geo[:distance] = result.delete('dis').to_f
21
+ end
22
+ result.each do |key,value|
23
+ res.geo[key.snakecase.to_sym] = value
24
+ end
25
+ # dist_options[:formula] = opts[:formula] if opts[:formula]
26
+ @opts[:calculate] = @document.spacial_fields_indexed if @document.spacial_fields_indexed.kind_of?(Array) && @opts[:calculate] == true
27
+ if @opts[:calculate]
28
+ @opts[:calculate] = [@opts[:calculate]] unless @opts[:calculate].kind_of? Array
29
+ @opts[:calculate] = @opts[:calculate].map(&:to_sym) & geo_fields
30
+ if @document.spacial_fields_indexed.kind_of?(Array) && @document.spacial_fields_indexed.size == 1
31
+ primary = @document.spacial_fields_indexed.first
32
+ end
33
+ @opts[:calculate].each do |key|
34
+ res.geo[(key.to_s+'_distance').to_sym] = res.distance_from(key,center,{:unit =>@opts[:unit] || @opts[:distance_multiplier], :spherical => @opts[:spherical]} )
35
+ res.geo[:distance] = res.geo[key] if primary && key == primary
36
+ end
37
+ end
38
+ res
39
+ end
40
+ if @opts[:page]
41
+ start = (@opts[:page]-1)*@opts[:per_page] # assuming current_page is 1 based.
42
+ @_paginated_array = @_original_array.clone
43
+ super(@_paginated_array[@opts[:skip]+start, @opts[:per_page]] || [])
44
+ else
45
+ super(@_original_array[@opts[:skip]..-1] || [])
46
+ end
47
+ end
48
+
49
+ def page(*args)
50
+ new_collection = self.clone
51
+ new_collection.page!(*args)
52
+ new_collection
53
+ end
54
+
55
+ def page!(page, options = {})
56
+ original = options.delete(:original)
57
+ self.opts.merge!(options)
58
+ self.opts[:paginator] ||= Mongoid::Spacial.paginator
59
+ self.opts[:page] = page
60
+ start = (self.current_page-1)*self.limit_value # assuming current_page is 1 based.
61
+
62
+ if original
63
+ @_paginated_array = @_original_array.clone
64
+ self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value] || [])
65
+ else
66
+ @_paginated_array ||= self.to_a
67
+ self.replace(@_paginated_array[self.opts[:skip]+start, self.limit_value])
68
+ end
69
+ true
70
+ end
71
+
72
+ def per(num)
73
+ self.page(current_page, :per_page => num)
74
+ end
75
+
76
+ def reset!
77
+ self.replace(@_original_array)
78
+ @opts = @_original_opts
79
+ @_paginated_array = nil
80
+ true
81
+ end
82
+
83
+ def reset
84
+ clone = self.clone
85
+ clone.reset!
86
+ clone
87
+ end
88
+
89
+ def total_entries
90
+ (@_paginated_array) ? @_paginated_array.count : @_original_array.count
91
+ end
92
+ alias_method :total_count, :total_entries
93
+
94
+ def current_page
95
+ page = (@opts[:page]) ? @opts[:page].to_i.abs : 1
96
+ (page < 1) ? 1 : page
97
+ end
98
+
99
+ def limit_value
100
+ if @opts[:per_page]
101
+ @opts[:per_page] = @opts[:per_page].to_i.abs
102
+ else
103
+ @opts[:per_page] = case self.opts[:paginator]
104
+ when :will_paginate
105
+ @document.per_page
106
+ when :kaminari
107
+ Kaminari.config.default_per_page
108
+ else
109
+ Mongoid::Spacial.default_per_page
110
+ end
111
+ end
112
+ end
113
+ alias_method :per_page, :limit_value
114
+
115
+ def num_pages
116
+ (self.total_entries && @opts[:per_page]) ? self.total_entries/@opts[:per_page] : nil
117
+ end
118
+ alias_method :total_pages, :num_pages
119
+
120
+ def out_of_bounds?
121
+ self.current_page > self.total_pages
122
+ end
123
+
124
+ def offset
125
+ (self.current_page - 1) * self.per_page
126
+ end
127
+
128
+ # current_page - 1 or nil if there is no previous page
129
+ def previous_page
130
+ self.current_page > 1 ? (self.current_page - 1) : nil
131
+ end
132
+
133
+ # current_page + 1 or nil if there is no next page
134
+ def next_page
135
+ self.current_page < self.total_pages ? (self.current_page + 1) : nil
136
+ end
137
+
138
+ end
139
+ end
140
+ end