The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the order in which keys will return keys, or each yield pairs. ActiveSupport::OrderedHash implements a hash that preserves insertion order, as in Ruby 1.9:

  oh = ActiveSupport::OrderedHash.new
  oh[:a] = 1
  oh[:b] = 2
  oh.keys # => [:a, :b], this order is guaranteed

ActiveSupport::OrderedHash is namespaced to prevent conflicts with other implementations.

Methods
#
C
D
E
I
K
M
N
R
S
T
V
Class Public methods
[](*args)
    # File activesupport/lib/active_support/ordered_hash.rb, line 73
73:       def self.[](*args)
74:         ordered_hash = new
75: 
76:         if (args.length == 1 && args.first.is_a?(Array))
77:           args.first.each do |key_value_pair|
78:             next unless (key_value_pair.is_a?(Array))
79:             ordered_hash[key_value_pair[0]] = key_value_pair[1]
80:           end
81: 
82:           return ordered_hash
83:         end
84: 
85:         unless (args.size % 2 == 0)
86:           raise ArgumentError.new("odd number of arguments for Hash")
87:         end
88: 
89:         args.each_with_index do |val, ind|
90:           next if (ind % 2 != 0)
91:           ordered_hash[val] = args[ind + 1]
92:         end
93: 
94:         ordered_hash
95:       end
new(*args, &block)

In MRI the Hash class is core and written in C. In particular, methods are programmed with explicit C function calls and polymorphism is not honored.

For example, []= is crucial in this implementation to maintain the @keys array but hash.c invokes rb_hash_aset() originally. This prevents method reuse through inheritance and forces us to reimplement stuff.

For instance, we cannot use the inherited merge! because albeit the algorithm itself would work, our []= is not being called at all by the C code.

    # File activesupport/lib/active_support/ordered_hash.rb, line 68
68:       def initialize(*args, &block)
69:         super
70:         @keys = []
71:       end
Instance Public methods
[]=(key, value)
     # File activesupport/lib/active_support/ordered_hash.rb, line 103
103:       def []=(key, value)
104:         @keys << key unless has_key?(key)
105:         super
106:       end
clear()
     # File activesupport/lib/active_support/ordered_hash.rb, line 174
174:       def clear
175:         super
176:         @keys.clear
177:         self
178:       end
delete(key)
     # File activesupport/lib/active_support/ordered_hash.rb, line 108
108:       def delete(key)
109:         if has_key? key
110:           index = @keys.index(key)
111:           @keys.delete_at index
112:         end
113:         super
114:       end
delete_if()
     # File activesupport/lib/active_support/ordered_hash.rb, line 116
116:       def delete_if
117:         super
118:         sync_keys!
119:         self
120:       end
each()
     # File activesupport/lib/active_support/ordered_hash.rb, line 160
160:       def each
161:         return to_enum(:each) unless block_given?
162:         @keys.each {|key| yield [key, self[key]]}
163:         self
164:       end
each_key()
     # File activesupport/lib/active_support/ordered_hash.rb, line 148
148:       def each_key
149:         return to_enum(:each_key) unless block_given?
150:         @keys.each { |key| yield key }
151:         self
152:       end
each_pair()
     # File activesupport/lib/active_support/ordered_hash.rb, line 166
166:       def each_pair
167:         return to_enum(:each_pair) unless block_given?
168:         @keys.each {|key| yield key, self[key]}
169:         self
170:       end
each_value()
     # File activesupport/lib/active_support/ordered_hash.rb, line 154
154:       def each_value
155:         return to_enum(:each_value) unless block_given?
156:         @keys.each { |key| yield self[key]}
157:         self
158:       end
encode_with(coder)
    # File activesupport/lib/active_support/ordered_hash.rb, line 28
28:     def encode_with(coder)
29:       coder.represent_seq '!omap', map { |k,v| { k => v } }
30:     end
extractable_options?()

Returns true to make sure that this hash is extractable via Array#extract_options!

    # File activesupport/lib/active_support/ordered_hash.rb, line 51
51:     def extractable_options?
52:       true
53:     end
initialize_copy(other)
     # File activesupport/lib/active_support/ordered_hash.rb, line 97
 97:       def initialize_copy(other)
 98:         super
 99:         # make a deep copy of keys
100:         @keys = other.keys
101:       end
inspect()
     # File activesupport/lib/active_support/ordered_hash.rb, line 212
212:       def inspect
213:         "#<OrderedHash #{super}>"
214:       end
invert()
     # File activesupport/lib/active_support/ordered_hash.rb, line 208
208:       def invert
209:         OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
210:       end
keys()
     # File activesupport/lib/active_support/ordered_hash.rb, line 132
132:       def keys
133:         @keys.dup
134:       end
merge(other_hash, &block)
     # File activesupport/lib/active_support/ordered_hash.rb, line 197
197:       def merge(other_hash, &block)
198:         dup.merge!(other_hash, &block)
199:       end
merge!(other_hash)
     # File activesupport/lib/active_support/ordered_hash.rb, line 186
186:       def merge!(other_hash)
187:         if block_given?
188:           other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
189:         else
190:           other_hash.each { |k, v| self[k] = v }
191:         end
192:         self
193:       end
nested_under_indifferent_access()
    # File activesupport/lib/active_support/ordered_hash.rb, line 46
46:     def nested_under_indifferent_access
47:       self
48:     end
reject(&block)
     # File activesupport/lib/active_support/ordered_hash.rb, line 128
128:       def reject(&block)
129:         dup.reject!(&block)
130:       end
reject!()
     # File activesupport/lib/active_support/ordered_hash.rb, line 122
122:       def reject!
123:         super
124:         sync_keys!
125:         self
126:       end
replace(other)

When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.

     # File activesupport/lib/active_support/ordered_hash.rb, line 202
202:       def replace(other)
203:         super
204:         @keys = other.keys
205:         self
206:       end
shift()
     # File activesupport/lib/active_support/ordered_hash.rb, line 180
180:       def shift
181:         k = @keys.first
182:         v = delete(k)
183:         [k, v]
184:       end
sync_keys!()
     # File activesupport/lib/active_support/ordered_hash.rb, line 217
217:         def sync_keys!
218:           @keys.delete_if {|k| !has_key?(k)}
219:         end
to_a()
     # File activesupport/lib/active_support/ordered_hash.rb, line 144
144:       def to_a
145:         @keys.map { |key| [ key, self[key] ] }
146:       end
to_hash()
     # File activesupport/lib/active_support/ordered_hash.rb, line 140
140:       def to_hash
141:         self
142:       end
to_yaml(opts = {})
    # File activesupport/lib/active_support/ordered_hash.rb, line 32
32:     def to_yaml(opts = {})
33:       if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
34:         return super
35:       end
36: 
37:       YAML.quick_emit(self, opts) do |out|
38:         out.seq(taguri) do |seq|
39:           each do |k, v|
40:             seq.add(k => v)
41:           end
42:         end
43:       end
44:     end
to_yaml_type()
    # File activesupport/lib/active_support/ordered_hash.rb, line 24
24:     def to_yaml_type
25:       "!tag:yaml.org,2002:omap"
26:     end
values()
     # File activesupport/lib/active_support/ordered_hash.rb, line 136
136:       def values
137:         @keys.collect { |key| self[key] }
138:       end