mongoid_spacial 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -24,6 +24,7 @@ group :test, :development do
24
24
  end
25
25
 
26
26
  group :test do
27
+ gem 'rspec-rails'
27
28
  gem 'mocha'
28
29
  gem 'will_paginate'
29
30
  gem 'kaminari'
data/README.md CHANGED
@@ -89,7 +89,6 @@ One of the most handy features we have added is geo_near finder
89
89
 
90
90
  ```ruby
91
91
  # accepts all criteria chains except without, only, asc, desc, order\_by
92
- # pagination does not work yet but will be added in the next version
93
92
  River.where(:name=>'hudson').geo_near({:lat => 40.73083, :lng => -73.99756})
94
93
 
95
94
  # geo\_near accepts a few parameters besides a point
@@ -99,13 +98,47 @@ River.where(:name=>'hudson').geo_near({:lat => 40.73083, :lng => -73.99756})
99
98
  # :max\_distance - Integer
100
99
  # :distance\_multiplier - Integer
101
100
  # :spherical - true - To enable spherical calculations
101
+ River.geo_near([-73.99756,40.73083], :max_distance => 4, :unit => :mi, :spherical => true)
102
+ ```
103
+
104
+ There are two types of pagination!
105
+
106
+ * MongoDB Pagination - Stores start of rows to page limit in memory
107
+ * Post Query Pagination - Stores all rows in memory
108
+
109
+ Post-Result is only minutely slower than MongoDB because MongoDB has to calculate distance for all of the rows anyway. The slow up is in the transfer of data from the database to ruby.
110
+
111
+ Post-Result has some advantages that are listed below.
112
+
113
+ ```ruby
114
+ # MongoDB pagination
115
+ # overwrites #skip chain method
102
116
  # :page - pagination will be enabled if set to any variable including nil, pagination will not be enabled if either :per\_page or :paginator is set
103
117
  # :per\_page
104
118
  # :paginator - Choose which paginator to use. [default :arrary]
105
119
  # Prefered method to set is Mongoid::Spacial.paginator=:array
106
120
  # Available Paginators [:kaminari, :will\_paginate, :array]
107
121
  # The only thing this does really is configure default per\_page so it is only kind of useful
108
- River.geo_near([-73.99756,40.73083], :max_distance => 4, :unit => :mi, :spherical => true, :page => 1)
122
+ River.geo_near([-73.99756,40.73083], :page => 1)
123
+ ```
124
+
125
+ ```ruby
126
+ # Post Query Pagination
127
+ # At carzen we use Post Query Pagination because we need to re-sort our rows after fetching. Pagination is not friendly with re-sorting.
128
+ # You can jump pages continously without querying the database again.
129
+ # listens to #limit/:num & #skip before geo\_near
130
+ # #page(page\_number, opts = {})
131
+ # opts:
132
+ # :per\_page
133
+ # :paginator
134
+ # #per(per\_page\_number, opts = {})
135
+ # opts:
136
+ # :page
137
+ # :paginator
138
+ #
139
+ # both return a GeoNearResults, which is really just a modified Array
140
+ # #per really just #page but just moves the options around
141
+ River.geo_near([-73.99756,40.73083]).per(25).page(1)
109
142
  ```
110
143
 
111
144
  Thanks
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -36,26 +36,31 @@ module Mongoid #:nodoc:
36
36
  center = center.to_lng_lat if center.respond_to?(:to_lng_lat)
37
37
 
38
38
  # set default opts
39
- if distance_multiplier = Mongoid::Spacial.earth_radius[opts.delete(:unit)]
40
- opts[:distance_multiplier] = distance_multiplier
39
+ opts[:skip] ||= 0
40
+
41
+ if unit = Mongoid::Spacial.earth_radius[opts[:unit]]
42
+ opts[:unit] = (opts[:spherical]) ? unit : unit * Mongoid::Spacial::RAD_PER_DEG
41
43
  end
44
+
45
+ opts[:distance_multiplier] = opts[:unit] if opts[:unit].kind_of?(Numeric)
42
46
 
43
47
  # setup paging.
44
48
  if opts.has_key?(:page)
45
49
  opts[:page] ||= 1
46
50
  opts[:paginator] ||= Mongoid::Spacial.paginator()
47
51
 
48
- if opts[:paginator] == :will_paginate
49
- opts[:per_page] ||= klass.per_page
50
- elsif opts[:paginator] == :kaminari
51
- opts[:per_page] ||= Kaminari.config.default_per_page
52
- else
53
- opts[:per_page] ||= Mongoid::Spacial.default_per_page
54
- end
52
+ opts[:per_page] ||= case opts[:paginator]
53
+ when :will_paginate
54
+ @document.per_page
55
+ when :kaminari
56
+ Kaminari.config.default_per_page
57
+ else
58
+ Mongoid::Spacial.default_per_page
59
+ end
55
60
  end
56
- query = create_geo_near_query(center,opts)
57
- results = klass.db.command(query)
58
- Mongoid::Spacial::GeoNear.new(klass,results,opts)
61
+ opts[:query] = create_geo_near_query(center,opts)
62
+ results = klass.db.command(opts[:query])
63
+ Mongoid::Spacial::GeoNearResults.new(klass,results,opts)
59
64
  end
60
65
 
61
66
  private
@@ -69,11 +74,11 @@ module Mongoid #:nodoc:
69
74
 
70
75
  # create limit and use skip
71
76
  if opts[:num]
72
- query['num'] = (opts[:skip] || 0) + opts[:num].to_i
77
+ query['num'] = opts[:skip] + opts[:num].to_i
73
78
  elsif opts[:limit]
74
- query['num'] = (opts[:skip] || 0) + opts[:limit]
79
+ query['num'] = opts[:skip] + opts[:limit]
75
80
  elsif opts[:page]
76
- query['num'] = (opts[:page] * opts[:per_page])
81
+ query['num'] = opts[:skip] +(opts[:page] * opts[:per_page])
77
82
  end
78
83
 
79
84
  # allow the use of complex werieis
@@ -85,7 +90,7 @@ module Mongoid #:nodoc:
85
90
 
86
91
  if opts[:max_distance]
87
92
  query['maxDistance'] = opts[:max_distance]
88
- query['maxDistance'] = query['maxDistance']/opts[:distance_multiplier] if opts[:distance_multiplier]
93
+ query['maxDistance'] = query['maxDistance']/opts[:unit] if opts[:unit]
89
94
  end
90
95
 
91
96
  if klass.db.connection.server_version >= '1.7'
@@ -1,8 +1,3 @@
1
1
  require 'mongoid_spacial/criterion/complex'
2
- # encoding: utf-8
3
- module Mongoid #:nodoc:
4
- module Criterion #:nodoc:
5
- autoload :NearSpacial, 'mongoid_spacial/criterion/near_spacial'
6
- autoload :WithinSpacial, 'mongoid_spacial/criterion/within_spacial'
7
- end
8
- end
2
+ require 'mongoid_spacial/criterion/near_spacial'
3
+ require 'mongoid_spacial/criterion/within_spacial'
@@ -4,8 +4,8 @@ field = (defined?(Mongoid::Field)) ? Mongoid::Field : Mongoid::Fields
4
4
 
5
5
  field.option :spacial do |model,field,options|
6
6
  options = {} unless options.kind_of?(Hash)
7
- lat_meth = options[:lat] || "lat"
8
- lng_meth = options[:lng] || "lng"
7
+ lat_meth = options[:lat] || :lat
8
+ lng_meth = options[:lng] || :lng
9
9
  model.class_eval do
10
10
  self.spacial_fields ||= []
11
11
  self.spacial_fields << field.name.to_sym if self.spacial_fields.kind_of? Array
@@ -1,7 +1,6 @@
1
1
  module Mongoid
2
2
  module Spacial
3
3
  module Formulas
4
- RAD_PER_DEG = Math::PI/180
5
4
 
6
5
  def self.n_vector(point1,point2)
7
6
  p1 = point1.map{|deg| deg * RAD_PER_DEG}
@@ -0,0 +1,123 @@
1
+ module Mongoid
2
+ module Spacial
3
+ class GeoNearResults < Array
4
+ attr_reader :stats, :document, :_original_array
5
+ attr_accessor :opts
6
+
7
+ def initialize(document,results,opts = {})
8
+ raise "class must include Mongoid::Spacial::Document" unless document.respond_to?(:spacial_fields_indexed)
9
+ @document = document
10
+ @opts = opts
11
+ @stats = results['stats'] || {}
12
+ @opts[:skip] ||= 0
13
+ @opts[:total_entries] = opts[:query]["num"] || @stats['nscanned']
14
+ @limit_value = opts[:per_page]
15
+ @current_page = opts[:page]
16
+
17
+ @_original_array = results['results'].collect do |result|
18
+ res = Mongoid::Factory.from_db(@document, result.delete('obj'))
19
+ res.geo = {}
20
+ # camel case is awkward in ruby when using variables...
21
+ if result['dis']
22
+ res.geo[:distance] = result.delete('dis').to_f
23
+ end
24
+ result.each do |key,value|
25
+ res.geo[key.snakecase.to_sym] = value
26
+ end
27
+ # dist_options[:formula] = opts[:formula] if opts[:formula]
28
+ @opts[:calculate] = @document.spacial_fields_indexed if @document.spacial_fields_indexed.kind_of?(Array) && @opts[:calculate] == true
29
+ if @opts[:calculate]
30
+ @opts[:calculate] = [@opts[:calculate]] unless @opts[:calculate].kind_of? Array
31
+ @opts[:calculate] = @opts[:calculate].map(&:to_sym) & geo_fields
32
+ if @document.spacial_fields_indexed.kind_of?(Array) && @document.spacial_fields_indexed.size == 1
33
+ primary = @document.spacial_fields_indexed.first
34
+ end
35
+ @opts[:calculate].each do |key|
36
+ key = (key.to_s+'_distance').to_sym
37
+ res.geo[key] = res.distance_from(key,center, @opts[:distance_multiplier])
38
+ res.geo[:distance] = res.geo[key] if primary && key == primary
39
+ end
40
+ end
41
+ res
42
+ end
43
+
44
+ if @opts[:page]
45
+ start = (@opts[:page]-1)*@opts[:per_page] # assuming current_page is 1 based.
46
+ super(@_original_array[@opts[:skip]+start, @opts[:per_page]] || [])
47
+ else
48
+ super(@_original_array[@opts[:skip]..-1] || [])
49
+ end
50
+ end
51
+
52
+ def page(page, options = {})
53
+ new_collection = self.dup
54
+
55
+ options = self.opts.merge(options)
56
+
57
+ options[:page] = page || 1
58
+
59
+ options[:paginator] ||= Mongoid::Spacial.paginator()
60
+
61
+ options[:per_page] ||= case options[:paginator]
62
+ when :will_paginate
63
+ @document.per_page
64
+ when :kaminari
65
+ Kaminari.config.default_per_page
66
+ else
67
+ Mongoid::Spacial.default_per_page
68
+ end
69
+
70
+
71
+ start = (options[:page]-1)*options[:per_page] # assuming current_page is 1 based.
72
+ new_collection.replace(@_original_array[@opts[:skip]+start, options[:per_page]] || [])
73
+
74
+ new_collection.opts[:page] = options[:page]
75
+ new_collection.opts[:paginator] = options[:paginator]
76
+ new_collection.opts[:per_page] = options[:per_page]
77
+
78
+ new_collection
79
+ end
80
+
81
+ def per(num)
82
+ self.page(current_page, :per_page => num)
83
+ end
84
+
85
+ def total_entries
86
+ @opts[:total_entries]
87
+ end
88
+
89
+ def current_page
90
+ @opts[:page]
91
+ end
92
+
93
+ def limit_value
94
+ @opts[:per_page]
95
+ end
96
+ alias_method :per_page, :limit_value
97
+
98
+ def num_pages
99
+ @opts[:total_entries]/@opts[:per_page]
100
+ end
101
+ alias_method :total_pages, :num_pages
102
+
103
+ def out_of_bounds?
104
+ self.current_page > self.total_pages
105
+ end
106
+
107
+ def offset
108
+ (self.current_page - 1) * self.per_page
109
+ end
110
+
111
+ # current_page - 1 or nil if there is no previous page
112
+ def previous_page
113
+ self.current_page > 1 ? (self.current_page - 1) : nil
114
+ end
115
+
116
+ # current_page + 1 or nil if there is no next page
117
+ def next_page
118
+ self.current_page < self.total_pages ? (self.current_page + 1) : nil
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -1,9 +1,9 @@
1
1
  require 'mongoid_spacial/spacial/core_ext'
