mongodb-mongo 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.rdoc +216 -0
  2. data/Rakefile +54 -0
  3. data/bin/mongo_console +21 -0
  4. data/bin/validate +51 -0
  5. data/examples/benchmarks.rb +38 -0
  6. data/examples/blog.rb +76 -0
  7. data/examples/index_test.rb +128 -0
  8. data/examples/simple.rb +17 -0
  9. data/lib/mongo/admin.rb +86 -0
  10. data/lib/mongo/collection.rb +161 -0
  11. data/lib/mongo/cursor.rb +230 -0
  12. data/lib/mongo/db.rb +399 -0
  13. data/lib/mongo/message/get_more_message.rb +21 -0
  14. data/lib/mongo/message/insert_message.rb +19 -0
  15. data/lib/mongo/message/kill_cursors_message.rb +20 -0
  16. data/lib/mongo/message/message.rb +68 -0
  17. data/lib/mongo/message/message_header.rb +34 -0
  18. data/lib/mongo/message/msg_message.rb +17 -0
  19. data/lib/mongo/message/opcodes.rb +16 -0
  20. data/lib/mongo/message/query_message.rb +67 -0
  21. data/lib/mongo/message/remove_message.rb +20 -0
  22. data/lib/mongo/message/update_message.rb +21 -0
  23. data/lib/mongo/message.rb +4 -0
  24. data/lib/mongo/mongo.rb +98 -0
  25. data/lib/mongo/query.rb +110 -0
  26. data/lib/mongo/types/binary.rb +34 -0
  27. data/lib/mongo/types/dbref.rb +37 -0
  28. data/lib/mongo/types/objectid.rb +137 -0
  29. data/lib/mongo/types/regexp_of_holding.rb +44 -0
  30. data/lib/mongo/types/undefined.rb +31 -0
  31. data/lib/mongo/util/bson.rb +431 -0
  32. data/lib/mongo/util/byte_buffer.rb +163 -0
  33. data/lib/mongo/util/ordered_hash.rb +68 -0
  34. data/lib/mongo/util/xml_to_ruby.rb +102 -0
  35. data/lib/mongo.rb +12 -0
  36. data/mongo-ruby-driver.gemspec +62 -0
  37. data/tests/test_admin.rb +60 -0
  38. data/tests/test_bson.rb +135 -0
  39. data/tests/test_byte_buffer.rb +69 -0
  40. data/tests/test_cursor.rb +66 -0
  41. data/tests/test_db.rb +85 -0
  42. data/tests/test_db_api.rb +354 -0
  43. data/tests/test_db_connection.rb +17 -0
  44. data/tests/test_message.rb +35 -0
  45. data/tests/test_objectid.rb +98 -0
  46. data/tests/test_ordered_hash.rb +85 -0
  47. data/tests/test_round_trip.rb +116 -0
  48. metadata +100 -0
@@ -0,0 +1,431 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ require 'base64'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/ordered_hash'
20
+ require 'mongo/types/binary'
21
+ require 'mongo/types/dbref'
22
+ require 'mongo/types/objectid'
23
+ require 'mongo/types/regexp_of_holding'
24
+ require 'mongo/types/undefined'
25
+
26
+ # A BSON seralizer/deserializer.
27
+ class BSON
28
+
29
+ MINKEY = -1
30
+ EOO = 0
31
+ NUMBER = 1
32
+ STRING = 2
33
+ OBJECT = 3
34
+ ARRAY = 4
35
+ BINARY = 5
36
+ UNDEFINED = 6
37
+ OID = 7
38
+ BOOLEAN = 8
39
+ DATE = 9
40
+ NULL = 10
41
+ REGEX = 11
42
+ REF = 12
43
+ CODE = 13
44
+ SYMBOL = 14
45
+ CODE_W_SCOPE = 15
46
+ NUMBER_INT = 16
47
+ MAXKEY = 127
48
+
49
+ if RUBY_VERSION >= '1.9'
50
+ def self.to_utf8(str)
51
+ str.encode("utf-8")
52
+ end
53
+ else
54
+ def self.to_utf8(str)
55
+ str # TODO punt for now
56
+ end
57
+ end
58
+
59
+ def self.serialize_cstr(buf, val)
60
+ buf.put_array(to_utf8(val.to_s).unpack("C*") + [0])
61
+ end
62
+
63
+ def initialize(db=nil)
64
+ # db is only needed during deserialization when the data contains a DBRef
65
+ @db = db
66
+ @buf = ByteBuffer.new
67
+ end
68
+
69
+ def to_a
70
+ @buf.to_a
71
+ end
72
+
73
+ def serialize(obj)
74
+ raise "Document is null" unless obj
75
+
76
+ @buf.rewind
77
+ # put in a placeholder for the total size
78
+ @buf.put_int(0)
79
+
80
+ obj.each {|k, v|
81
+ type = bson_type(v, k)
82
+ case type
83
+ when STRING, CODE, SYMBOL
84
+ serialize_string_element(@buf, k, v, type)
85
+ when NUMBER, NUMBER_INT
86
+ serialize_number_element(@buf, k, v, type)
87
+ when OBJECT
88
+ serialize_object_element(@buf, k, v)
89
+ when OID
90
+ serialize_oid_element(@buf, k, v)
91
+ when ARRAY
92
+ serialize_array_element(@buf, k, v)
93
+ when REGEX
94
+ serialize_regex_element(@buf, k, v)
95
+ when BOOLEAN
96
+ serialize_boolean_element(@buf, k, v)
97
+ when DATE
98
+ serialize_date_element(@buf, k, v)
99
+ when NULL
100
+ serialize_null_element(@buf, k)
101
+ when REF
102
+ serialize_dbref_element(@buf, k, v)
103
+ when BINARY
104
+ serialize_binary_element(@buf, k, v)
105
+ when UNDEFINED
106
+ serialize_undefined_element(@buf, k)
107
+ when CODE_W_SCOPE
108
+ # TODO
109
+ raise "unimplemented type #{type}"
110
+ else
111
+ raise "unhandled type #{type}"
112
+ end
113
+ }
114
+ serialize_eoo_element(@buf)
115
+ @buf.put_int(@buf.size, 0)
116
+ self
117
+ end
118
+
119
+ def deserialize(buf=nil, parent=nil)
120
+ # If buf is nil, use @buf, assumed to contain already-serialized BSON.
121
+ # This is only true during testing.
122
+ @buf = ByteBuffer.new(buf.to_a) if buf
123
+ @buf.rewind
124
+ @buf.get_int # eat message size
125
+ doc = OrderedHash.new
126
+ while @buf.more?
127
+ type = @buf.get
128
+ case type
129
+ when STRING, CODE
130
+ key = deserialize_cstr(@buf)
131
+ doc[key] = deserialize_string_data(@buf)
132
+ when SYMBOL
133
+ key = deserialize_cstr(@buf)
134
+ doc[key] = deserialize_string_data(@buf).intern
135
+ when NUMBER
136
+ key = deserialize_cstr(@buf)
137
+ doc[key] = deserialize_number_data(@buf)
138
+ when NUMBER_INT
139
+ key = deserialize_cstr(@buf)
140
+ doc[key] = deserialize_number_int_data(@buf)
141
+ when OID
142
+ key = deserialize_cstr(@buf)
143
+ doc[key] = deserialize_oid_data(@buf)
144
+ when ARRAY
145
+ key = deserialize_cstr(@buf)
146
+ doc[key] = deserialize_array_data(@buf, doc)
147
+ when REGEX
148
+ key = deserialize_cstr(@buf)
149
+ doc[key] = deserialize_regex_data(@buf)
150
+ when OBJECT
151
+ key = deserialize_cstr(@buf)
152
+ doc[key] = deserialize_object_data(@buf, doc)
153
+ when BOOLEAN
154
+ key = deserialize_cstr(@buf)
155
+ doc[key] = deserialize_boolean_data(@buf)
156
+ when DATE
157
+ key = deserialize_cstr(@buf)
158
+ doc[key] = deserialize_date_data(@buf)
159
+ when NULL
160
+ key = deserialize_cstr(@buf)
161
+ doc[key] = nil
162
+ when UNDEFINED
163
+ key = deserialize_cstr(@buf)
164
+ doc[key] = XGen::Mongo::Driver::Undefined.new
165
+ when REF
166
+ key = deserialize_cstr(@buf)
167
+ doc[key] = deserialize_dbref_data(@buf, key, parent)
168
+ when BINARY
169
+ key = deserialize_cstr(@buf)
170
+ doc[key] = deserialize_binary_data(@buf)
171
+ when CODE_W_SCOPE
172
+ # TODO
173
+ raise "unimplemented type #{type}"
174
+ when EOO
175
+ break
176
+ else
177
+ raise "Unknown type #{type}, key = #{key}"
178
+ end
179
+ end
180
+ @buf.rewind
181
+ doc
182
+ end
183
+
184
+ # For debugging.
185
+ def hex_dump
186
+ str = ''
187
+ @buf.to_a.each_with_index { |b,i|
188
+ if (i % 8) == 0
189
+ str << "\n" if i > 0
190
+ str << '%4d: ' % i
191
+ else
192
+ str << ' '
193
+ end
194
+ str << '%02X' % b
195
+ }
196
+ str
197
+ end
198
+
199
+ def deserialize_date_data(buf)
200
+ millisecs = buf.get_long()
201
+ Time.at(millisecs.to_f / 1000.0) # at() takes fractional seconds
202
+ end
203
+
204
+ def deserialize_boolean_data(buf)
205
+ buf.get == 1
206
+ end
207
+
208
+ def deserialize_number_data(buf)
209
+ buf.get_double
210
+ end
211
+
212
+ def deserialize_number_int_data(buf)
213
+ buf.get_int
214
+ end
215
+
216
+ def deserialize_object_data(buf, parent)
217
+ size = buf.get_int
218
+ buf.position -= 4
219
+ BSON.new(@db).deserialize(buf.get(size), parent)
220
+ end
221
+
222
+ def deserialize_array_data(buf, parent)
223
+ h = deserialize_object_data(buf, parent)
224
+ a = []
225
+ h.each { |k, v| a[k.to_i] = v }
226
+ a
227
+ end
228
+
229
+ def deserialize_regex_data(buf)
230
+ str = deserialize_cstr(buf)
231
+ options_str = deserialize_cstr(buf)
232
+ options = 0
233
+ options |= Regexp::IGNORECASE if options_str.include?('i')
234
+ options |= Regexp::MULTILINE if options_str.include?('m')
235
+ options |= Regexp::EXTENDED if options_str.include?('x')
236
+ options_str.gsub!(/[imx]/, '') # Now remove the three we understand
237
+ XGen::Mongo::Driver::RegexpOfHolding.new(str, options, options_str)
238
+ end
239
+
240
+ def deserialize_string_data(buf)
241
+ len = buf.get_int
242
+ bytes = buf.get(len)
243
+ str = bytes[0..-2].pack("C*")
244
+ if RUBY_VERSION >= '1.9'
245
+ str.force_encoding("utf-8")
246
+ end
247
+ str
248
+ end
249
+
250
+ def deserialize_oid_data(buf)
251
+ XGen::Mongo::Driver::ObjectID.new(buf.get(12))
252
+ end
253
+
254
+ def deserialize_dbref_data(buf, key, parent)
255
+ ns = deserialize_string_data(buf)
256
+ oid = deserialize_oid_data(buf)
257
+ XGen::Mongo::Driver::DBRef.new(parent, key, @db, ns, oid)
258
+ end
259
+
260
+ def deserialize_binary_data(buf)
261
+ len = buf.get_int
262
+ bytes = buf.get(len)
263
+ str = ''
264
+ bytes.each { |c| str << c.chr }
265
+ str.to_mongo_binary
266
+ end
267
+
268
+ def serialize_eoo_element(buf)
269
+ buf.put(EOO)
270
+ end
271
+
272
+ def serialize_null_element(buf, key)
273
+ buf.put(NULL)
274
+ self.class.serialize_cstr(buf, key)
275
+ end
276
+
277
+ def serialize_dbref_element(buf, key, val)
278
+ serialize_string_element(buf, key, val.namespace, REF)
279
+ buf.put_array(val.object_id.to_a)
280
+ end
281
+
282
+ def serialize_binary_element(buf, key, val)
283
+ buf.put(BINARY)
284
+ self.class.serialize_cstr(buf, key)
285
+ buf.put_int(val.length)
286
+ bytes = if RUBY_VERSION >= '1.9'
287
+ val.bytes.to_a
288
+ else
289
+ a = []
290
+ val.each_byte { |byte| a << byte }
291
+ a
292
+ end
293
+ buf.put_array(bytes)
294
+ end
295
+
296
+ def serialize_undefined_element(buf, key)
297
+ buf.put(UNDEFINED)
298
+ self.class.serialize_cstr(buf, key)
299
+ end
300
+
301
+ def serialize_boolean_element(buf, key, val)
302
+ buf.put(BOOLEAN)
303
+ self.class.serialize_cstr(buf, key)
304
+ buf.put(val ? 1 : 0)
305
+ end
306
+
307
+ def serialize_date_element(buf, key, val)
308
+ buf.put(DATE)
309
+ self.class.serialize_cstr(buf, key)
310
+ millisecs = (val.to_f * 1000).to_i
311
+ buf.put_long(millisecs)
312
+ end
313
+
314
+ def serialize_number_element(buf, key, val, type)
315
+ buf.put(type)
316
+ self.class.serialize_cstr(buf, key)
317
+ if type == NUMBER
318
+ buf.put_double(val)
319
+ else
320
+ buf.put_int(val)
321
+ end
322
+ end
323
+
324
+ def serialize_object_element(buf, key, val, opcode=OBJECT)
325
+ buf.put(opcode)
326
+ self.class.serialize_cstr(buf, key)
327
+ buf.put_array(BSON.new.serialize(val).to_a)
328
+ end
329
+
330
+ def serialize_array_element(buf, key, val)
331
+ # Turn array into hash with integer indices as keys
332
+ h = OrderedHash.new
333
+ i = 0
334
+ val.each { |v| h[i] = v; i += 1 }
335
+ serialize_object_element(buf, key, h, ARRAY)
336
+ end
337
+
338
+ def serialize_regex_element(buf, key, val)
339
+ buf.put(REGEX)
340
+ self.class.serialize_cstr(buf, key)
341
+
342
+ str = val.to_s.sub(/.*?:/, '')[0..-2] # Turn "(?xxx:yyy)" into "yyy"
343
+ self.class.serialize_cstr(buf, str)
344
+
345
+ options = val.options
346
+ options_str = ''
347
+ options_str << 'i' if ((options & Regexp::IGNORECASE) != 0)
348
+ options_str << 'm' if ((options & Regexp::MULTILINE) != 0)
349
+ options_str << 'x' if ((options & Regexp::EXTENDED) != 0)
350
+ options_str << val.extra_options_str if val.respond_to?(:extra_options_str)
351
+ # Must store option chars in alphabetical order
352
+ self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join)
353
+ end
354
+
355
+ def serialize_oid_element(buf, key, val)
356
+ buf.put(OID)
357
+ self.class.serialize_cstr(buf, key)
358
+
359
+ buf.put_array(val.to_a)
360
+ end
361
+
362
+ def serialize_string_element(buf, key, val, type)
363
+ buf.put(type)
364
+ self.class.serialize_cstr(buf, key)
365
+
366
+ # Make a hole for the length
367
+ len_pos = buf.position
368
+ buf.put_int(0)
369
+
370
+ # Save the string
371
+ start_pos = buf.position
372
+ self.class.serialize_cstr(buf, val)
373
+ end_pos = buf.position
374
+
375
+ # Put the string size in front
376
+ buf.put_int(end_pos - start_pos, len_pos)
377
+
378
+ # Go back to where we were
379
+ buf.position = end_pos
380
+ end
381
+
382
+ def deserialize_cstr(buf)
383
+ chars = ""
384
+ while 1
385
+ b = buf.get
386
+ break if b == 0
387
+ chars << b.chr
388
+ end
389
+ if RUBY_VERSION >= '1.9'
390
+ chars.force_encoding("utf-8") # Mongo stores UTF-8
391
+ end
392
+ chars
393
+ end
394
+
395
+ def bson_type(o, key)
396
+ case o
397
+ when nil
398
+ NULL
399
+ when Integer
400
+ NUMBER_INT
401
+ when Numeric
402
+ NUMBER
403
+ when XGen::Mongo::Driver::Binary # must be before String
404
+ BINARY
405
+ when String
406
+ # magic awful stuff - the DB requires that a where clause is sent as CODE
407
+ key == "$where" ? CODE : STRING
408
+ when Array
409
+ ARRAY
410
+ when Regexp
411
+ REGEX
412
+ when XGen::Mongo::Driver::ObjectID
413
+ OID
414
+ when XGen::Mongo::Driver::DBRef
415
+ REF
416
+ when true, false
417
+ BOOLEAN
418
+ when Time
419
+ DATE
420
+ when Hash
421
+ OBJECT
422
+ when Symbol
423
+ SYMBOL
424
+ when XGen::Mongo::Driver::Undefined
425
+ UNDEFINED
426
+ else
427
+ raise "Unknown type of object: #{o.class.name}"
428
+ end
429
+ end
430
+
431
+ end
@@ -0,0 +1,163 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ # A byte buffer.
18
+ class ByteBuffer
19
+
20
+ attr_reader :order
21
+
22
+ def initialize(initial_data=[])
23
+ @buf = initial_data
24
+ @cursor = 0
25
+ self.order = :little_endian
26
+ end
27
+
28
+ # +endianness+ should be :little_endian or :big_endian. Default is :little_endian
29
+ def order=(endianness)
30
+ @order = endianness
31
+ @int_pack_order = endianness == :little_endian ? 'V' : 'N'
32
+ @double_pack_order = endianness == :little_endian ? 'E' : 'G'
33
+ end
34
+
35
+ def rewind
36
+ @cursor = 0
37
+ end
38
+
39
+ def position
40
+ @cursor
41
+ end
42
+
43
+ def position=(val)
44
+ @cursor = val
45
+ end
46
+
47
+ def clear
48
+ @buf = []
49
+ rewind
50
+ end
51
+
52
+ def size
53
+ @buf.size
54
+ end
55
+ alias_method :length, :size
56
+
57
+ def put(byte, offset=nil)
58
+ @cursor = offset if offset
59
+ @buf[@cursor] = byte
60
+ @cursor += 1
61
+ end
62
+
63
+ def put_array(array, offset=nil)
64
+ @cursor = offset if offset
65
+ @buf[@cursor, array.length] = array
66
+ @cursor += array.length
67
+ end
68
+
69
+ if RUBY_VERSION >= '1.9'
70
+ def put_int(i, offset=nil)
71
+ put_array([i].pack(@int_pack_order).split(//).collect{|c| c.bytes.first}, offset)
72
+ end
73
+ else
74
+ def put_int(i, offset=nil)
75
+ put_array([i].pack(@int_pack_order).split(//).collect{|c| c[0]}, offset)
76
+ end
77
+ end
78
+
79
+ def put_long(i, offset=nil)
80
+ offset = @cursor unless offset
81
+ if @int_pack_order == 'N'
82
+ put_int(i >> 32, offset)
83
+ put_int(i & 0xffffffff, offset + 4)
84
+ else
85
+ put_int(i & 0xffffffff, offset)
86
+ put_int(i >> 32, offset + 4)
87
+ end
88
+ end
89
+
90
+ if RUBY_VERSION >= '1.9'
91
+ def put_double(d, offset=nil)
92
+ put_array([d].pack(@double_pack_order).split(//).collect{|c| c.bytes.first}, offset)
93
+ end
94
+ else
95
+ def put_double(d, offset=nil)
96
+ put_array([d].pack(@double_pack_order).split(//).collect{|c| c[0]}, offset)
97
+ end
98
+ end
99
+
100
+ # If +size+ == nil, returns one byte. Else returns array of bytes of length
101
+ # # +size+.
102
+ def get(len=nil)
103
+ one_byte = len.nil?
104
+ len ||= 1
105
+ check_read_length(len)
106
+ start = @cursor
107
+ @cursor += len
108
+ if one_byte
109
+ @buf[start]
110
+ else
111
+ @buf[start, len]
112
+ end
113
+ end
114
+
115
+ def get_int
116
+ check_read_length(4)
117
+ vals = ""
118
+ (@cursor..@cursor+3).each { |i| vals << @buf[i].chr }
119
+ @cursor += 4
120
+ vals.unpack(@int_pack_order)[0]
121
+ end
122
+
123
+ def get_long
124
+ i1 = get_int
125
+ i2 = get_int
126
+ if @int_pack_order == 'N'
127
+ (i1 << 32) + i2
128
+ else
129
+ (i2 << 32) + i1
130
+ end
131
+ end
132
+
133
+ def get_double
134
+ check_read_length(8)
135
+ vals = ""
136
+ (@cursor..@cursor+7).each { |i| vals << @buf[i].chr }
137
+ @cursor += 8
138
+ vals.unpack(@double_pack_order)[0]
139
+ end
140
+
141
+ def more?
142
+ @cursor < @buf.size
143
+ end
144
+
145
+ def to_a
146
+ @buf
147
+ end
148
+
149
+ def to_s
150
+ @buf.pack("C*")
151
+ end
152
+
153
+ def dump
154
+ @buf.each_with_index { |c, i| $stderr.puts "#{'%04d' % i}: #{'%02x' % c} #{'%03o' % c} #{'%s' % c.chr} #{'%3d' % c}" }
155
+ end
156
+
157
+ private
158
+
159
+ def check_read_length(len)
160
+ raise "attempt to read past end of buffer" if @cursor + len > @buf.length
161
+ end
162
+
163
+ end
@@ -0,0 +1,68 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it
5
+ # under the terms of the GNU Affero General Public License, version 3, as
6
+ # published by the Free Software Foundation.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
11
+ # for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+ # ++
16
+
17
+ # A hash in which the order of keys are preserved.
18
+ #
19
+ # Under Ruby 1.9 and greater, this class has no added methods because Ruby's
20
+ # Hash already keeps its keys ordered by order of insertion.
21
+ class OrderedHash < Hash
22
+
23
+ # We only need the body of this class if the RUBY_VERSION is before 1.9
24
+ if RUBY_VERSION < '1.9'
25
+
26
+ attr_accessor :ordered_keys
27
+
28
+ def keys
29
+ @ordered_keys || []
30
+ end
31
+
32
+ def []=(key, value)
33
+ @ordered_keys ||= []
34
+ @ordered_keys << key unless @ordered_keys.include?(key)
35
+ super(key, value)
36
+ end
37
+
38
+ def each
39
+ @ordered_keys ||= []
40
+ @ordered_keys.each { |k| yield k, self[k] }
41
+ end
42
+
43
+ def values
44
+ collect { |k, v| v }
45
+ end
46
+
47
+ def merge(other)
48
+ oh = self.dup
49
+ oh.merge!(other)
50
+ oh
51
+ end
52
+
53
+ def merge!(other)
54
+ @ordered_keys ||= []
55
+ @ordered_keys += other.keys # unordered if not an OrderedHash
56
+ @ordered_keys.uniq!
57
+ super(other)
58
+ end
59
+
60
+ def inspect
61
+ str = '{'
62
+ str << (@ordered_keys || []).collect { |k| "\"#{k}\"=>#{self.[](k).inspect}" }.join(", ")
63
+ str << '}'
64
+ end
65
+
66
+ end # Ruby before 1.9
67
+
68
+ end