mongoid-scroll 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: