em-mongo 0.3.6 → 0.4.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.
@@ -0,0 +1,34 @@
1
+ module EM
2
+ module Mongo
3
+ class RequestResponse
4
+ include EM::Deferrable
5
+
6
+ def status
7
+ @deferred_status
8
+ end
9
+
10
+ def completed?
11
+ [:succeeded, :failed].include?(status)
12
+ end
13
+
14
+ def succeeded?
15
+ status == :succeeded
16
+ end
17
+
18
+ def failed?
19
+ status == :failed
20
+ end
21
+
22
+ def data
23
+ @deferred_args[-1] if succeeded? && @deferred_args
24
+ end
25
+
26
+ def error
27
+ @deferred_args[-1] if failed? && @deferred_args
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+
@@ -0,0 +1,32 @@
1
+ module EM::Mongo
2
+ class ServerResponse
3
+
4
+ attr_reader :size, :request_id, :response_to, :op,
5
+ :result_flags, :cursor_id, :starting_from,
6
+ :number_returned, :docs, :connection
7
+
8
+
9
+ def initialize(buffer, connection)
10
+ @connection = connection
11
+ # Header
12
+ @size = buffer.get_int
13
+ @request_id = buffer.get_int
14
+ @response_to = buffer.get_int
15
+ @op = buffer.get_int
16
+
17
+ # Response Header
18
+ @result_flags = buffer.get_int
19
+ @cursor_id = buffer.get_long
20
+ @starting_from = buffer.get_int
21
+ @number_returned = buffer.get_int
22
+
23
+ # Documents
24
+ @docs = (1..number_returned).map do
25
+ size= @connection.peek_size(buffer)
26
+ buf = buffer.get(size)
27
+ BSON::BSON_CODER.deserialize(buf)
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -18,9 +18,9 @@
18
18
 
19
19
  require 'digest/md5'
20
20
 
21
- module Mongo
21
+ module EM::Mongo
22
22
  module Support
23
- include Mongo::Conversions
23
+ include EM::Mongo::Conversions
24
24
  extend self
25
25
 
26
26
  # Generate an MD5 for authentication.
@@ -52,10 +52,10 @@ module Mongo
52
52
 
53
53
  [" ", ".", "$", "/", "\\"].each do |invalid_char|
54
54
  if db_name.include? invalid_char
55
- raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
55
+ raise EM::Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
56
56
  end
57
57
  end
58
- raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
58
+ raise EM::Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
59
59
  db_name
60
60
  end
61
61
 
data/lib/em-mongo.rb CHANGED
@@ -22,5 +22,10 @@ require File.join(EM::Mongo::LIBPATH, "em-mongo/support")
22
22
  require File.join(EM::Mongo::LIBPATH, "em-mongo/database")
23
23
  require File.join(EM::Mongo::LIBPATH, "em-mongo/connection")
24
24
  require File.join(EM::Mongo::LIBPATH, "em-mongo/collection")
25
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/exceptions")
26
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/cursor")
27
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/request_response")
28
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/server_response")
29
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/core_ext")
25
30
 
26
31
  EMMongo = EM::Mongo
@@ -3,240 +3,740 @@ require File.expand_path('spec_helper', File.dirname(__FILE__) + '/../')
3
3
  describe EMMongo::Collection do
4
4
  include EM::Spec
5
5
 
6
- it 'should insert an object' do
6
+ it 'should return a sub collection via the indexer method' do
7
7
  @conn, @coll = connection_and_collection
8
-
9
- doc = {'hello' => 'world'}
10
- id = @coll.insert(doc)
11
- id.should be_a_kind_of(BSON::ObjectId)
12
- doc[:_id].should be_a_kind_of(BSON::ObjectId)
8
+ @coll["child"].name.should == "#{@coll.name}.child"
13
9
  done
14
10
  end
15
11
 
16
- it 'should insert an object with a custom _id' do
12
+ it "should drop the collection" do
17
13
  @conn, @coll = connection_and_collection
14
+ @coll.insert({:x => "x"})
15
+ @coll.drop.callback do
16
+ @coll.db.collection_names.callback do |names|
17
+ names.should_not include @ns
18
+ done
19
+ end
20
+ end
21
+ end
18
22
 
19
- id = @coll.insert(:_id => 1234, 'hello' => 'world')
20
- id.should == 1234
21
- @coll.first({'hello' => 'world'}) do |res|
22
- res['_id'].should == 1234
23
+ describe "find" do
24
+ it 'should return a cursor' do
25
+ @conn, @coll = connection_and_collection
26
+ cursor = @coll.find(:hi=>"there")
27
+ cursor.should be_a_kind_of(EM::Mongo::Cursor)
23
28
  done
24
29
  end
25
- end
26
30
 
27
- it 'should find an object by attribute' do
28
- @conn, @coll = connection_and_collection
31
+ it 'should find an object by attribute' do
32
+ @conn, @coll = connection_and_collection
29
33
 
30
- @coll.insert("hello" => 'world')
31
- @coll.find({"hello" => "world"},{}) do |res|
32
- res.size.should >= 1
33
- res[0]["hello"].should == "world"
34
- done
34
+ @coll.insert("hello" => 'world')
35
+ @coll.find({"hello" => "world"},{}).to_a.callback do |res|
36
+ res.size.should >= 1
37
+ res[0]["hello"].should == "world"
38
+ done
39
+ end
35
40
  end
36
- end
37
41
 
38
- it 'should take strings or symbols for hashes' do
39
- @conn, @coll = connection_and_collection
42
+ it 'should take strings or symbols for hashes' do
43
+ @conn, @coll = connection_and_collection
40
44
 
41
- obj = @coll.insert({:_id => 1234, 'foo' => 'bar', :hello => 'world'})
42
- @coll.first({:_id => 1234},{}) do |res|
43
- res['hello'].should == 'world'
44
- res['foo'].should == 'bar'
45
- done
45
+ obj = @coll.insert({:_id => 1234, 'foo' => 'bar', :hello => 'world'})
46
+ @coll.first({:_id => 1234},{}).callback do |res|
47
+ res['hello'].should == 'world'
48
+ res['foo'].should == 'bar'
49
+ done
50
+ end
46
51
  end
47
- end
48
52
 
49
- it 'should find an object by symbol' do
50
- @conn, @coll = connection_and_collection
53
+ it 'should find an object by symbol' do
54
+ @conn, @coll = connection_and_collection
51
55
 
52
- @coll.insert('hello' => 'world')
53
- @coll.find({:hello => "world"},{}) do |res|
54
- res.size.should >= 1
55
- res[0]["hello"].should == "world"
56
- done
56
+ @coll.insert('hello' => 'world')
57
+ @coll.find({:hello => "world"},{}).to_a.callback do |res|
58
+ res.size.should >= 1
59
+ res[0]["hello"].should == "world"
60
+ done
61
+ end
57
62
  end
58
- end
59
63
 
60
- it 'should find an object by id' do
61
- @conn, @coll = connection_and_collection
64
+ it 'should find an object by id' do
65
+ @conn, @coll = connection_and_collection
62
66
 
63
- id = @coll.insert('hello' => 'world')
64
- @coll.find({:_id => id},{}) do |res|
65
- res.size.should >= 1
66
- res[0]['hello'].should == "world"
67
- done
67
+ id = @coll.insert('hello' => 'world')
68
+ @coll.find({:_id => id},{}).to_a.callback do |res|
69
+ res.size.should >= 1
70
+ res[0]['hello'].should == "world"
71
+ done
72
+ end
68
73
  end
69
- end
70
74
 
71
- it 'should find all objects' do
72
- @conn, @coll = connection_and_collection
75
+ it 'should find all objects' do
76
+ @conn, @coll = connection_and_collection
73
77
 
74
- @coll.insert('one' => 'one')
75
- @coll.insert('two' => 'two')
76
- @coll.find do |res|
77
- res.size.should >= 2
78
- done
78
+ @coll.insert('one' => 'one')
79
+ @coll.insert('two' => 'two')
80
+ @coll.find.to_a.callback do |res|
81
+ res.size.should >= 2
82
+ done
83
+ end
84
+ end
85
+
86
+ it 'should find objects and sort by the order field' do
87
+ @conn, @coll = connection_and_collection
88
+
89
+ @coll.insert(:name => 'one', :position => 0)
90
+ @coll.insert(:name => 'three', :position => 2)
91
+ @coll.insert(:name => 'two', :position => 1)
92
+
93
+ @coll.find({}, {:order => 'position'}).to_a.callback do |res|
94
+ res[0]["name"].should == 'one'
95
+ res[1]["name"].should == 'two'
96
+ res[2]["name"].should == 'three'
97
+ done
98
+ end
99
+
100
+ @coll.find({}, {:order => [:position, :desc]}).to_a.callback do |res|
101
+ res[0]["name"].should == 'three'
102
+ res[1]["name"].should == 'two'
103
+ res[2]["name"].should == 'one'
104
+ done
105
+ end
106
+ end
107
+
108
+ it "should find a single document with find_one/first" do
109
+ @conn, @coll = connection_and_collection
110
+
111
+ @coll.insert(:name => 'one', :position => 0)
112
+ @coll.insert(:name => 'three', :position => 2)
113
+ @coll.insert(:name => 'two', :position => 1)
114
+
115
+ @coll.find_one({},:sort => [:position,-1]).callback do |first|
116
+ first["position"].should == 2
117
+ done
118
+ end
119
+ end
120
+
121
+ it 'should find an object using nested properties' do
122
+ @conn, @coll = connection_and_collection
123
+
124
+ @coll.insert({
125
+ 'name' => 'Google',
126
+ 'address' => {
127
+ 'cxity' => 'Mountain View',
128
+ 'state' => 'California'}
129
+ })
130
+
131
+ @coll.first('address.cxity' => 'Mountain View').callback do |res|
132
+ res['name'].should == 'Google'
133
+ done
134
+ end
135
+ end
136
+
137
+ it 'should find objects wxith specific values' do
138
+ @conn, @coll = connection_and_collection
139
+
140
+ number_hash.each do |num, word|
141
+ @coll.insert({'num' => num, 'word' => word})
142
+ end
143
+
144
+ @coll.find({'num' => {'$in' => [1,3,5]}}).to_a.callback do |res|
145
+ res.size.should == 3
146
+ res.map{|r| r['num'] }.sort.should == [1,3,5]
147
+ done
148
+ end
149
+ end
150
+
151
+ it 'should find objects greater than something' do
152
+ @conn, @coll = connection_and_collection
153
+
154
+ number_hash.each do |num, word|
155
+ @coll.insert('num' => num, 'word' => word)
156
+ end
157
+
158
+ @coll.find({'num' => {'$gt' => 3}}).to_a.callback do |res|
159
+ res.size.should == 6
160
+ res.map{|r| r['num'] }.sort.should == [4,5,6,7,8,9]
161
+ done
162
+ end
79
163
  end
80
164
  end
81
165
 
82
- it 'should find objects and sort by the order field' do
83
- @conn, @coll = connection_and_collection
166
+ describe "insert" do
84
167
 
85
- @coll.insert(:name => 'one', :position => 0)
86
- @coll.insert(:name => 'three', :position => 2)
87
- @coll.insert(:name => 'two', :position => 1)
168
+ it 'should insert an object' do
169
+ @conn, @coll = connection_and_collection
88
170
 
89
- @coll.find({}, {:order => 'position'}) do |res|
90
- res[0]["name"].should == 'one'
91
- res[1]["name"].should == 'two'
92
- res[2]["name"].should == 'three'
171
+ doc = {'hello' => 'world'}
172
+ id = @coll.insert(doc)
173
+ id.should be_a_kind_of(BSON::ObjectId)
174
+ doc[:_id].should be_a_kind_of(BSON::ObjectId)
93
175
  done
94
176
  end
95
177
 
96
- @coll.find({}, {:order => [:position, :desc]}) do |res|
97
- res[0]["name"].should == 'three'
98
- res[1]["name"].should == 'two'
99
- res[2]["name"].should == 'one'
178
+ it "should insert multiple documents" do
179
+ @conn, @coll = connection_and_collection
180
+
181
+ docs = [{'hello' => 'world'}, {'goodbye' => 'womb'}]
182
+ ids = @coll.insert(docs)
183
+ ids.should be_a_kind_of(Array)
184
+ ids[0].should == docs[0][:_id]
185
+ ids[1].should == docs[1][:_id]
100
186
  done
101
187
  end
188
+
189
+ it 'should insert an object with a custom _id' do
190
+ @conn, @coll = connection_and_collection
191
+
192
+ id = @coll.insert(:_id => 1234, 'hello' => 'world')
193
+ id.should == 1234
194
+ @coll.first({'hello' => 'world'}).callback do |res|
195
+ res['_id'].should == 1234
196
+ done
197
+ end
198
+ end
199
+
200
+ it 'should insert a Time' do
201
+ @conn, @coll = connection_and_collection
202
+
203
+ t = Time.now.utc.freeze
204
+ @coll.insert('date' => t)
205
+ @coll.find.to_a.callback do |res|
206
+ res[0]['date'].to_s.should == t.to_s
207
+ done
208
+ end
209
+ end
210
+
211
+ it 'should insert a complex object' do
212
+ @conn, @coll = connection_and_collection
213
+
214
+ obj = {
215
+ 'array' => [1,2,3],
216
+ 'float' => 123.456,
217
+ 'hash' => {'boolean' => true},
218
+ 'nil' => nil,
219
+ 'symbol' => :name,
220
+ 'string' => 'hello world',
221
+ 'time' => Time.now.to_f,
222
+ 'regex' => /abc$/ix
223
+ }
224
+ retobj = @coll.insert(obj)
225
+ @coll.find({:_id => obj[:_id]}).to_a.callback do |ret|
226
+ ret.size.should == 1
227
+ ret[0].each_key do |key|
228
+ next if key == '_id'
229
+ ret[0][key].should == obj[key]
230
+ end
231
+ done
232
+ end
233
+ end
234
+
235
+ context "safe_insert" do
236
+ it "should succesfully save a document with no errors" do
237
+ @conn, @coll = connection_and_collection('safe.test')
238
+ @coll.safe_insert({"hello" => "world"}).callback do |ok|
239
+ ok.should be_a_kind_of BSON::ObjectId
240
+ done
241
+ end
242
+ end
243
+
244
+ it "should respond with an error when an invalid document is saved" do
245
+ @conn, @coll = connection_and_collection('safe.test')
246
+ @coll.create_index("hello", :unique => true)
247
+ a = {"hello" => "world"}
248
+ @coll.insert(a)
249
+ resp = @coll.safe_insert(a).errback do |err|
250
+ err[0].should == EM::Mongo::OperationFailure
251
+ done
252
+ end
253
+ end
254
+ end
255
+
102
256
  end
103
257
 
104
- it 'should find large sets of objects' do
105
- @conn, @coll = connection_and_collection
258
+ describe "update" do
259
+
260
+ it 'should update an object' do
261
+ @conn, @coll = connection_and_collection
106
262
 
107
- (0..1500).each { |n| @coll.insert({n.to_s => n.to_s}) }
108
- @coll.find do |res|
109
- res.size.should == EM::Mongo::DEFAULT_QUERY_DOCS
110
- @coll.find({}, {:limit => 1500}) do |res|
111
- res.size.should == 1500
263
+ id = @coll.insert('hello' => 'world')
264
+ @coll.update({'hello' => 'world'}, {'hello' => 'newworld'})
265
+ @coll.find({:_id => id},{}).to_a.callback do |res|
266
+ res[0]['hello'].should == 'newworld'
112
267
  done
113
268
  end
114
269
  end
270
+
271
+ it 'should update an object wxith $inc' do
272
+ @conn, @coll = connection_and_collection
273
+
274
+ id = @coll.insert('hello' => 'world')
275
+ @coll.update({'hello' => 'world'}, {'$inc' => {'count' => 1}})
276
+ @coll.find({:_id => id},{}).to_a.callback do |res|
277
+ res.first['hello'].should == 'world'
278
+ res.first['count'].should == 1
279
+ done
280
+ end
281
+ end
282
+
283
+ context "safe_update" do
284
+ it "should respond with an error when an invalid document is updated" do
285
+ @conn, @coll = connection_and_collection('safe.update.test')
286
+ @coll.create_index("x", :unique => true)
287
+ @coll.insert({"x" => 5})
288
+ @coll.insert({"x" => 10})
289
+
290
+ @coll.safe_update({},{"x" => 10}).errback do |err|
291
+ err[0].should == EM::Mongo::OperationFailure
292
+ done
293
+ end
294
+ end
295
+ end
296
+
115
297
  end
116
298
 
117
- it 'should update an object' do
118
- @conn, @coll = connection_and_collection
299
+ describe "save" do
300
+
301
+ it "should insert a record when no id is present" do
302
+ @conn, @coll = connection_and_collection
303
+ id = @coll.save("x" => 1)
304
+ @coll.find("x" => 1).to_a.callback do |result|
305
+ result[0]["_id"].should == id
306
+ done
307
+ end
308
+ end
119
309
 
120
- id = @coll.insert('hello' => 'world')
121
- @coll.update({'hello' => 'world'}, {'hello' => 'newworld'})
122
- @coll.find({:_id => id},{}) do |res|
123
- res[0]['hello'].should == 'newworld'
124
- done
310
+ it "should update a record when id is present" do
311
+ @conn, @coll = connection_and_collection
312
+ doc = {"x" => 1}
313
+ id = @coll.save(doc)
314
+ doc["x"] = 2
315
+ @coll.save(doc).should be_true
316
+ @coll.find().to_a.callback do |result|
317
+ result.count.should == 1
318
+ result[0]["x"].should == 2
319
+ done
320
+ end
321
+ end
322
+
323
+ context "safe_save" do
324
+ it "should respond with an error when an invalid document is updated" do
325
+ @conn, @coll = connection_and_collection('safe.save.test')
326
+ @coll.create_index("x", :unique => true)
327
+ @coll.save({"x" => 5})
328
+ @coll.save({"x" => 5})
329
+
330
+ @coll.safe_save({"x" => 5}).errback do |err|
331
+ err[0].should == EM::Mongo::OperationFailure
332
+ done
333
+ end
334
+ end
125
335
  end
126
336
  end
127
337
 
128
- it 'should update an object wxith $inc' do
129
- @conn, @coll = connection_and_collection
338
+ describe "remove" do
130
339
 
131
- id = @coll.insert('hello' => 'world')
132
- @coll.update({'hello' => 'world'}, {'$inc' => {'count' => 1}})
133
- @coll.find({:_id => id},{}) do |res|
134
- res.first['hello'].should == 'world'
135
- res.first['count'].should == 1
136
- done
340
+ it 'should remove an object' do
341
+ @conn, @coll = connection_and_collection
342
+
343
+ id = @coll.insert('hello' => 'world')
344
+ @coll.remove(:_id => id)
345
+ @coll.find({'hello' => "world"}).to_a.callback do |res|
346
+ res.size.should == 0
347
+ done
348
+ end
137
349
  end
350
+
351
+ it 'should remove all objects' do
352
+ @conn, @coll = connection_and_collection
353
+
354
+ @coll.insert('one' => 'one')
355
+ @coll.insert('two' => 'two')
356
+ @coll.remove
357
+ @coll.find.to_a.callback do |res|
358
+ res.size.should == 0
359
+ done
360
+ end
361
+ end
362
+
138
363
  end
139
364
 
140
- it 'should remove an object' do
141
- @conn, @coll = connection_and_collection
365
+ describe "find_and_modify" do
366
+
367
+ it "should find and modify a document" do
368
+ @conn, @coll = connection_and_collection
369
+ @coll << { :a => 1, :processed => false }
370
+ @coll << { :a => 2, :processed => false }
371
+ @coll << { :a => 3, :processed => false }
372
+
373
+ resp = @coll.find_and_modify(:query => {}, :sort => [['a', -1]], :update => {"$set" => {:processed => true}})
374
+ resp.callback do |doc|
375
+ doc['processed'].should_not be_true
376
+ @coll.find_one({:a=>3}).callback do |updated|
377
+ updated['processed'].should be_true
378
+ done
379
+ end
380
+ end
381
+ end
142
382
 
143
- id = @coll.insert('hello' => 'world')
144
- @coll.remove(:_id => id)
145
- @coll.find({'hello' => "world"}) do |res|
146
- res.size.should == 0
147
- done
383
+ it "should fail with invalid options" do
384
+ @conn, @coll = connection_and_collection
385
+ @coll << { :a => 1, :processed => false }
386
+ @coll << { :a => 2, :processed => false }
387
+ @coll << { :a => 3, :processed => false }
388
+
389
+ resp = @coll.find_and_modify(:blimey => {})
390
+ resp.errback do |err|
391
+ err[0].should == EM::Mongo::OperationFailure
392
+ done
393
+ end
148
394
  end
395
+
149
396
  end
150
397
 
151
- it 'should remove all objects' do
152
- @conn, @coll = connection_and_collection
398
+ describe "mapreduce" do
399
+ it "should map, and then reduce" do
400
+ @conn, @coll = connection_and_collection
401
+ @coll << { "user_id" => 1 }
402
+ @coll << { "user_id" => 2 }
403
+
404
+ m = "function() { emit(this.user_id, 1); }"
405
+ r = "function(k,vals) { return 1; }"
406
+
407
+ res = @coll.map_reduce(m, r, :out => 'foo')
408
+ res.callback do |collection|
409
+ collection.find_one({"_id" => 1}).callback do |doc|
410
+ doc.should_not be_nil
411
+ collection.find_one({"_id" => 2}).callback do |doc2|
412
+ doc2.should_not be_nil
413
+ done
414
+ end
415
+ end
416
+ end
417
+ end
153
418
 
154
- @coll.insert('one' => 'one')
155
- @coll.insert('two' => 'two')
156
- @coll.remove
157
- @coll.find do |res|
158
- res.size.should == 0
159
- done
419
+ it "should work with code objects" do
420
+ @conn, @coll = connection_and_collection
421
+ @coll << { "user_id" => 1 }
422
+ @coll << { "user_id" => 2 }
423
+
424
+ m = BSON::Code.new "function() { emit(this.user_id, 1); }"
425
+ r = BSON::Code.new "function(k,vals) { return 1; }"
426
+
427
+ res = @coll.map_reduce(m, r, :out => 'foo')
428
+ res.callback do |collection|
429
+ collection.find_one({"_id" => 1}).callback do |doc|
430
+ doc.should_not be_nil
431
+ collection.find_one({"_id" => 2}).callback do |doc2|
432
+ doc2.should_not be_nil
433
+ done
434
+ end
435
+ end
436
+ end
160
437
  end
438
+
439
+ it "should respect a query" do
440
+ @conn, @coll = connection_and_collection
441
+ @coll << { "user_id" => 1 }
442
+ @coll << { "user_id" => 2 }
443
+ @coll << { "user_id" => 3 }
444
+
445
+ m = BSON::Code.new "function() { emit(this.user_id, 1); }"
446
+ r = BSON::Code.new "function(k,vals) { return 1; }"
447
+
448
+ res = @coll.map_reduce(m, r, :query => {"user_id" => {"$gt" => 1}}, :out => 'foo')
449
+ res.callback do |collection|
450
+ collection.count .callback do |c|
451
+ c.should == 2
452
+ collection.find_one({"_id" => 2}).callback do |doc|
453
+ doc.should_not be_nil
454
+ collection.find_one({"_id" => 3}).callback do |doc2|
455
+ doc2.should_not be_nil
456
+ done
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ it "should return a raw response if requested" do
464
+ @conn, @coll = connection_and_collection
465
+ m = BSON::Code.new("function() { emit(this.user_id, 1); }")
466
+ r = BSON::Code.new("function(k,vals) { return 1; }")
467
+ res = @coll.map_reduce(m, r, :raw => true, :out => 'foo')
468
+ res.callback do |res|
469
+ res["result"].should_not be_nil
470
+ res["counts"].should_not be_nil
471
+ res["timeMillis"].should_not be_nil
472
+ done
473
+ end
474
+ end
475
+
476
+ it "should use an output collection if specified" do
477
+ @conn, @coll = connection_and_collection
478
+ output_collection = "test-map-coll"
479
+ m = BSON::Code.new("function() { emit(this.user_id, 1); }")
480
+ r = BSON::Code.new("function(k,vals) { return 1; }")
481
+ res = @coll.map_reduce(m, r, :raw => true, :out => output_collection)
482
+ res.callback do |res|
483
+ res["result"].should == output_collection
484
+ res["counts"].should_not be_nil
485
+ res["timeMillis"].should_not be_nil
486
+ done
487
+ end
488
+ end
489
+
161
490
  end
162
491
 
163
- it 'should insert a Time' do
164
- @conn, @coll = connection_and_collection
492
+ describe "distinct" do
493
+ it "shoud perform a distinct query" do
494
+ @conn, @coll = connection_and_collection
495
+ @coll.insert([{:a => 0, :b => {:c => "a"}},
496
+ {:a => 1, :b => {:c => "b"}},
497
+ {:a => 1, :b => {:c => "c"}},
498
+ {:a => 2, :b => {:c => "a"}},
499
+ {:a => 3},
500
+ {:a => 3}])
501
+
502
+ @coll.distinct(:a).callback do |vals|
503
+ vals.sort.should == [0,1,2,3]
504
+ @coll.distinct("b.c").callback do |vals2|
505
+ vals2.sort.should == ["a","b","c"]
506
+ done
507
+ end
508
+ end
509
+ end
510
+ it "should respect a query" do
511
+ @conn, @coll = connection_and_collection
512
+ @coll.insert([{:a => 0, :b => {:c => "a"}},
513
+ {:a => 1, :b => {:c => "b"}},
514
+ {:a => 1, :b => {:c => "c"}},
515
+ {:a => 2, :b => {:c => "a"}},
516
+ {:a => 3},
517
+ {:a => 3}])
518
+
519
+ @coll.distinct(:a, {:a => {"$gt" => 1}}).callback do |vals|
520
+ vals.sort.should == [2,3]
521
+ done
522
+ end
523
+ end
524
+ it "should respect a query and nested objects" do
525
+ @conn, @coll = connection_and_collection
526
+ @coll.insert([{:a => 0, :b => {:c => "a"}},
527
+ {:a => 1, :b => {:c => "b"}},
528
+ {:a => 1, :b => {:c => "c"}},
529
+ {:a => 2, :b => {:c => "a"}},
530
+ {:a => 3},
531
+ {:a => 3}])
532
+
533
+ @coll.distinct("b.c", {"b.c" => {"$ne" => "c"}}).callback do |vals|
534
+ vals.sort.should == ["a","b"]
535
+ done
536
+ end
537
+ end
538
+ end
165
539
 
166
- t = Time.now.utc.freeze
167
- @coll.insert('date' => t)
168
- @coll.find do |res|
169
- res[0]['date'].to_s.should == t.to_s
540
+ describe "group" do
541
+ it "should fail if missing required options" do
542
+ @conn, @coll = connection_and_collection
543
+ lambda { @coll.group(:initial => {}) }.should raise_error EM::Mongo::MongoArgumentError
544
+ lambda { @coll.group(:reduce => "foo") }.should raise_error EM::Mongo::MongoArgumentError
170
545
  done
171
546
  end
547
+ it "should group results using eval form" do
548
+ @conn, @coll = connection_and_collection
549
+ @coll.save("a" => 1)
550
+ @coll.save("b" => 1)
551
+ @initial = {"count" => 0}
552
+ @reduce_function = "function (obj, prev) { prev.count += inc_value; }"
553
+ @coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 0.5})).callback do |result|
554
+ result[0]["count"].should == 1
555
+ done
556
+ end
557
+ @coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 1})).callback do |result|
558
+ result[0]["count"].should == 2
559
+ done
560
+ end
561
+ @coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 2})).callback do |result|
562
+ result[0]["count"].should == 4
563
+ done
564
+ end
565
+ end
566
+ it "should finalize grouped results" do
567
+ @conn, @coll = connection_and_collection
568
+ @coll.save("a" => 1)
569
+ @coll.save("b" => 1)
570
+ @initial = {"count" => 0}
571
+ @reduce_function = "function (obj, prev) { prev.count += inc_value; }"
572
+ @finalize = "function(doc) {doc.f = doc.count + 200; }"
573
+ @coll.group(:initial => @initial, :reduce => BSON::Code.new(@reduce_function, {"inc_value" => 1}), :finalize => BSON::Code.new(@finalize)).callback do |results|
574
+ results[0]["f"].should == 202
575
+ done
576
+ end
577
+ end
172
578
  end
173
579
 
174
- it 'should insert a complex object' do
175
- @conn, @coll = connection_and_collection
580
+ describe "grouping with a key" do
581
+ it "should group" do
582
+ @conn, @coll = connection_and_collection
583
+ @coll.save("a" => 1, "pop" => 100)
584
+ @coll.save("a" => 1, "pop" => 100)
585
+ @coll.save("a" => 2, "pop" => 100)
586
+ @coll.save("a" => 2, "pop" => 100)
587
+ @initial = {"count" => 0, "foo" => 1}
588
+ @reduce_function = "function (obj, prev) { prev.count += obj.pop; }"
589
+ @coll.group(:key => :a, :initial => @initial, :reduce => @reduce_function).callback do |result|
590
+ result.all? {|r| r['count'] = 200 }.should be_true
591
+ done
592
+ end
593
+ end
594
+ end
176
595
 
177
- obj = {
178
- 'array' => [1,2,3],
179
- 'float' => 123.456,
180
- 'hash' => {'boolean' => true},
181
- 'nil' => nil,
182
- 'symbol' => :name,
183
- 'string' => 'hello world',
184
- 'time' => Time.now.to_f,
185
- 'regex' => /abc$/ix
186
- }
187
- retobj = @coll.insert(obj)
188
- @coll.find({:_id => obj[:_id]}) do |ret|
189
- ret.size.should == 1
190
- ret[0].each_key do |key|
191
- next if key == '_id'
192
- ret[0][key].should == obj[key]
596
+ describe "grouping with a function" do
597
+ it "should group results" do
598
+ @conn, @coll = connection_and_collection
599
+ @coll.save("a" => 1)
600
+ @coll.save("a" => 2)
601
+ @coll.save("a" => 3)
602
+ @coll.save("a" => 4)
603
+ @coll.save("a" => 5)
604
+ @initial = {"count" => 0}
605
+ @keyf = "function (doc) { if(doc.a % 2 == 0) { return {even: true}; } else {return {odd: true}} };"
606
+ @reduce = "function (obj, prev) { prev.count += 1; }"
607
+ @coll.group(:keyf => @keyf, :initial => @initial, :reduce => @reduce).callback do |results|
608
+ res = results.sort {|a,b| a['count'] <=> b['count']}
609
+ (res[0]['even'] && res[0]['count']).should == 2.0
610
+ (res[1]['odd'] && res[1]['count']) == 3.0
611
+ done
612
+ end
613
+ end
614
+
615
+ it "should group filtered results" do
616
+ @conn, @coll = connection_and_collection
617
+ @coll.save("a" => 1)
618
+ @coll.save("a" => 2)
619
+ @coll.save("a" => 3)
620
+ @coll.save("a" => 4)
621
+ @coll.save("a" => 5)
622
+ @initial = {"count" => 0}
623
+ @keyf = "function (doc) { if(doc.a % 2 == 0) { return {even: true}; } else {return {odd: true}} };"
624
+ @reduce = "function (obj, prev) { prev.count += 1; }"
625
+ @coll.group(:keyf => @keyf, :cond => {:a => {'$ne' => 2}},
626
+ :initial => @initial, :reduce => @reduce).callback do |results|
627
+ res = results.sort {|a, b| a['count'] <=> b['count']}
628
+ (res[0]['even'] && res[0]['count']).should == 1.0
629
+ (res[1]['odd'] && res[1]['count']) == 3.0
630
+ done
193
631
  end
194
- done
195
632
  end
196
633
  end
197
634
 
198
- it 'should find an object using nested properties' do
199
- @conn, @coll = connection_and_collection
635
+ context "indexes" do
636
+ it "should create an index using symbols" do
637
+ @conn, @collection = connection_and_collection('test-collection')
638
+ @collection.create_index :foo, :name => :bar
639
+ @collection.index_information.callback do |info|
640
+ info['bar'].should_not be_nil
641
+ done
642
+ end
643
+ end
644
+
645
+ it "should create a geospatial index" do
646
+ @conn, @geo = connection_and_collection('geo')
647
+ @geo.save({'loc' => [-100, 100]})
648
+ @geo.create_index([['loc', EM::Mongo::GEO2D]])
649
+ @geo.index_information.callback do |info|
650
+ info['loc_2d'].should_not be_nil
651
+ done
652
+ end
653
+ end
654
+
655
+ it "should create a unique index" do
656
+ @conn, @collection = connection_and_collection('test-collection')
657
+ @collection.create_index([['a', EM::Mongo::ASCENDING]], :unique => true)
658
+ @collection.index_information.callback do |info|
659
+ info['a_1']['unique'].should == true
660
+ done
661
+ end
662
+ end
200
663
 