2
2
  require 'mongoid_spacial/spacial/formulas'
3
+ require 'mongoid_spacial/spacial/document'
4
+ require 'mongoid_spacial/spacial/geo_near_results'
3
5
  module Mongoid
4
6
  module Spacial
5
- autoload :Document, 'mongoid_spacial/spacial/document'
6
- autoload :GeoNear, 'mongoid_spacial/spacial/geo_near'
7
7
 
8
8
  EARTH_RADIUS_KM = 6371 # taken directly from mongodb
9
9
 
@@ -14,15 +14,18 @@ module Mongoid
14
14
  :ft => EARTH_RADIUS_KM*5280*0.621371192,
15
15
  }
16
16
 
17
+ RAD_PER_DEG = Math::PI/180
18
+
17
19
  LNG_SYMBOLS = [:x, :lon, :long, :lng, :longitude]
18
20
  LAT_SYMBOLS = [:y, :lat, :latitude]
19
21
 
20
- def distance(p1,p2,unit = nil, formula = nil)
22
+ def self.distance(p1,p2,unit = nil, formula = nil)
21
23
  formula ||= self.distance_formula
22
24
  unit = earth_radius[unit] if unit.kind_of?(Symbol) && earth_radius[unit]
23
25
  rads = Formulas.send(formula, p1, p2)
24
26
  (unit.kind_of?(Numeric)) ? unit*rads : rads
25
27
  end
28
+
26
29
  mattr_accessor :lng_symbols
27
30
  @@lng_symbols = LNG_SYMBOLS.dup
28
31
 
@@ -35,8 +38,8 @@ module Mongoid
35
38
  mattr_accessor :distance_formula
36
39
  @@distance_formula = :n_vector
37
40
 
38
- mattr_accessor :paginator
39
- @@paginator = :array
41
+ mattr_accessor :paginator
42
+ @@paginator = :array
40
43
 
41
44
  mattr_accessor :default_per_page
42
45
  @@default_per_page = 25
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongoid_spacial}
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = [%q{Ryan Ong}]
@@ -40,12 +40,13 @@ Gem::Specification.new do |s|
40
40
  "lib/mongoid_spacial/spacial/core_ext.rb",
41
41
  "lib/mongoid_spacial/spacial/document.rb",
42
42
  "lib/mongoid_spacial/spacial/formulas.rb",
43
- "lib/mongoid_spacial/spacial/geo_near.rb",
43
+ "lib/mongoid_spacial/spacial/geo_near_results.rb",
44
44
  "mongoid_spacial.gemspec",
45
45
  "spec/config/mongod.conf",
46
46
  "spec/config/mongoid.yml",
47
47
  "spec/functional/mongoid/contexts/mongo_spec.rb",
48
48
  "spec/functional/mongoid/criterion/inclusion_spec.rb",
49
+ "spec/functional/mongoid/spacial/geo_near_results_spec.rb",
49
50
  "spec/models/account.rb",
50
51
  "spec/models/acolyte.rb",
51
52
  "spec/models/address.rb",
@@ -86,18 +86,18 @@ describe Mongoid::Contexts::Mongo do
86
86
  Bar.delete_all
87
87
  Bar.create_indexes
88
88
 
