mongodb-mongo 0.1.3

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 (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