201
- @coll.insert({
202
- 'name' => 'Google',
203
- 'address' => {
204
- 'cxity' => 'Mountain View',
205
- 'state' => 'California'}
206
- })
664
+ it "should create an index in the background" do
665
+ @conn, @collection = connection_and_collection('test-collection')
666
+ @collection.create_index([['b', EM::Mongo::ASCENDING]], :background => true)
667
+ @collection.index_information.callback do |info|
668
+ info['b_1']['background'].should == true
669
+ done
670
+ end
671
+ end
207
672
 
208
- @coll.first('address.cxity' => 'Mountain View') do |res|
209
- res['name'].should == 'Google'
673
+ it "should require an array of arrays" do
674
+ @conn, @collection = connection_and_collection('test-collection')
675
+ proc { @collection.create_index(['c', EM::Mongo::ASCENDING]) }.should raise_error
210
676
  done
211
677
  end
212
- end
213
678
 
214
- it 'should find objects wxith specific values' do
215
- @conn, @coll = connection_and_collection
679
+ it "should enforce proper index types" do
680
+ @conn, @collection = connection_and_collection('test-collection')
681
+ proc { @collection.create_index([['c', 'blah']]) }.should raise_error
682
+ done
683
+ end
216
684
 
217
- number_hash.each do |num, word|
218
- @coll.insert({'num' => num, 'word' => word})
685
+ it "should allow an alernate name to be specified" do
686
+ @conn, @collection = connection_and_collection('test-collection')
687
+ @collection.create_index :bar, :name => 'foo_index'
688
+ @collection.index_information.callback do |info|
689
+ info['foo_index'].should_not be_nil
690
+ done
691
+ end
219
692
  end
220
693
 
221
- @coll.find({'num' => {'$in' => [1,3,5]}}) do |res|
222
- res.size.should == 3
223
- res.map{|r| r['num'] }.sort.should == [1,3,5]
694
+ it "should generate indexes in the proper order" do
695
+ @conn, @collection = connection_and_collection('test-collection')
696
+ @collection.should_receive(:insert_documents) do |sel, coll|
697
+ sel[0][:name].should == 'b_1_a_1'
698
+ end
699
+ @collection.create_index([['b',1],['a',1]])
224
700
  done
225
701
  end
226
- end
227
-
228
- it 'should find objects greater than something' do
229
- @conn, @coll = connection_and_collection
230
702
 
231
- number_hash.each do |num, word|
232
- @coll.insert('num' => num, 'word' => word)
703
+ it "should allow multiple calls to create_index" do
704
+ @conn, @collection = connection_and_collection('test-collection')
705
+ @collection.create_index([['a',1]]).should be_true
706
+ @collection.create_index([['a',1]]).should be_true
707
+ done
233
708
  end
234
709
 
235
- @coll.find({'num' => {'$gt' => 3}}) do |res|
236
- res.size.should == 6
237
- res.map{|r| r['num'] }.sort.should == [4,5,6,7,8,9]
710
+ it "should allow the creation of multiple indexes" do
711
+ @conn, @collection = connection_and_collection('test-collection')
712
+ @collection.create_index([['a',1]]).should be_true
713
+ @collection.create_index([['b',1]]).should be_true
238
714
  done
239
715
  end
716
+
717
+ it "should return a properly ordered index info" do
718
+ @conn, @collection = connection_and_collection('test-collection')
719
+ @collection.create_index([['b',1],['a',1]])
720
+ @collection.index_information.callback do |info|
721
+ info['b_1_a_1'].should_not be_nil
722
+ done
723
+ end
724
+ end
725
+
726
+ it "should drop an index" do
727
+ @conn, @collection = connection_and_collection('test-collection')
728
+ @collection.create_index([['a',EM::Mongo::ASCENDING]])
729
+ @collection.index_information.callback do |info|
730
+ info['a_1'].should_not be_nil
731
+ @collection.drop_index([['a',EM::Mongo::ASCENDING]]).callback do
732
+ @collection.index_information.callback do |info|
733
+ info['a_1'].should be_nil
734
+ done
735
+ end
736
+ end
737
+ end
738
+
739
+ end
240
740
  end
241
741
 
242
742
  it 'should handle multiple pending queries' do
@@ -246,7 +746,7 @@ describe EMMongo::Collection do
246
746
  received = 0
247
747
 
248
748
  10.times do |n|
249
- @coll.first("_id" => id) do |res|
749
+ @coll.first("_id" => id).callback do |res|
250
750
  received += 1
251
751
  done
252
752
  end