em-mongo 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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