89
- 100.times do
89
+ 50.times do
90
90
  Bar.create({:location => [rand(360)-180,rand(360)-180]})
91
91
  end
92
92
  end
93
93
  context ":paginator :array" do
94
- [nil,1,2,3,4].each do |page|
94
+ [nil,1,2].each do |page|
95
95
  it "page=#{page} should have 25" do
96
96
  Bar.geo_near([1,1], :page => page).size.should == 25
97
97
  end
98
98
  end
99
99
 
100
- it "page=20 should have 0" do
100
+ it "page=3 should have 0" do
101
101
  Bar.geo_near([1,1], :page => 20).size.should == 0
102
102
  end
103
103
 
@@ -105,7 +105,21 @@ describe Mongoid::Contexts::Mongo do
105
105
  Bar.geo_near([1,1], :page => 1, :per_page => 5).size.should == 5
106
106
  end
107
107
  end
108
+
109
+ context ":paginator :kaminari" do
110
+ let(:near) {Bar.geo_near([1,1], :page => 1)}
111
+ it "should have current_page" do
112
+ near.current_page.should == 1
113
+ end
114
+
115
+ it "should have num_pages" do
116
+ near.num_pages.should == 2
117
+ end
118
+
119
+ it "should have limit_value" do
120
+ near.limit_value.should == 25
121
+ end
122
+ end
108
123
  end
109
124
 
110
125
  end
111
-
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Spacial::GeoNearResults do
4
+ before(:all) do
5
+ Bar.delete_all
6
+ Bar.create_indexes
7
+
8
+ 50.times do
9
+ Bar.create({:location => [rand(360)-180,rand(360)-180]})
10
+ end
11
+ while Bar.count < 50
12
+ end
13
+ end
14
+ context ":paginator :array" do
15
+ [nil,1,2].each do |page|
16
+ it "page=#{page} should have 25" do
17
+ Bar.geo_near([1,1]).page(page).size.should == 25
18
+ end
19
+ end
20
+
21
+ it "page=3 should have 0" do
22
+ Bar.geo_near([1,1]).page(3).size.should == 0
23
+ end
24
+
25
+ it "per=5" do
26
+ Bar.geo_near([1,1]).per(5).size.should == 5
27
+ end
28
+ end
29
+
30
+ context ":paginator :kaminari" do
31
+ let!(:near) {Bar.geo_near([1,1]).page(1)}
32
+ it "should have current_page" do
33
+ near.current_page.should == 1
34
+ end
35
+
36
+ it "should have num_pages" do
37
+ near.total_entries.should == 50
38
+ near.num_pages.should == 2
39
+ end
40
+
41
+ it "should have limit_value" do
42
+ near.limit_value.should == 25
43
+ end
44
+ end
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -10,10 +10,13 @@ require "mongoid"
10
10
  require "mocha"
11
11
  require "rspec"
12
12
  require "mongoid_spacial"
13
- require 'ruby-debug'
14
13
 
15
14
  LOGGER = Logger.new($stdout)
16
15
 
16
+ if RUBY_VERSION >= '1.9.2'
17
+ YAML::ENGINE.yamler = 'syck'
18
+ end
19
+
17
20
  Mongoid.configure do |config|
18
21
  name = "mongoid_spacial_test"
19
22
  config.master = Mongo::Connection.new.db(name)
@@ -35,7 +38,6 @@ RSpec.configure do |config|
35
38
  warn(Support::Authentication.message) unless user_configured
36
39
 
37
40
  config.filter_run_excluding(:config => lambda { |value|
38
- return true if value == :mongohq && !mongohq_configured
39
41
  return true if value == :user && !user_configured
40
42
  })
41
43
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: mongoid_spacial
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.1
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ryan Ong
@@ -166,12 +166,13 @@ files:
166
166
  - lib/mongoid_spacial/spacial/core_ext.rb
167
167
  - lib/mongoid_spacial/spacial/document.rb
168
168
  - lib/mongoid_spacial/spacial/formulas.rb
