em-mongo 0.1.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/lib/em-mongo.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'eventmachine'
2
+ require 'pp'
3
+
4
+ begin
5
+ require 'securerandom'
6
+ rescue LoadError
7
+ require 'uuid'
8
+ end
9
+
10
+ module EM::Mongo
11
+ VERSION = '0.0.1'
12
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
13
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
14
+
15
+ class Util
16
+ def self.unique_id
17
+ if defined? SecureRandom
18
+ SecureRandom.hex(12)
19
+ else
20
+ UUID.new.generate(:compact).gsub(/^(.{20})(.{8})(.{4})$/){ $1+$3 }
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/connection")
27
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/buffer")
28
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/collection")
29
+
30
+ EMMongo = EM::Mongo
@@ -0,0 +1,414 @@
1
+ if [].map.respond_to? :with_index
2
+ class Array
3
+ def enum_with_index
4
+ each.with_index
5
+ end
6
+ end
7
+ else
8
+ require 'enumerator'
9
+ end
10
+
11
+ module EM::Mongo
12
+ class Buffer
13
+ class Overflow < Exception; end
14
+ class InvalidType < Exception; end
15
+
16
+ def initialize data = ''
17
+ @data = data
18
+ @pos = 0
19
+ end
20
+ attr_reader :pos
21
+
22
+ def data
23
+ @data.clone
24
+ end
25
+ alias :contents :data
26
+ alias :to_s :data
27
+
28
+ def << data
29
+ @data << data.to_s
30
+ self
31
+ end
32
+
33
+ def length
34
+ @data.length
35
+ end
36
+ alias :size :length
37
+
38
+ def empty?
39
+ pos == length
40
+ end
41
+
42
+ def rewind
43
+ @pos = 0
44
+ end
45
+
46
+ def read *types
47
+ values = types.map do |type|
48
+ case type
49
+ when :byte
50
+ _read(1, 'C')
51
+ when :short
52
+ _read(2, 'n')
53
+ when :int
54
+ _read(4, 'I')
55
+ when :double
56
+ _read(8, 'd')
57
+ when :long
58
+ _read(4, 'N')
59
+ when :longlong
60
+ upper, lower = _read(8, 'NN')
61
+ upper << 32 | lower
62
+ when :cstring
63
+ str = @data.unpack("@#{@pos}Z*").first
64
+ @data.slice!(@pos, str.size+1)
65
+ str
66
+ when :oid
67
+ # _read(12, 'i*').enum_with_index.inject([]) do |a, (num, i)|
68
+ # a[ i == 0 ? 1 : i == 1 ? 0 : i ] = num # swap first two
69
+ # a
70
+ # end.map do |num|
71
+ # num.to_s(16).rjust(8,'0')
72
+ # end.join('')
73
+
74
+ _read(12, 'I*').map do |num|
75
+ num.to_s(16).rjust(8,'0')
76
+ end.join('')
77
+ when :bson
78
+ bson = {}
79
+ data = Buffer.new _read(read(:int)-4)
80
+
81
+ until data.empty?
82
+ type = data.read(:byte)
83
+ next if type == 0 # end of object
84
+
85
+ key = data.read(:cstring).intern
86
+
87
+ bson[key] = case type
88
+ when 1 # number
89
+ data.read(:double)
90
+ when 2 # string
91
+ data.read(:int)
92
+ data.read(:cstring)
93
+ when 3 # object
94
+ data.read(:bson)
95
+ when 4 # array
96
+ data.read(:bson).inject([]){ |a, (k,v)| a[k.to_s.to_i] = v; a }
97
+ when 5 # binary
98
+ data._read data.read(:int)
99
+ when 6 # undefined
100
+ when 7 # oid
101
+ data.read(:oid)
102
+ when 8 # bool
103
+ data.read(:byte) == 1 ? true : false
104
+ when 9 # time
105
+ Time.at data.read(:longlong)/1000.0
106
+ when 10 # nil
107
+ nil
108
+ when 11 # regex
109
+ source = data.read(:cstring)
110
+ options = data.read(:cstring).split('')
111
+
112
+ options = { 'i' => 1, 'm' => 2, 'x' => 4 }.inject(0) do |s, (o, n)|
113
+ s |= n if options.include?(o)
114
+ s
115
+ end
116
+
117
+ Regexp.new(source, options)
118
+ when 12 # ref
119
+ ref = {}
120
+ ref[:_ns] = data.read(:cstring)
121
+ ref[:_id] = data.read(:oid)
122
+ ref
123
+ when 13 # code
124
+ data.read(:int)
125
+ data.read(:cstring)
126
+ when 14 # symbol
127
+ data.read(:int)
128
+ data.read(:cstring).intern
129
+ end
130
+ end
131
+
132
+ bson
133
+ else
134
+ raise InvalidType, "Cannot read data of type #{type}"
135
+ end
136
+ end
137
+
138
+ types.size == 1 ? values.first : values
139
+ end
140
+
141
+ def write *args
142
+ args.each_slice(2) do |type, data|
143
+ case type
144
+ when :byte
145
+ _write(data, 'C')
146
+ when :short
147
+ _write(data, 'n')
148
+ when :int
149
+ _write(data, 'I')
150
+ when :double
151
+ _write(data, 'd')
152
+ when :long
153
+ _write(data, 'N')
154
+ when :longlong
155
+ lower = data & 0xffffffff
156
+ upper = (data & ~0xffffffff) >> 32
157
+ _write([upper, lower], 'NN')
158
+ when :cstring
159
+ _write(data.to_s + "\0")
160
+ when :oid
161
+ # data.scan(/.{8}/).enum_with_index.inject([]) do |a, (num, i)|
162
+ # a[ i == 0 ? 1 : i == 1 ? 0 : i ] = num # swap first two
163
+ # a
164
+ # end.each do |num|
165
+ # write(:int, num.to_i(16))
166
+ # end
167
+ data.scan(/.{8}/).each do |num|
168
+ write(:int, num.to_i(16))
169
+ end
170
+ when :bson
171
+ buf = Buffer.new
172
+ data.each do |key,value|
173
+ case value
174
+ when Numeric
175
+ id = 1
176
+ type = :double
177
+ when String
178
+ if key == :_id
179
+ id = 7
180
+ type = :oid
181
+ else
182
+ id = 2
183
+ type = proc{ |out|
184
+ out.write(:int, value.length+1)
185
+ out.write(:cstring, value)
186
+ }
187
+ end
188
+ when Hash
189
+ if data.keys.map{|k| k.to_s}.sort == %w[ _id _ns ]
190
+ id = 12 # ref
191
+ type = proc{ |out|
192
+ out.write(:cstring, data[:_ns])
193
+ out.write(:oid, data[:_id])
194
+ }
195
+ else
196
+ id = 3
197
+ type = :bson
198
+ end
199
+ when Array
200
+ id = 4
201
+ type = :bson
202
+ value = value.enum_with_index.inject({}){ |h, (v, i)| h.update i => v }
203
+ when TrueClass, FalseClass
204
+ id = 8
205
+ type = :byte
206
+ value = value ? 1 : 0
207
+ when Time
208
+ id = 9
209
+ type = :longlong
210
+ value = value.to_i * 1000 + (value.tv_usec/1000)
211
+ when NilClass
212
+ id = 10
213
+ type = nil
214
+ when Regexp
215
+ id = 11
216
+ type = proc{ |out|
217
+ out.write(:cstring, value.source)
218
+ out.write(:cstring, { 'i' => 1, 'm' => 2, 'x' => 4 }.inject('') do |s, (o, n)|
219
+ s += o if value.options & n > 0
220
+ s
221
+ end)
222
+ }
223
+ when Symbol
224
+ id = 14
225
+ type = proc{ |out|
226
+ out.write(:int, value.to_s.length+1)
227
+ out.write(:cstring, value.to_s)
228
+ }
229
+ end
230
+
231
+ buf.write(:byte, id)
232
+ buf.write(:cstring, key)
233
+
234
+ if type.respond_to? :call
235
+ type.call(buf)
236
+ elsif type
237
+ buf.write(type, value)
238
+ end
239
+ end
240
+ buf.write(:byte, 0) # eoo
241
+
242
+ write(:int, buf.size+4)
243
+ _write(buf.to_s)
244
+ else
245
+ raise InvalidType, "Cannot write data of type #{type}"
246
+ end
247
+ end
248
+ self
249
+ end
250
+
251
+ def _peek(pos, size, pack)
252
+ data = @data[pos,size]
253
+ data = data.unpack(pack)
254
+ data = data.pop if data.size == 1
255
+ data
256
+ end
257
+
258
+ def _read size, pack = nil
259
+ if @pos + size > length
260
+ raise Overflow
261
+ else
262
+ data = @data[@pos,size]
263
+ @data[@pos,size] = ''
264
+ if pack
265
+ data = data.unpack(pack)
266
+ data = data.pop if data.size == 1
267
+ end
268
+ data
269
+ end
270
+ end
271
+
272
+ def _write data, pack = nil
273
+ data = [*data].pack(pack) if pack
274
+ @data[@pos,0] = data
275
+ @pos += data.length
276
+ end
277
+
278
+ def extract
279
+ begin
280
+ cur_data, cur_pos = @data.clone, @pos
281
+ yield self
282
+ rescue Overflow
283
+ @data, @pos = cur_data, cur_pos
284
+ nil
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ if $0 =~ /bacon/ or $0 == __FILE__
291
+ require 'bacon'
292
+ Bacon.summary_on_exit
293
+ include Mongo
294
+
295
+ describe Buffer do
296
+ before do
297
+ @buf = Buffer.new
298
+ end
299
+
300
+ should 'have contents' do
301
+ @buf.contents.should == ''
302
+ end
303
+
304
+ should 'initialize with data' do
305
+ @buf = Buffer.new('abc')
306
+ @buf.contents.should == 'abc'
307
+ end
308
+
309
+ should 'append raw data' do
310
+ @buf << 'abc'
311
+ @buf << 'def'
312
+ @buf.contents.should == 'abcdef'
313
+ end
314
+
315
+ should 'append other buffers' do
316
+ @buf << Buffer.new('abc')
317
+ @buf.data.should == 'abc'
318
+ end
319
+
320
+ should 'have a position' do
321
+ @buf.pos.should == 0
322
+ end
323
+
324
+ should 'have a length' do
325
+ @buf.length.should == 0
326
+ @buf << 'abc'
327
+ @buf.length.should == 3
328
+ end
329
+
330
+ should 'know the end' do
331
+ @buf.empty?.should == true
332
+ end
333
+
334
+ should 'read and write data' do
335
+ @buf._write('abc')
336
+ @buf.rewind
337
+ @buf._read(2).should == 'ab'
338
+ @buf._read(1).should == 'c'
339
+ end
340
+
341
+ should 'raise on overflow' do
342
+ lambda{ @buf._read(1) }.should.raise Buffer::Overflow
343
+ end
344
+
345
+ should 'raise on invalid types' do
346
+ lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
347
+ lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
348
+ end
349
+
350
+ { :byte => 0b10101010,
351
+ :short => 100,
352
+ :int => 65536,
353
+ :double => 123.456,
354
+ :long => 100_000_000,
355
+ :longlong => 666_555_444_333_222_111,
356
+ :cstring => 'hello',
357
+ }.each do |type, value|
358
+
359
+ should "read and write a #{type}" do
360
+ @buf.write(type, value)
361
+ @buf.rewind
362
+ @buf.read(type).should == value
363
+ @buf.should.be.empty
364
+ end
365
+
366
+ end
367
+
368
+ should "read and write multiple times" do
369
+ arr = [ :byte, 0b10101010, :short, 100 ]
370
+ @buf.write(*arr)
371
+ @buf.rewind
372
+ @buf.read(arr.shift).should == arr.shift
373
+ @buf.read(arr.shift).should == arr.shift
374
+ end
375
+
376
+ [
377
+ { :num => 1 },
378
+ { :symbol => :abc },
379
+ { :object => {} },
380
+ { :array => [1, 2, 3] },
381
+ { :string => 'abcdefg' },
382
+ { :oid => { :_id => '51d9ca7053a2012be4ecd660' } },
383
+ { :ref => { :_ns => 'namespace',
384
+ :_id => '51d9ca7053a2012be4ecd660' } },
385
+ { :boolean => true },
386
+ { :time => Time.at(Time.now.to_i) },
387
+ { :null => nil },
388
+ { :regex => /^.*?def/im }
389
+ ]. each do |bson|
390
+
391
+ should "read and write bson with #{bson.keys.first}s" do
392
+ @buf.write(:bson, bson)
393
+ @buf.rewind
394
+ @buf.read(:bson).should == bson
395
+ @buf.should.be.empty
396
+ end
397
+
398
+ end
399
+
400
+ should 'do transactional reads with #extract' do
401
+ @buf.write :byte, 8
402
+ orig = @buf.to_s
403
+
404
+ @buf.rewind
405
+ @buf.extract do |b|
406
+ b.read :byte
407
+ b.read :short
408
+ end
409
+
410
+ @buf.pos.should == 0
411
+ @buf.data.should == orig
412
+ end
413
+ end
414
+ end
@@ -0,0 +1,46 @@
1
+ module EM::Mongo
2
+ class Collection
3
+ attr_accessor :connection
4
+
5
+ def initialize(db, ns, connection = nil)
6
+ @db = db || "db"
7
+ @ns = ns || "ns"
8
+ @name = [@db,@ns].join('.')
9
+ @connection = connection || EM::Mongo::Connection.new
10
+ end
11
+
12
+ def find(selector={}, opts={}, &blk)
13
+ raise "find requires a block" if not block_given?
14
+
15
+ skip = opts.delete(:skip) || 0
16
+ limit = opts.delete(:limit) || 0
17
+
18
+ @connection.find(@name, skip, limit, selector, &blk)
19
+ end
20
+
21
+ def first(selector={}, opts={}, &blk)
22
+ opts[:limit] = 1
23
+ find(selector, opts) do |res|
24
+ yield res.first
25
+ end
26
+ end
27
+
28
+ def insert(obj)
29
+ obj[:_id] ||= EM::Mongo::Util.unique_id
30
+ @connection.insert(@name, obj)
31
+ obj
32
+ end
33
+
34
+ def remove(obj = {})
35
+ @connection.delete(@name, obj)
36
+ true
37
+ end
38
+
39
+ #def method_missing meth
40
+ # puts meth
41
+ # raise ArgumentError, 'collection cannot take block' if block_given?
42
+ # (@subns ||= {})[meth] ||= self.class.new("#{@ns}.#{meth}", @client)
43
+ #end
44
+
45
+ end
46
+ end
@@ -0,0 +1,209 @@
1
+ module EM::Mongo
2
+ DEFAULT_IP = "127.0.0.1"
3
+ DEFAULT_PORT = 27017
4
+ DEFAULT_DB = "db"
5
+ DEFAULT_NS = "ns"
6
+
7
+ module EMConnection
8
+ class Error < Exception;
9
+ class ConnectionNotBound
10
+ end
11
+ end
12
+
13
+ include EM::Deferrable
14
+
15
+ RESERVED = 0
16
+ OP_REPLY = 1
17
+ OP_MSG = 1000
18
+ OP_UPDATE = 2001
19
+ OP_INSERT = 2002
20
+ OP_QUERY = 2004
21
+ OP_DELETE = 2006
22
+
23
+ attr_reader :connection
24
+
25
+ def responses_pending?
26
+ @responses.size >= 1
27
+ end
28
+
29
+ def connected?
30
+ @is_connected
31
+ end
32
+
33
+ # RMongo interface
34
+ def collection(db = DEFAULT_DB, ns = DEFAULT_NS)
35
+ raise "Not connected" if not connected?
36
+ EM::Mongo::Collection.new(db, ns, self)
37
+ end
38
+
39
+ # MongoDB Commands
40
+
41
+ def send_command(id, *args, &blk)
42
+ request_id = @request_id += 1
43
+
44
+ callback {
45
+ buf = Buffer.new
46
+ buf.write :int, request_id,
47
+ :int, response = 0,
48
+ :int, operation = id
49
+
50
+ buf.write *args
51
+ send_data [ buf.size + 4 ].pack('i') # header length first
52
+ send_data buf.data
53
+ }
54
+
55
+ @responses[request_id] = blk if blk
56
+ request_id
57
+ end
58
+
59
+ def insert(collection_name, documents)
60
+ # XXX multiple documents?
61
+ send_command(OP_INSERT, :int, RESERVED,
62
+ :cstring, collection_name,
63
+ :bson, documents)
64
+ end
65
+
66
+ def delete(collection_name, selector)
67
+ send_command(OP_DELETE, :int, RESERVED,
68
+ :cstring, collection_name,
69
+ :int, RESERVED,
70
+ :bson, selector)
71
+ end
72
+
73
+ def find(collection_name, skip, limit, query, &blk)
74
+ send_command(OP_QUERY, :int, RESERVED,
75
+ :cstring, collection_name,
76
+ :int, skip,
77
+ :int, limit,
78
+ :bson, query,
79
+ &blk)
80
+ end
81
+
82
+
83
+ # EM hooks
84
+ def initialize(options={})
85
+ @request_id = 0
86
+ @responses = {}
87
+ @is_connected = false
88
+ @host = options[:host] || DEFAULT_IP
89
+ @port = options[:port] || DEFAULT_PORT
90
+
91
+ @on_close = proc{
92
+ raise Error, "could not connect to server #{@host}:#{@port}"
93
+ }
94
+ timeout options[:timeout] if options[:timeout]
95
+ errback{ @on_close.call }
96
+ end
97
+
98
+ def self.connect(host = DEFAULT_IP, port = DEFAUL_PORT, timeout = nil)
99
+ opt = {:host => host, :port => port, :timeout => timeout}
100
+ EM.connect(host, port, self, opt)
101
+ end
102
+
103
+ def connection_completed
104
+ log 'connected'
105
+ @buf = Buffer.new
106
+ @is_connected = true
107
+ @on_close = proc{
108
+ }
109
+ succeed
110
+ end
111
+
112
+ def receive_data data
113
+ log "receive_data: #{data.size}"#, data
114
+
115
+ @buf << data
116
+
117
+ until @buf.empty?
118
+ size = @buf._peek(0, 4, 'I')
119
+
120
+ break unless @buf.size >= size-4
121
+
122
+ size, id, response, operation = @buf.read(:int, :int, :int, :int)
123
+ reserved, cursor, start, num = @buf.read(:int, :longlong, :int, :int)
124
+
125
+ results = (1..num).map do
126
+ @buf.read(:bson)
127
+ end
128
+
129
+ if cb = @responses.delete(response)
130
+ cb.call(results)
131
+ end
132
+ close_connection if @close_pending and @responses.size == 0
133
+ end
134
+ end
135
+
136
+ def send_data data
137
+ log "send_data:#{data.size}"#, data
138
+ super data
139
+ end
140
+
141
+ def unbind
142
+ log "unbind"
143
+ @is_connected = false
144
+ @on_close.call unless $!
145
+ end
146
+
147
+ def close
148
+ log "close"
149
+ @on_close = proc{ yield if block_given? }
150
+ if @responses.empty?
151
+ close_connection
152
+ else
153
+ @close_pending = true
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def log *args
160
+ return
161
+ pp args
162
+ puts
163
+ end
164
+
165
+ end
166
+ end
167
+
168
+ # Make EM::Mongo look like mongo-ruby-driver
169
+ module EM::Mongo
170
+ class Database
171
+ def initialize(name = DEFAULT_DB, connection = nil)
172
+ @db_name = name
173
+ @em_connection = connection || EM::Mongo::Connection.new
174
+ @collection = nil
175
+ end
176
+
177
+ def collection(name = DEFAULT_NS)
178
+ @collection = EM::Mongo::Collection.new(@db_name, name, @em_connection)
179
+ end
180
+
181
+ def close
182
+ @em_connection.close
183
+ end
184
+ end
185
+ class Connection
186
+ def initialize(host = DEFAULT_IP, port = DEFAULT_PORT, timeout = nil)
187
+ @em_connection = EMConnection.connect(host, port, timeout)
188
+ @db = {}
189
+ self
190
+ end
191
+
192
+ def db(name = DEFAULT_DB)
193
+ @db[name] = EM::Mongo::Database.new(name, @em_connection)
194
+ end
195
+
196
+ def collection(db = DEFAULT_DB, ns = DEFAULT_NS)
197
+ @em_connection.collection(db, ns)
198
+ end
199
+
200
+ def close
201
+ @em_connection.close
202
+ end
203
+
204
+ def connected?
205
+ @em_connection.connected?
206
+ end
207
+
208
+ end
209
+ end
@@ -0,0 +1,123 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ include EMMongo
4
+
5
+ describe Buffer do
6
+ before do
7
+ @buf = Buffer.new
8
+ end
9
+
10
+ it 'should have contents' do
11
+ @buf.contents.should == ''
12
+ end
13
+
14
+ it 'should initialize with data' do
15
+ @buf = Buffer.new('abc')
16
+ @buf.contents.should == 'abc'
17
+ end
18
+
19
+ it 'should append raw data' do
20
+ @buf << 'abc'
21
+ @buf << 'def'
22
+ @buf.contents.should == 'abcdef'
23
+ end
24
+
25
+ it 'should append other buffers' do
26
+ @buf << Buffer.new('abc')
27
+ @buf.data.should == 'abc'
28
+ end
29
+
30
+ it 'should have a position' do
31
+ @buf.pos.should == 0
32
+ end
33
+
34
+ it 'should have a length' do
35
+ @buf.length.should == 0
36
+ @buf << 'abc'
37
+ @buf.length.should == 3
38
+ end
39
+
40
+ it 'should know the end' do
41
+ @buf.empty?.should == true
42
+ end
43
+
44
+ it 'should read and write data' do
45
+ @buf._write('abc')
46
+ @buf.rewind
47
+ @buf._read(2).should == 'ab'
48
+ @buf._read(1).should == 'c'
49
+ end
50
+
51
+ it 'should raise on overflow' do
52
+ lambda{ @buf._read(1) }.should { raise Buffer::Overflow }
53
+ end
54
+
55
+ it 'should raise on invalid types' do
56
+ lambda{ @buf.read(:junk) }.should { raise Buffer::InvalidType }
57
+ lambda{ @buf.write(:junk, 1) }.should { raise Buffer::InvalidType }
58
+ end
59
+
60
+ { :byte => 0b10101010,
61
+ :short => 100,
62
+ :int => 65536,
63
+ :double => 123.456,
64
+ :long => 100_000_000,
65
+ :longlong => 666_555_444_333_222_111,
66
+ :cstring => 'hello',
67
+ }.each do |type, value|
68
+
69
+ it "should read and write a #{type}" do
70
+ @buf.write(type, value)
71
+ @buf.rewind
72
+ @buf.read(type).should == value
73
+ #@buf.should.be.empty
74
+ end
75
+
76
+ end
77
+
78
+ it "should read and write multiple times" do
79
+ arr = [ :byte, 0b10101010, :short, 100 ]
80
+ @buf.write(*arr)
81
+ @buf.rewind
82
+ @buf.read(arr.shift).should == arr.shift
83
+ @buf.read(arr.shift).should == arr.shift
84
+ end
85
+
86
+ [
87
+ { :num => 1 },
88
+ { :symbol => :abc },
89
+ { :object => {} },
90
+ { :array => [1, 2, 3] },
91
+ { :string => 'abcdefg' },
92
+ { :oid => { :_id => '51d9ca7053a2012be4ecd660' } },
93
+ { :ref => { :_ns => 'namespace',
94
+ :_id => '51d9ca7053a2012be4ecd660' } },
95
+ { :boolean => true },
96
+ { :time => Time.at(Time.now.to_i) },
97
+ { :null => nil },
98
+ { :regex => /^.*?def/im }
99
+ ]. each do |bson|
100
+
101
+ it "should read and write bson with #{bson.keys.first}s" do
102
+ @buf.write(:bson, bson)
103
+ @buf.rewind
104
+ @buf.read(:bson).should == bson
105
+ #@buf.should.be.empty
106
+ end
107
+
108
+ end
109
+
110
+ it 'should do transactional reads with #extract' do
111
+ @buf.write :byte, 8
112
+ orig = @buf.to_s
113
+
114
+ @buf.rewind
115
+ @buf.extract do |b|
116
+ b.read :byte
117
+ b.read :short
118
+ end
119
+
120
+ @buf.pos.should == 0
121
+ @buf.data.should == orig
122
+ end
123
+ end
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe EMMongo::Collection do
4
+ include EM::SpecHelper
5
+
6
+ before(:all) do
7
+ @numbers = {
8
+ 1 => 'one',
9
+ 2 => 'two',
10
+ 3 => 'three',
11
+ 4 => 'four',
12
+ 5 => 'five',
13
+ 6 => 'six',
14
+ 7 => 'seven',
15
+ 8 => 'eight',
16
+ 9 => 'nine'
17
+ }
18
+ end
19
+
20
+ after(:all) do
21
+ end
22
+
23
+ it 'should insert an object' do
24
+ EM::Spec::Mongo.collection do |collection|
25
+ obj = collection.insert(:hello => 'world')
26
+ obj.keys.should include :_id
27
+ obj[:_id].should be_a_kind_of String
28
+ obj[:_id].length.should == 24
29
+ EM::Spec::Mongo.close
30
+ end
31
+ end
32
+
33
+ it 'should find an object' do
34
+ EM::Spec::Mongo.collection do |collection|
35
+ collection.insert(:hello => 'world')
36
+ r = collection.find({:hello => "world"},{}) do |res|
37
+ res.size.should >= 1
38
+ res[0][:hello].should == "world"
39
+ EM::Spec::Mongo.close
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'should find all objects' do
45
+ EM::Spec::Mongo.collection do |collection|
46
+ collection.insert(:one => 'one')
47
+ collection.insert(:two => 'two')
48
+ collection.find do |res|
49
+ res.size.should >= 2
50
+ EM::Spec::Mongo.close
51
+ end
52
+ end
53
+ end
54
+
55
+ it 'should remove an object' do
56
+ EM::Spec::Mongo.collection do |collection|
57
+ obj = collection.insert(:hello => 'world')
58
+ collection.remove(obj)
59
+ collection.find({:hello => "world"}) do |res|
60
+ res.size.should == 0
61
+ EM::Spec::Mongo.close
62
+ end
63
+ end
64
+ end
65
+
66
+ it 'should remove all objects' do
67
+ EM::Spec::Mongo.collection do |collection|
68
+ collection.insert(:one => 'one')
69
+ collection.insert(:two => 'two')
70
+ collection.remove
71
+ collection.find do |res|
72
+ res.size.should == 0
73
+ EM::Spec::Mongo.close
74
+ end
75
+ end
76
+ end
77
+
78
+ it 'should insert a complex object' do
79
+ EM::Spec::Mongo.collection do |collection|
80
+ obj = {
81
+ :array => [1,2,3],
82
+ :float => 123.456,
83
+ :hash => {:boolean => true},
84
+ :nil => nil,
85
+ :symbol => :name,
86
+ :string => 'hello world',
87
+ :time => Time.at(Time.now.to_i),
88
+ :regex => /abc$/ix
89
+ }
90
+ obj = collection.insert(obj)
91
+ collection.find(:_id => obj[:_id]) do |ret|
92
+ ret.should == [ obj ]
93
+ EM::Spec::Mongo.close
94
+ end
95
+ end
96
+ end
97
+
98
+ it 'should find an object using nested properties' do
99
+ EM::Spec::Mongo.collection do |collection|
100
+ collection.insert({
101
+ :name => 'Google',
102
+ :address => {
103
+ :city => 'Mountain View',
104
+ :state => 'California'}
105
+ })
106
+
107
+ collection.first('address.city' => 'Mountain View') do |res|
108
+ res[:name].should == 'Google'
109
+ EM::Spec::Mongo.close
110
+ end
111
+ end
112
+ end
113
+
114
+ it 'should find objects with specific values' do
115
+ EM::Spec::Mongo.collection do |collection|
116
+ @numbers.each do |num, word|
117
+ collection.insert(:num => num, :word => word)
118
+ end
119
+
120
+ collection.find({:num => {'$in' => [1,3,5]}}) do |res|
121
+ res.size.should == 3
122
+ res.map{|r| r[:num] }.sort.should == [1,3,5]
123
+ EM::Spec::Mongo.close
124
+ end
125
+ end
126
+ end
127
+
128
+ it 'should find objects greater than something' do
129
+ EM::Spec::Mongo.collection do |collection|
130
+ @numbers.each do |num, word|
131
+ collection.insert(:num => num, :word => word)
132
+ end
133
+
134
+ collection.find({:num => {'$gt' => 3}}) do |res|
135
+ res.size.should == 6
136
+ res.map{|r| r[:num] }.sort.should == [4,5,6,7,8,9]
137
+ EM::Spec::Mongo.close
138
+ end
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe EMMongo::Connection do
4
+ include EM::SpecHelper
5
+
6
+ it 'should connect' do
7
+ em do
8
+ connection = EMMongo::Connection.new
9
+ EM.next_tick do
10
+ connection.connected?.should == true
11
+ done
12
+ end
13
+ end
14
+ end
15
+
16
+ it 'should close' do
17
+ em do
18
+ connection = EMMongo::Connection.new
19
+ EM.next_tick do
20
+ connection.connected?.should == true
21
+ connection.close
22
+ end
23
+ EM.add_timer(1) do
24
+ EM.next_tick do
25
+ connection.connected?.should == false
26
+ done
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ # Support the old RMongo interface for now
33
+ it 'should instantiate a Collection' do
34
+ EM::Spec::Mongo.connection do |connection|
35
+ connection.collection.is_a?(Collection).should == true
36
+ EM::Spec::Mongo.close
37
+ end
38
+ end
39
+
40
+ it 'should instantiate a Databse' do
41
+ EM::Spec::Mongo.connection do |connection|
42
+ db1 = connection.db
43
+ db1.is_a?(Database).should == true
44
+ db2 = connection.db('db2')
45
+ db2.is_a?(Database).should == true
46
+ db2.should_not == db1
47
+ EM::Spec::Mongo.close
48
+ end
49
+ end
50
+
51
+
52
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../lib/em-mongo'
2
+
3
+ #$LOAD_PATH << File.dirname(__FILE__)+'/../../em-spec/lib'
4
+ #require File.dirname(__FILE__)+'/../../em-spec/lib/em/spec'
5
+ #require 'spec'
6
+ #require File.dirname(__FILE__)+'/../../em-spec/lib/em/spec/rspec'
7
+ #EM.spec_backend = EventMachine::Spec::Rspec
8
+
9
+ require "em-spec/rspec"
10
+
11
+ module EM
12
+ module Spec
13
+ module Mongo
14
+ extend EM::SpecHelper
15
+
16
+ @@clean_collection_up = nil
17
+
18
+ def self.close
19
+ @@clean_collection_up.call if @@clean_collection_up
20
+ done
21
+ end
22
+
23
+ def self.collection
24
+ self.database do |database|
25
+ database.collection.remove
26
+ yield database.collection
27
+ end
28
+ end
29
+
30
+ def self.database
31
+ self.connection do |connection|
32
+ yield connection.db
33
+ end
34
+ end
35
+
36
+ def self.connection
37
+ em do
38
+ connection = EMMongo::Connection.new
39
+ EM.next_tick do
40
+ yield connection
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-mongo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - tmm1, bcg
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-03 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 12
30
+ - 10
31
+ version: 0.12.10
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: em-mongo
35
+ email:
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - lib/em-mongo/buffer.rb
44
+ - lib/em-mongo/collection.rb
45
+ - lib/em-mongo/connection.rb
46
+ - lib/em-mongo.rb
47
+ has_rdoc: true
48
+ homepage:
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.6
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: em-mongo based on rmongo
77
+ test_files:
78
+ - spec/buffer_spec.rb
79
+ - spec/collection_spec.rb
80
+ - spec/connection_spec.rb
81
+ - spec/spec_helper.rb