mongoid-scroll 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ 0.2.1 (3/21/2013)
2
+ =================
3
+
4
+ * Fix: scroll over a collection that has duplicate values while data is being modified in a way that causes a change in the natural sort order - [@dblock](https://github.com/dblock).
5
+
1
6
  0.2.0 (3/14/2013)
2
7
  =================
3
8
 
data/README.md CHANGED
@@ -101,8 +101,8 @@ Indexes and Performance
101
101
  A query without a cursor is identical to a query without a scroll.
102
102
 
103
103
  ``` ruby
104
- # db.feed_items.find().sort({ position: 1 }).limit(5)
105
- Feed::Item.desc(:position).limit(5).scroll
104
+ # db.feed_items.find().sort({ position: 1 }).limit(7)
105
+ Feed::Item.desc(:position).limit(7).scroll
106
106
  ```
107
107
 
108
108
  Subsequent queries use an `$or` to avoid skipping items with the same value as the one at the current cursor position.
@@ -111,8 +111,8 @@ Subsequent queries use an `$or` to avoid skipping items with the same value as t
111
111
  # db.feed_items.find({ "$or" : [
112
112
  # { "position" : { "$gt" : 13 }},
113
113
  # { "position" : 13, "_id": { "$gt" : ObjectId("511d7c7c3b5552c92400000e") }}
114
- # ]}).sort({ position: 1 })
115
- Feed:Item.desc(:position).limit(5).scroll(cursor)
114
+ # ]}).sort({ position: 1 }).limit(7)
115
+ Feed:Item.desc(:position).limit(7).scroll(cursor)
116
116
  ```
117
117
 
118
118
  This means you need to hit an index on `position` and `_id`.
@@ -13,14 +13,14 @@ module Mongoid
13
13
  end
14
14
  # scroll field and direction
15
15
  scroll_field = criteria.options[:sort].keys.first
16
- scroll_direction = criteria.options[:sort].values.first.to_i == 1 ? '$gt' : '$lt'
16
+ scroll_direction = criteria.options[:sort].values.first.to_i
17
17
  # scroll cursor from the parameter, with value and tiebreak_id
18
18
  field = criteria.klass.fields[scroll_field.to_s]
19
19
  cursor_options = { field_type: field.type, field_name: scroll_field, direction: scroll_direction }
20
20
  cursor = cursor.is_a?(Mongoid::Scroll::Cursor) ? cursor : Mongoid::Scroll::Cursor.new(cursor, cursor_options)
21
21
  # scroll
22
22
  if block_given?
23
- criteria.where(cursor.criteria).each do |record|
23
+ criteria.where(cursor.criteria).order_by(_id: scroll_direction).each do |record|
24
24
  yield record, Mongoid::Scroll::Cursor.from_record(record, cursor_options)
25
25
  end
26
26
  else
@@ -6,14 +6,15 @@ module Mongoid
6
6
 
7
7
  def initialize(value = nil, options = {})
8
8
  @field_type, @field_name = Mongoid::Scroll::Cursor.extract_field_options(options)
9
- @direction = options[:direction] || '$gt'
9
+ @direction = options[:direction] || 1
10
10
  parse(value)
11
11
  end
12
12
 
13
13
  def criteria
14
14
  mongo_value = value.class.mongoize(value) if value
15
- cursor_criteria = { field_name => { direction => mongo_value } } if mongo_value
16
- tiebreak_criteria = { field_name => mongo_value, :_id => { '$gt' => tiebreak_id } } if mongo_value && tiebreak_id
15
+ compare_direction = direction == 1 ? "$gt" : "$lt"
16
+ cursor_criteria = { field_name => { compare_direction => mongo_value } } if mongo_value
17
+ tiebreak_criteria = { field_name => mongo_value, :_id => { compare_direction => tiebreak_id } } if mongo_value && tiebreak_id
17
18
  (cursor_criteria || tiebreak_criteria) ? { '$or' => [ cursor_criteria, tiebreak_criteria].compact } : {}
18
19
  end
19
20
 
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module Scroll
3
- VERSION = '0.2.0'
3
+ VERSION = '0.2.1'
4
4
  end
5
5
  end
@@ -2,27 +2,31 @@ module Moped
2
2
  module Scrollable
3
3
 
4
4
  def scroll(cursor = nil, options = { field_type: Moped::BSON::ObjectId }, &block)
5
+ query = Query.new(collection, operation.selector.dup)
6
+ query.operation.skip = operation.skip
7
+ query.operation.limit = operation.limit
5
8
  # we don't support scrolling over a criteria with multiple fields
6
- if operation.selector["$orderby"] && operation.selector["$orderby"].keys.size != 1
7
- raise Mongoid::Scroll::Errors::MultipleSortFieldsError.new(sort: operation.selector["$orderby"])
8
- elsif ! operation.selector.has_key?("$orderby") || operation.selector["$orderby"].empty?
9
+ if query.operation.selector["$orderby"] && query.operation.selector["$orderby"].keys.size != 1
10
+ raise Mongoid::Scroll::Errors::MultipleSortFieldsError.new(sort: query.operation.selector["$orderby"])
11
+ elsif ! query.operation.selector.has_key?("$orderby") || query.operation.selector["$orderby"].empty?
9
12
  # introduce a default sort order if there's none
10
- sort("_id" => 1)
13
+ query.sort(_id: 1)
11
14
  end
12
15
  # scroll field and direction
13
- scroll_field = operation.selector["$orderby"].keys.first
14
- scroll_direction = operation.selector["$orderby"].values.first.to_i == 1 ? '$gt' : '$lt'
16
+ scroll_field = query.operation.selector["$orderby"].keys.first
17
+ scroll_direction = query.operation.selector["$orderby"].values.first.to_i
15
18
  # scroll cursor from the parameter, with value and tiebreak_id
16
19
  cursor_options = { field_name: scroll_field, field_type: options[:field_type], direction: scroll_direction }
17
20
  cursor = cursor.is_a?(Mongoid::Scroll::Cursor) ? cursor : Mongoid::Scroll::Cursor.new(cursor, cursor_options)
18
- operation.selector["$query"].merge!(cursor.criteria)
21
+ query.operation.selector["$query"] = query.operation.selector["$query"].merge(cursor.criteria)
22
+ query.operation.selector["$orderby"] = query.operation.selector["$orderby"].merge(_id: scroll_direction)
19
23
  # scroll
20
24
  if block_given?
21
- each do |record|
25
+ query.each do |record|
22
26
  yield record, Mongoid::Scroll::Cursor.from_record(record, cursor_options)
23
27
  end
24
28
  else
25
- self
29
+ query
26
30
  end
27
31
  end
28
32
 
@@ -96,20 +96,29 @@ describe Mongoid::Criteria do
96
96
  context "with overlapping data" do
97
97
  before :each do
98
98
  3.times { Feed::Item.create! a_integer: 5 }
99
+ Feed::Item.first.update_attributes!(name: Array(1000).join('a'))
99
100
  end
100
- it "scrolls" do
101
- records = []
102
- cursor = nil
103
- Feed::Item.desc(:a_integer).limit(2).scroll do |record, next_cursor|
104
- records << record
105
- cursor = next_cursor
106
- end
107
- records.size.should == 2
108
- Feed::Item.desc(:a_integer).scroll(cursor) do |record, next_cursor|
109
- records << record
101
+ it "natural order is different from order by id" do
102
+ # natural order isn't necessarily going to be the same as _id order
103
+ # if a document is updated and grows in size, it may need to be relocated and
104
+ # thus cause the natural order to change
105
+ Feed::Item.order_by("$natural" => 1).to_a.should_not eq Feed::Item.order_by(_id: 1).to_a
106
+ end
107
+ [ { a_integer: 1 }, { a_integer: -1 }].each do |sort_order|
108
+ it "scrolls by #{sort_order}" do
109
+ records = []
110
+ cursor = nil
111
+ Feed::Item.order_by(sort_order).limit(2).scroll do |record, next_cursor|
112
+ records << record
113
+ cursor = next_cursor
114
+ end
115
+ records.size.should == 2
116
+ Feed::Item.order_by(sort_order).scroll(cursor) do |record, next_cursor|
117
+ records << record
118
+ end
119
+ records.size.should == 3
120
+ records.should eq Feed::Item.all.sort(_id: sort_order[:a_integer]).to_a
110
121
  end
111
- records.size.should == 3
112
- records.should eq Feed::Item.all.to_a
113
122
  end
114
123
  end
115
124
  end
@@ -23,7 +23,7 @@ describe Moped::Query do
23
23
  Mongoid.default_session['feed_items'].find
24
24
  end
25
25
  it "adds a default sort by _id" do
26
- subject.scroll.operation.selector["$orderby"].should == { "_id" => 1 }
26
+ subject.scroll.operation.selector["$orderby"].should == { _id: 1 }
27
27
  end
28
28
  end
29
29
  context "with data" do
@@ -98,20 +98,29 @@ describe Moped::Query do
98
98
  context "with overlapping data" do
99
99
  before :each do
100
100
  3.times { Feed::Item.create! a_integer: 5 }
101
+ Feed::Item.first.update_attributes!(name: Array(1000).join('a'))
101
102
  end
102
- it "scrolls" do
103
- records = []
104
- cursor = nil
105
- Mongoid.default_session['feed_items'].find.sort(a_integer: -1).limit(2).scroll do |record, next_cursor|
106
- records << record
107
- cursor = next_cursor
108
- end
109
- records.size.should == 2
110
- Mongoid.default_session['feed_items'].find.sort(a_integer: -1).scroll(cursor) do |record, next_cursor|
111
- records << record
103
+ it "natural order is different from order by id" do
104
+ # natural order isn't necessarily going to be the same as _id order
105
+ # if a document is updated and grows in size, it may need to be relocated and
106
+ # thus cause the natural order to change
107
+ Feed::Item.order_by("$natural" => 1).to_a.should_not eq Feed::Item.order_by(_id: 1).to_a
108
+ end
109
+ [ { a_integer: 1 }, { a_integer: -1 }].each do |sort_order|
110
+ it "scrolls by #{sort_order}" do
111
+ records = []
112
+ cursor = nil
113
+ Mongoid.default_session['feed_items'].find.sort(sort_order).limit(2).scroll do |record, next_cursor|
114
+ records << record
115
+ cursor = next_cursor
116
+ end
117
+ records.size.should == 2
118
+ Mongoid.default_session['feed_items'].find.sort(sort_order).scroll(cursor) do |record, next_cursor|
119
+ records << record
120
+ end
121
+ records.size.should == 3
122
+ records.should eq Mongoid.default_session['feed_items'].find.sort(_id: sort_order[:a_integer]).to_a
112
123
  end
113
- records.size.should == 3
114
- records.should eq Mongoid.default_session['feed_items'].find.to_a
115
124
  end
116
125
  end
117
126
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid-scroll
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-15 00:00:00.000000000 Z
13
+ date: 2013-02-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: mongoid
@@ -95,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
95
  version: '0'
96
96
  segments:
97
97
  - 0
98
- hash: 4314128693864897181
98
+ hash: -2452181324971075888
99
99
  required_rubygems_version: !ruby/object:Gem::Requirement
100
100
  none: false
101
101
  requirements: