mcmire-cassandra 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +108 -0
  2. data/LICENSE +202 -0
  3. data/Manifest +63 -0
  4. data/README.md +352 -0
  5. data/Rakefile +169 -0
  6. data/bin/cassandra_helper +16 -0
  7. data/conf/0.6/cassandra.in.sh +47 -0
  8. data/conf/0.6/log4j.properties +38 -0
  9. data/conf/0.6/schema.json +57 -0
  10. data/conf/0.6/storage-conf.xml +352 -0
  11. data/conf/0.7/cassandra.in.sh +46 -0
  12. data/conf/0.7/cassandra.yaml +336 -0
  13. data/conf/0.7/log4j-server.properties +41 -0
  14. data/conf/0.7/schema.json +57 -0
  15. data/conf/0.7/schema.txt +45 -0
  16. data/conf/0.8/cassandra.in.sh +41 -0
  17. data/conf/0.8/cassandra.yaml +61 -0
  18. data/conf/0.8/log4j-server.properties +40 -0
  19. data/conf/0.8/schema.json +66 -0
  20. data/conf/0.8/schema.txt +51 -0
  21. data/lib/cassandra/0.6/cassandra.rb +113 -0
  22. data/lib/cassandra/0.6/columns.rb +78 -0
  23. data/lib/cassandra/0.6/protocol.rb +90 -0
  24. data/lib/cassandra/0.6.rb +7 -0
  25. data/lib/cassandra/0.7/cassandra.rb +2 -0
  26. data/lib/cassandra/0.7/columns.rb +4 -0
  27. data/lib/cassandra/0.7/protocol.rb +5 -0
  28. data/lib/cassandra/0.7.rb +7 -0
  29. data/lib/cassandra/0.8/cassandra.rb +10 -0
  30. data/lib/cassandra/0.8/columns.rb +4 -0
  31. data/lib/cassandra/0.8/protocol.rb +21 -0
  32. data/lib/cassandra/0.8.rb +7 -0
  33. data/lib/cassandra/array.rb +8 -0
  34. data/lib/cassandra/cassandra.rb +1070 -0
  35. data/lib/cassandra/column_family.rb +3 -0
  36. data/lib/cassandra/columns.rb +144 -0
  37. data/lib/cassandra/comparable.rb +28 -0
  38. data/lib/cassandra/constants.rb +11 -0
  39. data/lib/cassandra/debug.rb +9 -0
  40. data/lib/cassandra/helpers.rb +41 -0
  41. data/lib/cassandra/keyspace.rb +3 -0
  42. data/lib/cassandra/long.rb +58 -0
  43. data/lib/cassandra/mock.rb +511 -0
  44. data/lib/cassandra/ordered_hash.rb +192 -0
  45. data/lib/cassandra/protocol.rb +120 -0
  46. data/lib/cassandra/time.rb +11 -0
  47. data/lib/cassandra.rb +38 -0
  48. data/mcmire-cassandra.gemspec +43 -0
  49. data/test/cassandra_client_test.rb +20 -0
  50. data/test/cassandra_mock_test.rb +116 -0
  51. data/test/cassandra_test.rb +863 -0
  52. data/test/comparable_types_test.rb +45 -0
  53. data/test/eventmachine_test.rb +42 -0
  54. data/test/ordered_hash_test.rb +386 -0
  55. data/test/test_helper.rb +15 -0
  56. data/vendor/0.6/gen-rb/cassandra.rb +1481 -0
  57. data/vendor/0.6/gen-rb/cassandra_constants.rb +12 -0
  58. data/vendor/0.6/gen-rb/cassandra_types.rb +482 -0
  59. data/vendor/0.7/gen-rb/cassandra.rb +1936 -0
  60. data/vendor/0.7/gen-rb/cassandra_constants.rb +12 -0
  61. data/vendor/0.7/gen-rb/cassandra_types.rb +681 -0
  62. data/vendor/0.8/gen-rb/cassandra.rb +2215 -0
  63. data/vendor/0.8/gen-rb/cassandra_constants.rb +12 -0
  64. data/vendor/0.8/gen-rb/cassandra_types.rb +824 -0
  65. metadata +200 -0
