mongo_delegate 0.1.2 → 0.2.0

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.
@@ -6,11 +6,11 @@ A DelegateCollection wraps a local collection and remote collection in one objec
6
6
 
7
7
  This was inspired by things I've been told about Goldman Sachs' proprietary object database. You can point one database at another remote database, and it will function in this manner.
8
8
 
9
- The classic use case is debugging a production problem, or developing while using production data.
9
+ === The classic use case is debugging a production problem, or developing while using production data.
10
10
 
11
11
  The annoying ways
12
12
  * Generate data that mimics production
13
- * get a production dump and load it locally
13
+ * Get a production dump and load it locally
14
14
  * "Test" in production while walking on eggshells to make sure not to break anything
15
15
 
16
16
  The easy way
data/Rakefile CHANGED
@@ -50,3 +50,16 @@ Rake::RDocTask.new do |rdoc|
50
50
  end
51
51
 
52
52
  Jeweler::GemcutterTasks.new
53
+
54
+ task :run_specs do
55
+ require 'directory_watcher'
56
+
57
+ dw = DirectoryWatcher.new '.'
58
+ dw.glob = "**/*"
59
+ dw.interval = 0.25
60
+ dw.add_observer {|*args| Rake::Task['spec'].execute }
61
+
62
+ dw.start
63
+ gets # when the user hits "enter" the script will terminate
64
+ dw.stop
65
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -1,3 +1,34 @@
1
+ class Mongo::Cursor
2
+ def foc
3
+ formatted_order_clause
4
+ rescue => exp
5
+ return nil
6
+ end
7
+ end
8
+
9
+ class Numeric
10
+ def sortflip
11
+ self * -1.0
12
+ end
13
+ end
14
+
15
+ class String
16
+ def num_val
17
+ return 0 if length == 0
18
+ self[-1] + 500*self[0..-2].num_val
19
+ end
20
+ def sortflip
21
+ opposite_word
22
+ end
23
+ def opposite_word
24
+ res = " " * length
25
+ for i in (0...length)
26
+ res[i] = 255-self[i]
27
+ end
28
+ res
29
+ end
30
+ end
31
+
1
32
  class CompositeCursor
2
33
  class FindOps
3
34
  attr_accessor :res, :cursor
@@ -5,10 +36,22 @@ class CompositeCursor
5
36
  def options; cursor.options; end
6
37
  fattr(:num_using) { res.map { |x| x[0].to_af.size }.sum }
7
38
  fattr(:found_ids) { res.map { |x| x[1].to_af.map { |x| x['_id'] } }.flatten }
39
+ fattr(:limit) do
40
+ if options[:limit]
41
+ if options[:sort]
42
+ options[:limit]
43
+ else
44
+ options[:limit] - num_using
45
+ end
46
+ else
47
+ nil
48
+ end
49
+ end
8
50
  fattr(:ops) do
9
51
  r = {}
10
- r[:limit] = (options[:limit] - num_using) if options[:limit]
11
- r[:skip] = [(options[:skip] - found_ids.size ),0].max if options[:skip]
52
+ r[:limit] = limit if limit
53
+ r[:skip] = [(options[:skip] - found_ids.size ),0].max if options[:skip] && !options[:sort]
54
+ r[:sort] = options[:sort] if options[:sort]
12
55
  r
13
56
  end
14
57
  def zero_limit?
@@ -20,9 +63,23 @@ class CompositeCursor
20
63
  include FromHash
21
64
  include Enumerable
22
65
  def to_af; rows; end
23
- fattr(:rows) do
66
+ def sort_proc
67
+ foc = cursors.first.foc
68
+ return nil unless foc
69
+ lambda do |doc|
70
+ foc.map { |k,v| (v == 1) ? doc[k] : doc[k].sortflip }
71
+ end
72
+ end
73
+ fattr(:raw_rows) do
24
74
  cursors.map { |x| x.to_af }.flatten
25
75
  end
76
+ fattr(:rows) do
77
+ res = raw_rows
78
+ res = res.sort_by(&sort_proc) if sort_proc
79
+ res = res[0...(options[:limit])] if options[:limit]
80
+ res = res[(options[:skip])..-1] if options[:skip] && options[:sort]
81
+ res
82
+ end
26
83
  def each(&b)
27
84
  rows.each(&b)
28
85
  end
@@ -1,55 +1,5 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- class Array
4
- def num_from_bytes
5
- return 0 if empty?
6
- self[-1] + 256 * self[0..-2].num_from_bytes
7
- end
8
- def time_from_bytes
9
- Time.at(num_from_bytes)
10
- end
11
- end
12
-
13
- class Numeric
14
- def to_bytes
15
- return [] if self == 0
16
- (self/256).to_bytes + [self%256]
17
- end
18
- def to_bytes4
19
- res = to_bytes
20
- while res.length < 4
21
- res = [0] + res
22
- end
23
- res
24
- end
25
- end
26
-
27
- class Time
28
- def to_small_mongo_id
29
- res = Mongo::ObjectID.new
30
- res.data = to_i.to_bytes4 + (0...8).map { |x| 0 }
31
- res
32
- end
33
- def to_big_mongo_id
34
- res = Mongo::ObjectID.new
35
- res.data = to_i.to_bytes4 + (0...8).map { |x| 255 }
36
- res
37
- end
38
- end
39
-
40
- class Mongo::ObjectID
41
- attr_accessor :data
42
- def time
43
- data[0...4].time_from_bytes
44
- end
45
- end
46
-
47
- class Hash
48
- def created_at
49
- self['_id'].time
50
- end
51
- end
52
-
53
3
  context Mongo::DelegatingCollection do
54
4
  def setup_coll
55
5
  @start_time = Time.now
@@ -57,8 +7,8 @@ context Mongo::DelegatingCollection do
57
7
  @db = Mongo::DelegatingDatabase.new(:remote => @conn.db('dc-remote'), :local => @conn.db('dc-local'))
58
8
  @d = @db.collection('abc')
59
9
  @d.remove
60
- @remotes = %w(Ellen Randy Barbara).each { |x| @d.remote.save(:name => x) }
61
- @locals = %w(Mike Lou Lowell).each { |x| @d.local.save(:name => x) }
10
+ @remotes = %w(Barbara Danny Frank).each { |x| @d.remote.save(:name => x) }
11
+ @locals = %w(Adam Chris Eric).each { |x| @d.local.save(:name => x) }
62
12
  @all = @remotes + @locals
63
13
  end
64
14
  context 'find' do
@@ -67,7 +17,7 @@ context Mongo::DelegatingCollection do
67
17
  @d.find.count.should == @all.size
68
18
  end
69
19
  it 'find doesnt return dups' do
70
- @d.save(@d.remote.find_one(:name => 'Randy'))
20
+ @d.save(@d.remote.find_one(:name => @remotes.first))
71
21
  @d.find.count.should == @all.size
72
22
  @d.find.unpruned_rows.size.should == @all.size + 1
73
23
  end
@@ -77,9 +27,9 @@ context Mongo::DelegatingCollection do
77
27
  @d.find.rows
78
28
  end
79
29
  it 'find returns local precedence' do
80
- r = @d.remote.find_one(:name => 'Randy').merge(:age => 'old')
30
+ r = @d.remote.find_one(:name => @remotes.first).merge(:age => 'old')
81
31
  @d.local.save(r)
82
- @d.find_one(:name => 'Randy')['age'].should == 'old'
32
+ @d.find_one(:name => @remotes.first)['age'].should == 'old'
83
33
  end
84
34
  it 'respects limit' do
85
35
  @d.find({},:limit => 4).to_af.size.should == 4
@@ -91,25 +41,52 @@ context Mongo::DelegatingCollection do
91
41
  @d.find({},:limit => 2).rows
92
42
  end
93
43
  it 'respects skip' do
94
- @d.find({},:skip => 1).to_af.size.should == 5
95
- @d.find({},:skip => 1).map { |x| x['name'] }.sort.should == @all.reject { |x| x == 'Mike' }.sort
44
+ @d.find({},:skip => 1).to_af.size.should == @all.size - 1
45
+ @d.find({},:skip => 1).map { |x| x['name'] }.sort.should == @all.reject { |x| x == @locals.first }.sort
96
46
  end
97
47
  it 'respects skip whole collection' do
98
- @d.find({},:skip => 4).to_af.size.should == 2
48
+ @d.find({},:skip => 4).to_af.size.should == @all.size - 4
99
49
  end
100
50
  it 'wont return remote records that were skipped locally' do
101
- @d.local.save(@d.remote.find_one(:name => 'Randy'))
102
- @d.find({},:skip => 5).to_af.size.should == 1
51
+ @d.local.save(@d.remote.find_one(:name => @remotes.first))
52
+ @d.find({},:skip => 5).to_af.size.should == @all.size - 5
103
53
  end
104
54
  it 'respects skip and limit' do
105
55
  @d.find({},:skip => 1, :limit => 4).to_af.size.should == 4
106
56
  end
107
57
  it 'count doesnt fetch records' do
108
58
  mock.instance_of(Mongo::Cursor).to_a.times(0)
109
- @d.count.should == 6
59
+ @d.count.should == @all.size
110
60
  end