169
- - lib/mongoid_spacial/spacial/geo_near.rb
169
+ - lib/mongoid_spacial/spacial/geo_near_results.rb
170
170
  - mongoid_spacial.gemspec
171
171
  - spec/config/mongod.conf
172
172
  - spec/config/mongoid.yml
173
173
  - spec/functional/mongoid/contexts/mongo_spec.rb
174
174
  - spec/functional/mongoid/criterion/inclusion_spec.rb
175
+ - spec/functional/mongoid/spacial/geo_near_results_spec.rb
175
176
  - spec/models/account.rb
176
177
  - spec/models/acolyte.rb
177
178
  - spec/models/address.rb
@@ -261,7 +262,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
261
262
  requirements:
262
263
  - - ">="
263
264
  - !ruby/object:Gem::Version
264
- hash: 3825965172872748297
265
+ hash: -931697683295411699
265
266
  segments:
266
267
  - 0
267
268
  version: "0"
@@ -1,86 +0,0 @@
1
- module Mongoid
2
- module Spacial
3
- class GeoNear < Array
4
- attr_reader :stats
5
-
6
- def initialize(klass,results,opts = {})
7
- raise "class must include Mongoid::Spacial::Document" unless klass.respond_to?(:spacial_fields_indexed)
8
- @klass = klass
9
- @opts = opts
10
- @stats = results['stats']
11
-
12
- @_original_array = results['results'].collect do |result|
13
- res = Mongoid::Factory.from_db(klass, result.delete('obj'))
14
- res.geo = {}
15
- # camel case is awkward in ruby when using variables...
16
- if result['dis']
17
- res.geo[:distance] = result.delete('dis').to_f
18
- end
19
- result.each do |key,value|
20
- res.geo[key.snakecase.to_sym] = value
21
- end
22
- # dist_options[:formula] = opts[:formula] if opts[:formula]
23
- @opts[:calculate] = klass.spacial_fields_indexed if klass.spacial_fields_indexed.kind_of?(Array) && @opts[:calculate] == true
24
- if @opts[:calculate]
25
- @opts[:calculate] = [@opts[:calculate]] unless @opts[:calculate].kind_of? Array
26
- @opts[:calculate] = @opts[:calculate].map(&:to_sym) & geo_fields
27
- if klass.spacial_fields_indexed.kind_of?(Array) && klass.spacial_fields_indexed.size == 1
28
- primary = klass.spacial_fields_indexed.first
29
- end
30
- @opts[:calculate].each do |key|
31
- key = (key.to_s+'_distance').to_sym
32
- res.geo[key] = res.distance_from(key,center, @opts[:distance_multiplier])
33
- res.geo[:distance] = res.geo[key] if primary && key == primary
34
- end
35
- end
36
- res
37
- end
38
-
39
- if @opts[:page]
40
- start = (@opts[:page]-1)*@opts[:per_page] # assuming current_page is 1 based.
41
- super(@_original_array[start, @opts[:per_page]] || [])
42
- elsif @opts[:skip] && @_original_array.size > @opts[:skip]
43
- super(@_original_array[@opts[:skip]..-1] || [])
44
- else
45
- super(@_original_array || [])
46
- end
47
- end
48
-
49
- def current_page
50
- @opts[:page]
51
- end
52
-
53
- def num_pages
54
- @stats['nscanned']/@opts[:per_page]
55
- end
56
- alias_method :total_pages, :num_pages
57
-
58
- def out_of_bounds?
59
- current_page > total_pages
60
- end
61
-
62
- def limit_value
63
- @opts[:per_page]
64
- end
65
- alias_method :per_page, :limit_value
66
-
67
- def offset
68
- (current_page - 1) * per_page
69
- end
70
-
71
- # current_page - 1 or nil if there is no previous page
72
- def previous_page
73
- current_page > 1 ? (current_page - 1) : nil
74
- end
75
-
76
- # current_page + 1 or nil if there is no next page
77
- def next_page
78
- current_page < total_pages ? (current_page + 1) : nil
79
- end
80
-
81
- def total_entries
82
- @stats['nscanned']
83
- end
84
- end
85
- end
86
- end