mongo 0.1.0 → 0.15

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.
Files changed (89) hide show
  1. data/README.rdoc +268 -71
  2. data/Rakefile +27 -62
  3. data/bin/bson_benchmark.rb +59 -0
  4. data/bin/mongo_console +3 -3
  5. data/bin/run_test_script +19 -0
  6. data/bin/standard_benchmark +109 -0
  7. data/examples/admin.rb +41 -0
  8. data/examples/benchmarks.rb +42 -0
  9. data/examples/blog.rb +76 -0
  10. data/examples/capped.rb +23 -0
  11. data/examples/cursor.rb +47 -0
  12. data/examples/gridfs.rb +87 -0
  13. data/examples/index_test.rb +125 -0
  14. data/examples/info.rb +30 -0
  15. data/examples/queries.rb +69 -0
  16. data/examples/simple.rb +23 -0
  17. data/examples/strict.rb +34 -0
  18. data/examples/types.rb +35 -0
  19. data/lib/mongo.rb +9 -2
  20. data/lib/mongo/admin.rb +65 -68
  21. data/lib/mongo/collection.rb +379 -117
  22. data/lib/mongo/connection.rb +151 -0
  23. data/lib/mongo/cursor.rb +271 -216
  24. data/lib/mongo/db.rb +500 -315
  25. data/lib/mongo/errors.rb +26 -0
  26. data/lib/mongo/gridfs.rb +16 -0
  27. data/lib/mongo/gridfs/chunk.rb +92 -0
  28. data/lib/mongo/gridfs/grid_store.rb +464 -0
  29. data/lib/mongo/message.rb +16 -0
  30. data/lib/mongo/message/get_more_message.rb +24 -13
  31. data/lib/mongo/message/insert_message.rb +29 -11
  32. data/lib/mongo/message/kill_cursors_message.rb +23 -12
  33. data/lib/mongo/message/message.rb +74 -62
  34. data/lib/mongo/message/message_header.rb +35 -24
  35. data/lib/mongo/message/msg_message.rb +21 -9
  36. data/lib/mongo/message/opcodes.rb +26 -15
  37. data/lib/mongo/message/query_message.rb +63 -43
  38. data/lib/mongo/message/remove_message.rb +29 -12
  39. data/lib/mongo/message/update_message.rb +30 -13
  40. data/lib/mongo/query.rb +97 -89
  41. data/lib/mongo/types/binary.rb +25 -21
  42. data/lib/mongo/types/code.rb +30 -0
  43. data/lib/mongo/types/dbref.rb +19 -23
  44. data/lib/mongo/types/objectid.rb +130 -116
  45. data/lib/mongo/types/regexp_of_holding.rb +27 -31
  46. data/lib/mongo/util/bson.rb +273 -160
  47. data/lib/mongo/util/byte_buffer.rb +32 -28
  48. data/lib/mongo/util/ordered_hash.rb +88 -42
  49. data/lib/mongo/util/xml_to_ruby.rb +18 -15
  50. data/mongo-ruby-driver.gemspec +103 -0
  51. data/test/mongo-qa/_common.rb +8 -0
  52. data/test/mongo-qa/admin +26 -0
  53. data/test/mongo-qa/capped +22 -0
  54. data/test/mongo-qa/count1 +18 -0
  55. data/test/mongo-qa/dbs +22 -0
  56. data/test/mongo-qa/find +10 -0
  57. data/test/mongo-qa/find1 +15 -0
  58. data/test/mongo-qa/gridfs_in +16 -0
  59. data/test/mongo-qa/gridfs_out +17 -0
  60. data/test/mongo-qa/indices +49 -0
  61. data/test/mongo-qa/remove +25 -0
  62. data/test/mongo-qa/stress1 +35 -0
  63. data/test/mongo-qa/test1 +11 -0
  64. data/test/mongo-qa/update +18 -0
  65. data/{tests → test}/test_admin.rb +25 -16
  66. data/test/test_bson.rb +268 -0
  67. data/{tests → test}/test_byte_buffer.rb +0 -0
  68. data/test/test_chunk.rb +84 -0
  69. data/test/test_collection.rb +282 -0
  70. data/test/test_connection.rb +101 -0
  71. data/test/test_cursor.rb +321 -0
  72. data/test/test_db.rb +196 -0
  73. data/test/test_db_api.rb +798 -0
  74. data/{tests → test}/test_db_connection.rb +4 -3
  75. data/test/test_grid_store.rb +284 -0
  76. data/{tests → test}/test_message.rb +1 -1
  77. data/test/test_objectid.rb +105 -0
  78. data/{tests → test}/test_ordered_hash.rb +55 -0
  79. data/{tests → test}/test_round_trip.rb +13 -9
  80. data/test/test_threading.rb +37 -0
  81. metadata +74 -32
  82. data/bin/validate +0 -51
  83. data/lib/mongo/mongo.rb +0 -74
  84. data/lib/mongo/types/undefined.rb +0 -31
  85. data/tests/test_bson.rb +0 -135
  86. data/tests/test_cursor.rb +0 -66
  87. data/tests/test_db.rb +0 -51
  88. data/tests/test_db_api.rb +0 -349
  89. data/tests/test_objectid.rb +0 -88
@@ -0,0 +1,26 @@
1
+ # Copyright 2009 10gen, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Exceptions raised by the MongoDB driver.
16
+
17
+ module Mongo
18
+ # Raised when a database operation fails.
19
+ class OperationFailure < RuntimeError; end
20
+
21
+ # Raised when a client attempts to perform an invalid operation.
22
+ class InvalidOperation < RuntimeError; end
23
+
24
+ # Raised when an invalid name is used.
25
+ class InvalidName < RuntimeError; end
26
+ end
@@ -0,0 +1,16 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+ require 'mongo/gridfs/grid_store'
@@ -0,0 +1,92 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/ordered_hash'
20
+
21
+
22
+ module GridFS
23
+
24
+ # A chunk stores a portion of GridStore data.
25
+ class Chunk
26
+
27
+ DEFAULT_CHUNK_SIZE = 1024 * 256
28
+
29
+ attr_reader :object_id, :chunk_number
30
+ attr_accessor :data
31
+
32
+ def initialize(file, mongo_object={})
33
+ @file = file
34
+ @object_id = mongo_object['_id'] || Mongo::ObjectID.new
35
+ @chunk_number = mongo_object['n'] || 0
36
+
37
+ @data = ByteBuffer.new
38
+ case mongo_object['data']
39
+ when String
40
+ mongo_object['data'].each_byte { |b| @data.put(b) }
41
+ when ByteBuffer
42
+ @data.put_array(mongo_object['data'].to_a)
43
+ when Array
44
+ @data.put_array(mongo_object['data'])
45
+ when nil
46
+ else
47
+ raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
48
+ end
49
+ @data.rewind
50
+ end
51
+
52
+ def pos; @data.position; end
53
+ def pos=(pos); @data.position = pos; end
54
+ def eof?; !@data.more?; end
55
+
56
+ def size; @data.size; end
57
+ alias_method :length, :size
58
+
59
+ # Erase all data after current position.
60
+ def truncate
61
+ if @data.position < @data.length
62
+ curr_data = @data
63
+ @data = ByteBuffer.new
64
+ @data.put_array(curr_data.to_a[0...curr_data.position])
65
+ end
66
+ end
67
+
68
+ def getc
69
+ @data.more? ? @data.get : nil
70
+ end
71
+
72
+ def putc(byte)
73
+ @data.put(byte)
74
+ end
75
+
76
+ def save
77
+ coll = @file.chunk_collection
78
+ coll.remove({'_id' => @object_id})
79
+ coll.insert(to_mongo_object)
80
+ end
81
+
82
+ def to_mongo_object
83
+ h = OrderedHash.new
84
+ h['_id'] = @object_id
85
+ h['files_id'] = @file.files_id
86
+ h['n'] = @chunk_number
87
+ h['data'] = data
88
+ h
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,464 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/ordered_hash'
19
+ require 'mongo/gridfs/chunk'
20
+
21
+ module GridFS
22
+
23
+ # GridStore is an IO-like object that provides input and output for
24
+ # streams of data to Mongo. See Mongo's documentation about GridFS for
25
+ # storage implementation details.
26
+ #
27
+ # Example code:
28
+ #
29
+ # require 'mongo/gridfs'
30
+ # GridStore.open(database, 'filename', 'w') { |f|
31
+ # f.puts "Hello, world!"
32
+ # }
33
+ # GridStore.open(database, 'filename, 'r') { |f|
34
+ # puts f.read # => Hello, world!\n
35
+ # }
36
+ # GridStore.open(database, 'filename', 'w+') { |f|
37
+ # f.puts "But wait, there's more!"
38
+ # }
39
+ # GridStore.open(database, 'filename, 'r') { |f|
40
+ # puts f.read # => Hello, world!\nBut wait, there's more!\n
41
+ # }
42
+ class GridStore
43
+
44
+ DEFAULT_ROOT_COLLECTION = 'fs'
45
+ DEFAULT_CONTENT_TYPE = 'text/plain'
46
+
47
+ include Enumerable
48
+
49
+ attr_accessor :filename
50
+
51
+ # Array of strings; may be +nil+
52
+ attr_accessor :aliases
53
+
54
+ # Default is DEFAULT_CONTENT_TYPE
55
+ attr_accessor :content_type
56
+
57
+ attr_accessor :metadata
58
+
59
+ attr_reader :files_id
60
+
61
+ # Time that the file was first saved.
62
+ attr_reader :upload_date
63
+
64
+ attr_reader :chunk_size
65
+
66
+ attr_accessor :lineno
67
+
68
+ attr_reader :md5
69
+
70
+ class << self
71
+
72
+ def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
73
+ db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
74
+ end
75
+
76
+ def open(db, name, mode, options={})
77
+ gs = self.new(db, name, mode, options)
78
+ result = nil
79
+ begin
80
+ result = yield gs if block_given?
81
+ ensure
82
+ gs.close
83
+ end
84
+ result
85
+ end
86
+
87
+ def read(db, name, length=nil, offset=nil)
88
+ GridStore.open(db, name, 'r') { |gs|
89
+ gs.seek(offset) if offset
90
+ gs.read(length)
91
+ }
92
+ end
93
+
94
+ # List the contains of all GridFS files stored in the given db and
95
+ # root collection.
96
+ #
97
+ # :db :: the database to use
98
+ #
99
+ # :root_collection :: the root collection to use
100
+ def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
101
+ db.collection("#{root_collection}.files").find().map { |f|
102
+ f['filename']
103
+ }
104
+ end
105
+
106
+ def readlines(db, name, separator=$/)
107
+ GridStore.open(db, name, 'r') { |gs|
108
+ gs.readlines(separator)
109
+ }
110
+ end
111
+
112
+ def unlink(db, *names)
113
+ names.each { |name|
114
+ gs = GridStore.new(db, name)
115
+ gs.send(:delete_chunks)
116
+ gs.collection.remove('_id' => gs.files_id)
117
+ }
118
+ end
119
+ alias_method :delete, :unlink
120
+
121
+ end
122
+
123
+ #---
124
+ # ================================================================
125
+ #+++
126
+
127
+ # Mode may only be 'r', 'w', or 'w+'.
128
+ #
129
+ # Options. Descriptions start with a list of the modes for which that
130
+ # option is legitimate.
131
+ #
132
+ # :root :: (r, w, w+) Name of root collection to use, instead of
133
+ # DEFAULT_ROOT_COLLECTION.
134
+ #
135
+ # :metadata:: (w, w+) A hash containing any data you want persisted as
136
+ # this file's metadata. See also metadata=
137
+ #
138
+ # :chunk_size :: (w) Sets chunk size for files opened for writing
139
+ # See also chunk_size= which may only be called before
140
+ # any data is written.
141
+ #
142
+ # :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
143
+ # also #content_type=
144
+ def initialize(db, name, mode='r', options={})
145
+ @db, @filename, @mode = db, name, mode
146
+ @root = options[:root] || DEFAULT_ROOT_COLLECTION
147
+
148
+ doc = collection.find({'filename' => @filename}).next_object
149
+ if doc
150
+ @files_id = doc['_id']
151
+ @content_type = doc['contentType']
152
+ @chunk_size = doc['chunkSize']
153
+ @upload_date = doc['uploadDate']
154
+ @aliases = doc['aliases']
155
+ @length = doc['length']
156
+ @metadata = doc['metadata']
157
+ @md5 = doc['md5']
158
+ else
159
+ @files_id = Mongo::ObjectID.new
160
+ @content_type = DEFAULT_CONTENT_TYPE
161
+ @chunk_size = Chunk::DEFAULT_CHUNK_SIZE
162
+ @length = 0
163
+ end
164
+
165
+ case mode
166
+ when 'r'
167
+ @curr_chunk = nth_chunk(0)
168
+ @position = 0
169
+ when 'w'
170
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
171
+ delete_chunks
172
+ @curr_chunk = Chunk.new(self, 'n' => 0)
173
+ @content_type = options[:content_type] if options[:content_type]
174
+ @chunk_size = options[:chunk_size] if options[:chunk_size]
175
+ @metadata = options[:metadata] if options[:metadata]
176
+ @position = 0
177
+ when 'w+'
178
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
179
+ @curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
180
+ @curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
181
+ @metadata = options[:metadata] if options[:metadata]
182
+ @position = @length
183
+ else
184
+ raise "error: illegal mode #{mode}"
185
+ end
186
+
187
+ @lineno = 0
188
+ @pushback_byte = nil
189
+ end
190
+
191
+ def collection
192
+ @db.collection("#{@root}.files")
193
+ end
194
+
195
+ # Returns collection used for storing chunks. Depends on value of
196
+ # @root.
197
+ def chunk_collection
198
+ @db.collection("#{@root}.chunks")
199
+ end
200
+
201
+ # Change chunk size. Can only change if the file is opened for write
202
+ # and no data has yet been written.
203
+ def chunk_size=(size)
204
+ unless @mode[0] == ?w && @position == 0 && @upload_date == nil
205
+ raise "error: can only change chunk size if open for write and no data written."
206
+ end
207
+ @chunk_size = size
208
+ end
209
+
210
+ #---
211
+ # ================ reading ================
212
+ #+++
213
+
214
+ def getc
215
+ if @pushback_byte
216
+ byte = @pushback_byte
217
+ @pushback_byte = nil
218
+ @position += 1
219
+ byte
220
+ elsif eof?
221
+ nil
222
+ else
223
+ if @curr_chunk.eof?
224
+ @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
225
+ end
226
+ @position += 1
227
+ @curr_chunk.getc
228
+ end
229
+ end
230
+
231
+ def gets(separator=$/)
232
+ str = ''
233
+ byte = self.getc
234
+ return nil if byte == nil # EOF
235
+ while byte != nil
236
+ s = byte.chr
237
+ str << s
238
+ break if s == separator
239
+ byte = self.getc
240
+ end
241
+ @lineno += 1
242
+ str
243
+ end
244
+
245
+ def read(len=nil, buf=nil)
246
+ buf ||= ''
247
+ byte = self.getc
248
+ while byte != nil && (len == nil || len > 0)
249
+ buf << byte.chr
250
+ len -= 1 if len
251
+ byte = self.getc if (len == nil || len > 0)
252
+ end
253
+ buf
254
+ end
255
+
256
+ def readchar
257
+ byte = self.getc
258
+ raise EOFError.new if byte == nil
259
+ byte
260
+ end
261
+
262
+ def readline(separator=$/)
263
+ line = gets
264
+ raise EOFError.new if line == nil
265
+ line
266
+ end
267
+
268
+ def readlines(separator=$/)
269
+ read.split(separator).collect { |line| "#{line}#{separator}" }
270
+ end
271
+
272
+ def each
273
+ line = gets
274
+ while line
275
+ yield line
276
+ line = gets
277
+ end
278
+ end
279
+ alias_method :each_line, :each
280
+
281
+ def each_byte
282
+ byte = self.getc
283
+ while byte
284
+ yield byte
285
+ byte = self.getc
286
+ end
287
+ end
288
+
289
+ def ungetc(byte)
290
+ @pushback_byte = byte
291
+ @position -= 1
292
+ end
293
+
294
+ #---
295
+ # ================ writing ================
296
+ #+++
297
+
298
+ def putc(byte)
299
+ if @curr_chunk.pos == @chunk_size
300
+ prev_chunk_number = @curr_chunk.chunk_number
301
+ @curr_chunk.save
302
+ @curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
303
+ end
304
+ @position += 1
305
+ @curr_chunk.putc(byte)
306
+ end
307
+
308
+ def print(*objs)
309
+ objs = [$_] if objs == nil || objs.empty?
310
+ objs.each { |obj|
311
+ str = obj.to_s
312
+ str.each_byte { |byte| self.putc(byte) }
313
+ }
314
+ nil
315
+ end
316
+
317
+ def puts(*objs)
318
+ if objs == nil || objs.empty?
319
+ self.putc(10)
320
+ else
321
+ print(*objs.collect{ |obj|
322
+ str = obj.to_s
323
+ str << "\n" unless str =~ /\n$/
324
+ str
325
+ })
326
+ end
327
+ nil
328
+ end
329
+
330
+ def <<(obj)
331
+ write(obj.to_s)
332
+ end
333
+
334
+ # Writes +string+ as bytes and returns the number of bytes written.
335
+ def write(string)
336
+ raise "#@filename not opened for write" unless @mode[0] == ?w
337
+ count = 0
338
+ string.each_byte { |byte|
339
+ self.putc byte
340
+ count += 1
341
+ }
342
+ count
343
+ end
344
+
345
+ # A no-op.
346
+ def flush
347
+ end
348
+
349
+ #---
350
+ # ================ status ================
351
+ #+++
352
+
353
+ def eof
354
+ raise IOError.new("stream not open for reading") unless @mode[0] == ?r
355
+ @position >= @length
356
+ end
357
+ alias_method :eof?, :eof
358
+
359
+ #---
360
+ # ================ positioning ================
361
+ #+++
362
+
363
+ def rewind
364
+ if @curr_chunk.chunk_number != 0
365
+ if @mode[0] == ?w
366
+ delete_chunks
367
+ @curr_chunk = Chunk.new(self, 'n' => 0)
368
+ else
369
+ @curr_chunk == nth_chunk(0)
370
+ end
371
+ end
372
+ @curr_chunk.pos = 0
373
+ @lineno = 0
374
+ @position = 0
375
+ end
376
+
377
+ def seek(pos, whence=IO::SEEK_SET)
378
+ target_pos = case whence
379
+ when IO::SEEK_CUR
380
+ @position + pos
381
+ when IO::SEEK_END
382
+ @length + pos
383
+ when IO::SEEK_SET
384
+ pos
385
+ end
386
+
387
+ new_chunk_number = (target_pos / @chunk_size).to_i
388
+ if new_chunk_number != @curr_chunk.chunk_number
389
+ @curr_chunk.save if @mode[0] == ?w
390
+ @curr_chunk = nth_chunk(new_chunk_number)
391
+ end
392
+ @position = target_pos
393
+ @curr_chunk.pos = @position % @chunk_size
394
+ 0
395
+ end
396
+
397
+ def tell
398
+ @position
399
+ end
400
+
401
+ #---
402
+ # ================ closing ================
403
+ #+++
404
+
405
+ def close
406
+ if @mode[0] == ?w
407
+ if @curr_chunk
408
+ @curr_chunk.truncate
409
+ @curr_chunk.save if @curr_chunk.pos > 0
410
+ end
411
+ files = collection
412
+ if @upload_date
413
+ files.remove('_id' => @files_id)
414
+ else
415
+ @upload_date = Time.now
416
+ end
417
+ files.insert(to_mongo_object)
418
+ end
419
+ @db = nil
420
+ end
421
+
422
+ def closed?
423
+ @db == nil
424
+ end
425
+
426
+ #---
427
+ # ================ protected ================
428
+ #+++
429
+
430
+ protected
431
+
432
+ def to_mongo_object
433
+ h = OrderedHash.new
434
+ h['_id'] = @files_id
435
+ h['filename'] = @filename
436
+ h['contentType'] = @content_type
437
+ h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
438
+ h['chunkSize'] = @chunk_size
439
+ h['uploadDate'] = @upload_date
440
+ h['aliases'] = @aliases
441
+ h['metadata'] = @metadata
442
+ md5_command = OrderedHash.new
443
+ md5_command['filemd5'] = @files_id
444
+ md5_command['root'] = @root
445
+ h['md5'] = @db.db_command(md5_command)['md5']
446
+ h
447
+ end
448
+
449
+ def delete_chunks
450
+ chunk_collection.remove({'files_id' => @files_id}) if @files_id
451
+ @curr_chunk = nil
452
+ end
453
+
454
+ def nth_chunk(n)
455
+ mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
456
+ Chunk.new(self, mongo_chunk || {})
457
+ end
458
+
459
+ def last_chunk_number
460
+ (@length / @chunk_size).to_i
461
+ end
462
+
463
+ end
464
+ end