111
- it 'abc' do
112
- @d.local.scope_gte('_id' => @start_time.to_small_mongo_id).count.should == 3
61
+ # it 'abc' do
62
+ # @d.local.scope_gte('_id' => @start_time.to_small_mongo_id).count.should == 3
63
+ # end
64
+ context 'sorting' do
65
+ it 'honors sort order when retreiving all records' do
66
+ @d.find({},:sort => [['name','ascending']]).map { |x| x['name'] }.should == @all.sort
67
+ end
68
+ it 'honors reverse sort order when retreiving all records' do
69
+ @d.find({},:sort => [['name','descending']]).map { |x| x['name'] }.should == @all.sort.reverse
70
+ end
71
+ it 'honors sort order with a limit' do
72
+ exp = @all.sort[0...4]
73
+ @d.find({},:sort => [['name','ascending']], :limit => 4).map { |x| x['name'] }.should == exp
74
+ end
75
+ it 'honors sort order with a limit desc' do
76
+ exp = @all.sort.reverse[0...4]
77
+ @d.find({},:sort => [['name','descending']], :limit => 4).map { |x| x['name'] }.should == exp
78
+ end
79
+ it 'honors sort order with a skip' do
80
+ exp = @all.sort[1..-1]
81
+ @d.find({},:sort => [['name','ascending']], :skip => 1).map { |x| x['name'] }.should == exp
82
+ end
83
+ it 'honors sort order with a skip desc' do
84
+ exp = @all.sort.reverse[1..-1]
85
+ @d.find({},:sort => [['name','descending']], :skip => 1).map { |x| x['name'] }.should == exp
86
+ end
87
+ it 'when sorting with a limit, never retreives more than the limit from any one collection' do
88
+ @d.find({},:sort => [['name','ascending']], :limit => 2).raw_rows.size.should == 4
89
+ end
113
90
  end
114
91
  # it 'count honors remote deletes' do
115
92
  # @d.remote.find.each { |x| @d.save(x) }
@@ -126,16 +103,16 @@ context Mongo::DelegatingCollection do
126
103
  @d.local.count.should == @locals.size + 1
127
104
  end
128
105
  it 'updating a non-dup shouldnt mark it as a dup' do
129
- r = @d.local.find_one(:name => 'Mike').merge(:foo => :bar)
106
+ r = @d.local.find_one(:name => @locals.first).merge(:foo => :bar)
130
107
  @d.save(r)
131
108
  @d.count.should == 6
132
- @d.local.find_one(:name => 'Mike')['_duplicate'].should be_nil
109
+ @d.local.find_one(:name => @locals.first)['_duplicate'].should be_nil
133
110
  end
134
111
  it 'saving a dup for first time should mark it as a dup' do
135
- r = @d.remote.find_one(:name => 'Randy')
112
+ r = @d.remote.find_one(:name => @remotes.first)
136
113
  @d.save(r)
137
114
  @d.count.should == 6
138
- @d.local.find_one(:name => 'Randy')['_duplicate'].should == true
115
+ @d.local.find_one(:name => @remotes.first)['_duplicate'].should == true
139
116
  end
140
117
  end
141
118
  end
@@ -0,0 +1,49 @@
1
+ class Array
2
+ def num_from_bytes
3
+ return 0 if empty?
4
+ self[-1] + 256 * self[0..-2].num_from_bytes
5
+ end
6
+ def time_from_bytes
7
+ Time.at(num_from_bytes)
8
+ end
9
+ end
10
+
11
+ class Numeric
12
+ def to_bytes
13
+ return [] if self == 0
14
+ (self/256).to_bytes + [self%256]
15
+ end
16
+ def to_bytes4
17
+ res = to_bytes
18
+ while res.length < 4
19
+ res = [0] + res
20
+ end
21
+ res
22
+ end
23
+ end
24
+
25
+ class Time
26
+ def to_small_mongo_id
27
+ res = Mongo::ObjectID.new
28
+ res.data = to_i.to_bytes4 + (0...8).map { |x| 0 }
29
+ res
30
+ end
31
+ def to_big_mongo_id
32
+ res = Mongo::ObjectID.new
33
+ res.data = to_i.to_bytes4 + (0...8).map { |x| 255 }
34
+ res
35
+ end
36
+ end
37
+
38
+ class Mongo::ObjectID
39
+ attr_accessor :data
40
+ def time
41
+ data[0...4].time_from_bytes
42
+ end
43
+ end
44
+
45
+ class Hash
46
+ def created_at
47
+ self['_id'].time
48
+ end
49
+ end
@@ -4,6 +4,7 @@ require 'mongo_delegate'
4
4
  require 'spec'
5
5
  require 'spec/autorun'
6
6
  require 'rr'
7
+ require File.join(File.dirname(__FILE__),"id_time")
7
8
 
8
9
  Spec::Runner.configure do |config|
9
10
  config.mock_with :rr
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo_delegate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Harris
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-14 00:00:00 -05:00
12
+ date: 2010-01-15 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -151,5 +151,6 @@ specification_version: 3
151
151
  summary: restful interface to delegate mongodb records
152
152
  test_files:
153
153
  - spec/delegating_collection_spec.rb
154
+ - spec/id_time.rb
154
155
  - spec/mongo_delegate_spec.rb
155
156
  - spec/spec_helper.rb