@@ -0,0 +1,511 @@
1
+ class SimpleUUID::UUID
2
+ def >=(other)
3
+ (self <=> other) >= 0
4
+ end
5
+
6
+ def <=(other)
7
+ (self <=> other) <= 0
8
+ end
9
+ end
10
+
11
+ class Cassandra
12
+ class Mock
13
+ include ::Cassandra::Helpers
14
+ include ::Cassandra::Columns
15
+
16
+ attr_reader :keyspace
17
+
18
+ def initialize(keyspace, schema)
19
+ @is_super = {}
20
+ @keyspace = keyspace
21
+ @column_name_class = {}
22
+ @sub_column_name_class = {}
23
+ @indexes = {}
24
+ @schema = schema[keyspace]
25
+ clear_keyspace!
26
+ end
27
+
28
+ def disconnect!
29
+ end
30
+
31
+ def clear_keyspace!
32
+ @data = {}
33
+ end
34
+
35
+ def clear_column_family!(column_family)
36
+ @data[column_family.to_sym] = OrderedHash.new
37
+ end
38
+
39
+ def default_write_consistency=(value)
40
+ WRITE_DEFAULTS[:consistency] = value
41
+ end
42
+
43
+ def default_read_consistency=(value)
44
+ READ_DEFAULTS[:consistency] = value
45
+ end
46
+
47
+ def insert(column_family, key, hash_or_array, options = {})
48
+ if @batch
49
+ @batch << [:insert, column_family, key, hash_or_array, options]
50
+ else
51
+ raise ArgumentError if key.nil?
52
+ if !is_super(column_family)
53
+ insert_standard(column_family, key, hash_or_array)
54
+ else
55
+ insert_super(column_family, key, hash_or_array)
56
+ end
57
+ end
58
+ end
59
+
60
+ def insert_standard(column_family, key, hash_or_array)
61
+ old = cf(column_family)[key] || OrderedHash.new
62
+ cf(column_family)[key] = merge_and_sort(old, hash_or_array)
63
+ end
64
+
65
+ def insert_super(column_family, key, hash)
66
+ raise ArgumentError unless hash.is_a?(Hash)
67
+ cf(column_family)[key] ||= OrderedHash.new
68
+
69
+ hash.keys.each do |sub_key|
70
+ old = cf(column_family)[key][sub_key] || OrderedHash.new
71
+ cf(column_family)[key][sub_key] = merge_and_sort(old, hash[sub_key])
72
+ end
73
+ end
74
+
75
+ def batch
76
+ @batch = []
77
+ yield
78
+ b = @batch
79
+ @batch = nil
80
+ b.each do |mutation|
81
+ send(*mutation)
82
+ end
83
+ ensure
84
+ @batch = nil
85
+ end
86
+
87
+ def get(column_family, key, *columns_and_options)
88
+ column_family, column, sub_column, options =
89
+ extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
90
+ if !is_super(column_family)
91
+ get_standard(column_family, key, column, options)
92
+ else
93
+ get_super(column_family, key, column, sub_column, options)
94
+ end
95
+ end
96
+
97
+ def get_standard(column_family, key, column, options)
98
+ columns = cf(column_family)[key] || OrderedHash.new
99
+ row = columns_to_hash(column_family, columns)
100
+
101
+ if column
102
+ row[column]
103
+ else
104
+ row = apply_range(row, column_family, options[:start], options[:finish])
105
+ row = apply_count(row, options[:count], options[:reversed])
106
+ end
107
+ end
108
+
109
+ def get_super(column_family, key, column, sub_column, options)
110
+ columns = cf(column_family)[key] || OrderedHash.new
111
+ row = columns_to_hash(column_family, columns)
112
+
113
+ if column
114
+ if sub_column
115
+ if row[column] &&
116
+ row[column][sub_column]
117
+ row[column][sub_column]
118
+ else
119
+ nil
120
+ end
121
+ else
122
+ row = row[column] || OrderedHash.new
123
+ row = apply_range(row, column_family, options[:start], options[:finish], false)
124
+ row = apply_count(row, options[:count], options[:reversed])
125
+ end
126
+ else
127
+ row
128
+ end
129
+ end
130
+
131
+ def exists?(column_family, key, *columns_and_options)
132
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
133
+ results = get(column_family, key, column, sub_column)
134
+
135
+ ![{}, nil].include?(results)
136
+ end
137
+
138
+ def multi_get(column_family, keys, *columns_and_options)
139
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, keys, columns_and_options, READ_DEFAULTS)
140
+ keys.inject(OrderedHash.new) do |hash, key|
141
+ hash[key] = get(column_family, key)
142
+ hash
143
+ end
144
+ end
145
+
146
+ def remove(column_family, key, *columns_and_options)
147
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
148
+ if @batch
149
+ @batch << [:remove, column_family, key, column, sub_column]
150
+ else
151
+ if column
152
+ if sub_column
153
+ cf(column_family)[key][column].delete(sub_column.to_s) if cf(column_family)[key][column]
154
+ else
155
+ cf(column_family)[key].delete(column.to_s) if cf(column_family)[key]
156
+ end
157
+ else
158
+ cf(column_family).delete(key)
159
+ end
160
+ end
161
+ end
162
+
163
+ def get_columns(column_family, key, *columns_and_options)
164
+ column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
165
+ d = get(column_family, key)
166
+
167
+ if sub_columns
168
+ sub_columns.collect do |sub_column|
169
+ d[columns][sub_column]
170
+ end
171
+ else
172
+ columns.collect do |column|
173
+ d[column]
174
+ end
175
+ end
176
+ end
177
+
178
+ def count_columns(column_family, key, *columns_and_options)
179
+ column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
180
+
181
+ get(column_family, key, columns, options).keys.length
182
+ end
183
+
184
+ def multi_get_columns(column_family, keys, columns)
185
+ keys.inject(OrderedHash.new) do |hash, key|
186
+ hash[key] = get_columns(column_family, key, columns)
187
+ hash
188
+ end
189
+ end
190
+
191
+ def multi_count_columns(column_family, keys)
192
+ keys.inject(OrderedHash.new) do |hash, key|
193
+ hash[key] = count_columns(column_family, key)
194
+ hash
195
+ end
196
+ end
197
+
198
+ def get_range(column_family, options = {}, &blk)
199
+ column_family, _, _, options = extract_and_validate_params_for_real(column_family, "", [options],
200
+ READ_DEFAULTS.merge(:start_key => nil,
201
+ :finish_key => nil,
202
+ :key_count => 100,
203
+ :columns => nil,
204
+ :reversed => false
205
+ )
206
+ )
207
+ res = _get_range(column_family,
208
+ options[:start_key],
209
+ options[:finish_key],
210
+ options[:key_count],
211
+ options[:columns],
212
+ options[:start],
213
+ options[:finish],
214
+ options[:count],
215
+ options[:consistency],
216
+ options[:reversed], &blk)
217
+
218
+ if blk.nil?
219
+ res
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ def get_range_keys(column_family, options = {})
226
+ get_range(column_family,options.merge!(:columns => [])).keys
227
+ end
228
+
229
+ def count_range(column_family, options = {})
230
+ Hash[get_range(column_family, options).select{|k,v| v.length > 0}].keys.compact.length
231
+ end
232
+
233
+ def each_key(column_family, options = {})
234
+ each(column_family, options.merge!(:columns => [])) do |key, value|
235
+ yield key
236
+ end
237
+ end
238
+
239
+ def each(column_family, options = {})
240
+ batch_size = options.delete(:batch_size) || 100
241
+ count = options.delete(:key_count)
242
+ yielded_count = 0
243
+
244
+ options[:start_key] ||= ''
245
+ last_key = nil
246
+
247
+ while options[:start_key] != last_key && (count.nil? || count > yielded_count)
248
+ options[:start_key] = last_key
249
+ res = get_range(column_family, options.merge!(:start_key => last_key, :key_count => batch_size))
250
+ res.each do |key, columns|
251
+ next if options[:start_key] == key
252
+ next if yielded_count == count
253
+ yield key, columns
254
+ yielded_count += 1
255
+ last_key = key
256
+ end
257
+ end
258
+ end
259
+
260
+ def create_index(ks_name, cf_name, c_name, v_class)
261
+ if @indexes[ks_name] &&
262
+ @indexes[ks_name][cf_name] &&
263
+ @indexes[ks_name][cf_name][c_name]
264
+ nil
265
+
266
+ else
267
+ @indexes[ks_name] ||= {}
268
+ @indexes[ks_name][cf_name] ||= {}
269
+ @indexes[ks_name][cf_name][c_name] = true
270
+ end
271
+ end
272
+
273
+ def drop_index(ks_name, cf_name, c_name)
274
+ if @indexes[ks_name] &&
275
+ @indexes[ks_name][cf_name] &&
276
+ @indexes[ks_name][cf_name][c_name]
277
+
278
+ @indexes[ks_name][cf_name].delete(c_name)
279
+ else
280
+ nil
281
+ end
282
+ end
283
+
284
+ def create_index_expression(c_name, value, op)
285
+ {:column_name => c_name, :value => value, :comparison => op}
286
+ end
287
+ alias :create_idx_expr :create_index_expression
288
+
289
+ def create_index_clause(idx_expressions, start = "", count = 100)
290
+ {:start => start, :index_expressions => idx_expressions, :count => count, :type => :index_clause}
291
+ end
292
+ alias :create_idx_clause :create_index_clause
293
+
294
+ def get_indexed_slices(column_family, idx_clause, *columns_and_options)
295
+ column_family, columns, _, options =
296
+ extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS.merge(:key_count => 100, :key_start => ""))
297
+
298
+ unless [Hash, OrderedHash].include?(idx_clause.class) && idx_clause[:type] == :index_clause
299
+ idx_clause = create_index_clause(idx_clause, options[:key_start], options[:key_count])
300
+ end
301
+
302
+ ret = {}
303
+ cf(column_family).each do |key, row|
304
+ next if idx_clause[:start] != '' && key < idx_clause[:start]
305
+ next if ret.length == idx_clause[:count]
306
+
307
+ matches = []
308
+ idx_clause[:index_expressions].each do |expr|
309
+ next if row[expr[:column_name]].nil?
310
+ next unless row[expr[:column_name]].send(expr[:comparison].to_sym, expr[:value])
311
+
312
+ matches << expr
313
+ end
314
+
315
+ ret[key] = row if matches.length == idx_clause[:index_expressions].length
316
+ end
317
+
318
+ ret
319
+ end
320
+
321
+ def add(column_family, key, value, *columns_and_options)
322
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
323
+
324
+ if is_super(column_family)
325
+ cf(column_family)[key] ||= OrderedHash.new
326
+ cf(column_family)[key][column] ||= OrderedHash.new
327
+ cf(column_family)[key][column][sub_column] ||= 0
328
+ cf(column_family)[key][column][sub_column] += value
329
+ else
330
+ cf(column_family)[key] ||= OrderedHash.new
331
+ cf(column_family)[key][column] ||= 0
332
+ cf(column_family)[key][column] += value
333
+ end
334
+
335
+ nil
336
+ end
337
+
338
+ def column_families
339
+ cf_defs = {}
340
+ schema.each do |key, value|
341
+ cf_def = Cassandra::ColumnFamily.new
342
+
343
+ value.each do |property, property_value|
344
+ cf_def.send(:"#{property}=", property_value)
345
+ end
346
+
347
+ cf_defs[key] = cf_def
348
+ end
349
+
350
+ cf_defs
351
+ end
352
+
353
+ def schema(load=true)
354
+ @schema
355
+ end
356
+
357
+ def column_family_property(column_family, key)
358
+ schema[column_family.to_s][key]
359
+ end
360
+
361
+ def add_column_family(cf)
362
+ @schema[cf.name.to_s] ||= OrderedHash.new
363
+
364
+ cf.instance_variables.each do |var|
365
+ @schema[cf.name.to_s][var.slice(1..-1)] = cf.instance_variable_get(var)
366
+ end
367
+ end
368
+
369
+ def update_column_family(cf)
370
+ return false unless @schema.include?(cf.name.to_s)
371
+
372
+ cf.instance_variables.each do |var|
373
+ @schema[cf.name.to_s][var.slice(1..-1)] = cf.instance_variable_get(var)
374
+ end
375
+ end
376
+
377
+ def drop_column_family(column_family_name)
378
+ @schema.delete(column_family_name)
379
+ end
380
+
381
+ private
382
+
383
+ def schema_for_keyspace(keyspace)
384
+ @schema
385
+ end
386
+
387
+ def _get_range(column_family, start_key, finish_key, key_count, columns, start, finish, count, consistency, reversed, &blk)
388
+ ret = OrderedHash.new
389
+ start = to_compare_with_type(start, column_family)
390
+ finish = to_compare_with_type(finish, column_family)
391
+ cf(column_family).keys.sort.each do |key|
392
+ break if ret.keys.size >= key_count
393
+ if (start_key.nil? || key >= start_key) && (finish_key.nil? || key <= finish_key)
394
+ if columns
395
+ #ret[key] = columns.inject(OrderedHash.new){|hash, column_name| hash[column_name] = cf(column_family)[key][column_name]; hash;}
396
+ selected_hash = OrderedHash.new
397
+ cf(column_family)[key].each do |k, v|
398
+ selected_hash.[]=(k, v, cf(column_family)[key].timestamps[k]) if columns.include?(k)
399
+ end
400
+ ret[key] = columns_to_hash(column_family, selected_hash)
401
+ ret[key] = apply_count(ret[key], count, reversed)
402
+ blk.call(key,ret[key]) unless blk.nil?
403
+ else
404
+ #ret[key] = apply_range(cf(column_family)[key], column_family, start, finish, !is_super(column_family))
405
+ start, finish = finish, start if reversed
406
+ ret[key] = apply_range(columns_to_hash(column_family, cf(column_family)[key]), column_family, start, finish)
407
+ ret[key] = apply_count(ret[key], count, reversed)
408
+ blk.call(key,ret[key]) unless blk.nil?
409
+ end
410
+ end
411
+ end
412
+ ret
413
+ end
414
+
415
+ def extract_and_validate_params_for_real(column_family, keys, args, options)
416
+ column_family, columns, sub_column, options = extract_and_validate_params(column_family, keys, args, options)
417
+ options[:start] = nil if options[:start] == ''
418
+ options[:finish] = nil if options[:finish] == ''
419
+ [column_family, to_compare_with_types(columns, column_family), to_compare_with_types(sub_column, column_family, false), options]
420
+ end
421
+
422
+ def to_compare_with_types(column_names, column_family, standard=true)
423
+ if column_names.is_a?(Array)
424
+ column_names.collect do |name|
425
+ to_compare_with_type(name, column_family, standard)
426
+ end
427
+ else
428
+ to_compare_with_type(column_names, column_family, standard)
429
+ end
430
+ end
431
+
432
+ def to_compare_with_type(column_name, column_family, standard=true)
433
+ return column_name if column_name.nil?
434
+ klass = if standard
435
+ column_name_class(column_family)
436
+ else
437
+ sub_column_name_class(column_family)
438
+ end
439
+
440
+ klass.new(column_name)
441
+ end
442
+
443
+ def cf(column_family)
444
+ @data[column_family.to_sym] ||= OrderedHash.new
445
+ end
446
+
447
+ def merge_and_sort(old_stuff, new_stuff)
448
+ if new_stuff.is_a?(Array)
449
+ new_stuff = new_stuff.inject({}){|h,k| h[k] = nil; h }
450
+ end
451
+
452
+ new_stuff = new_stuff.to_a.inject({}){|h,k| h[k[0].to_s] = k[1]; h }
453
+
454
+ new_stuff.each { |k,v| old_stuff.[]=(k, v, (Time.now.to_f * 1000000).to_i) }
455
+ hash = OrderedHash.new
456
+ old_stuff.sort{ |a,b| a[0] <=> b[0] }.each do |k, v|
457
+ hash.[]=(k, v, old_stuff.timestamps[k])
458
+ end
459
+ hash
460
+ end
461
+
462
+ def columns_to_hash(column_family, columns)
463
+ column_class, sub_column_class = column_name_class(column_family), sub_column_name_class(column_family)
464
+ output = OrderedHash.new
465
+
466
+ columns.each do |column_name, value|
467
+ timestamp = columns.timestamps[column_name]
468
+ column = column_class.new(column_name)
469
+
470
+ if [Hash, OrderedHash].include?(value.class)
471
+ output[column] ||= OrderedHash.new
472
+ value.each do |sub_column, sub_column_value|
473
+ timestamp = value.timestamps[sub_column]
474
+ output[column].[]=(sub_column_class.new(sub_column), sub_column_value, timestamp)
475
+ end
476
+ else
477
+ output.[]=(column_class.new(column_name), value, timestamp)
478
+ end
479
+ end
480
+
481
+ output
482
+ end
483
+
484
+ def apply_count(row, count, reversed=false)
485
+ if count
486
+ keys = row.keys.sort
487
+ keys = keys.reverse if reversed
488
+ keys = keys[0...count]
489
+ keys.inject(OrderedHash.new) do |memo, key|
490
+ memo.[]=(key, row[key], row.timestamps[key])
491
+ memo
492
+ end
493
+ else
494
+ row
495
+ end
496
+ end
497
+
498
+ def apply_range(row, column_family, strt, fin, standard=true)
499
+ start = to_compare_with_type(strt, column_family, standard)
500
+ finish = to_compare_with_type(fin, column_family, standard)
501
+ ret = OrderedHash.new
502
+ row.keys.each do |key|
503
+ if (start.nil? || key >= start) && (finish.nil? || key <= finish)
504
+ ret.[]=(key, row[key], row.timestamps[key])
505
+ end
506
+ end
507
+ ret
508
+ end
509
+
510
+ end
511
+ end
@@ -0,0 +1,192 @@
1
+ # OrderedHash is namespaced to prevent conflicts with other implementations
2
+ class Cassandra
3
+ class OrderedHashInt < Hash #:nodoc:
4
+ def initialize(*args, &block)
5
+ super
6
+ @keys = []
7
+ end
8
+
9
+ def self.[](*args)
10
+ ordered_hash = new
11
+
12
+ if (args.length == 1 && args.first.is_a?(Array))
13
+ args.first.each do |key_value_pair|
14
+ next unless (key_value_pair.is_a?(Array))
15
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
16
+ end
17
+
18
+ return ordered_hash
19
+ end
20
+
21
+ unless (args.size % 2 == 0)
22
+ raise ArgumentError.new("odd number of arguments for Hash")
23
+ end
24
+
25
+ args.each_with_index do |val, ind|
26
+ next if (ind % 2 != 0)
27
+ ordered_hash[val] = args[ind + 1]
28
+ end
29
+
30
+ ordered_hash
31
+ end
32
+
33
+ def initialize_copy(other)
34
+ super
35
+ # make a deep copy of keys
36
+ @keys = other.keys
37
+ end
38
+
39
+ def []=(key, value)
40
+ @keys << key if !has_key?(key)
41
+ super
42
+ end
43
+
44
+ def delete(key)
45
+ if has_key? key
46
+ index = @keys.index(key)
47
+ @keys.delete_at index
48
+ end
49
+ super
50
+ end
51
+
52
+ def delete_if
53
+ super
54
+ sync_keys!
55
+ self
56
+ end
57
+
58
+ def reject!
59
+ super
60
+ sync_keys!
61
+ self
62
+ end
63
+
64
+ def reject(&block)
65
+ dup.reject!(&block)
66
+ end
67
+
68
+ def keys
69
+ @keys.dup
70
+ end
71
+
72
+ def values
73
+ @keys.collect { |key| self[key] }
74
+ end
75
+
76
+ def to_hash
77
+ self
78
+ end
79
+
80
+ def to_a
81
+ @keys.map { |key| [ key, self[key] ] }
82
+ end
83
+
84
+ def each_key
85
+ @keys.each { |key| yield key }
86
+ end
87
+
88
+ def each_value
89
+ @keys.each { |key| yield self[key]}
90
+ end
91
+
92
+ def each
93
+ @keys.each {|key| yield [key, self[key]]}
94
+ end
95
+
96
+ alias_method :each_pair, :each
97
+
98
+ def clear
99
+ super
100
+ @keys.clear
101
+ self
102
+ end
103
+
104
+ def shift
105
+ k = @keys.first
106
+ v = delete(k)
107
+ [k, v]
108
+ end
109
+
110
+ def merge!(other_hash)
111
+ other_hash.each {|k,v| self[k] = v }
112
+ self
113
+ end
114
+
115
+ def merge(other_hash)
116
+ dup.merge!(other_hash)
117
+ end
118
+
119
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
120
+ def replace(other)
121
+ super
122
+ @keys = other.keys
123
+ self
124
+ end
125
+
126
+ def reverse
127
+ OrderedHashInt[self.to_a.reverse]
128
+ end
129
+
130
+ private
131
+
132
+ def sync_keys!
133
+ @keys.delete_if {|k| !has_key?(k)}
134
+ end
135
+ end
136
+
137
+ class OrderedHash < OrderedHashInt #:nodoc:
138
+ def initialize(*args, &block)
139
+ @timestamps = OrderedHashInt.new
140
+ super
141
+ end
142
+
143
+ def initialize_copy(other)
144
+ @timestamps = other.timestamps
145
+ super
146
+ end
147
+
148
+ def []=(key, value, timestamp = nil)
149
+ @timestamps[key] = timestamp
150
+ super(key, value)
151
+ end
152
+
153
+ def delete(key)
154
+ @timestamps.delete(key)
155
+ super
156
+ end
157
+
158
+ def delete_if(&block)
159
+ @timestamps.delete_if(&block)
160
+ super
161
+ end
162
+
163
+ def reject!(&block)
164
+ @timestamps.reject!(&block)
165
+ super
166
+ end
167
+
168
+ def timestamps
169
+ @timestamps.dup
170
+ end
171
+
172
+ def clear
173
+ @timestamps.clear
174
+ super
175
+ end
176
+
177
+ def shift
178
+ k, v = super
179
+ @timestamps.delete(k)
180
+ [k, v]
181
+ end
182
+
183
+ def replace(other)
184
+ @timestamps = other.timestamps
185
+ super
186
+ end
187
+
188
+ def inspect
189
+ "#<OrderedHash #{super}\n#{@timestamps.inspect}>"
190
+ end
191
+ end
192
+ end