em-mongo 0.1.1

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