mongo 0.1.0 → 0.15

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