Class: Urbane::ActiveSupport::OrderedHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/urbane/vendor/ordered_hash.rb

Overview

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.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args, &block) ⇒ OrderedHash

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.



69
70
71
72
# File 'lib/urbane/vendor/ordered_hash.rb', line 69

def initialize(*args, &block)
  super
  @keys = []
end

Class Method Details

.[](*args) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/urbane/vendor/ordered_hash.rb', line 74

def self.[](*args)
  ordered_hash = new

  if (args.length == 1 && args.first.is_a?(Array))
    args.first.each do |key_value_pair|
      next unless (key_value_pair.is_a?(Array))
      ordered_hash[key_value_pair[0]] = key_value_pair[1]
    end

    return ordered_hash
  end

  unless (args.size % 2 == 0)
    raise ArgumentError.new("odd number of arguments for Hash")
  end

  args.each_with_index do |val, ind|
    next if (ind % 2 != 0)
    ordered_hash[val] = args[ind + 1]
  end

  ordered_hash
end

Instance Method Details

#[]=(key, value) ⇒ Object



104
105
106
107
# File 'lib/urbane/vendor/ordered_hash.rb', line 104

def []=(key, value)
  @keys << key unless has_key?(key)
  super
end

#clearObject



175
176
177
178
179
# File 'lib/urbane/vendor/ordered_hash.rb', line 175

def clear
  super
  @keys.clear
  self
end

#delete(key) ⇒ Object



109
110
111
112
113
114
115
# File 'lib/urbane/vendor/ordered_hash.rb', line 109

def delete(key)
  if has_key? key
    index = @keys.index(key)
    @keys.delete_at index
  end
  super
end

#delete_ifObject



117
118
119
120
121
# File 'lib/urbane/vendor/ordered_hash.rb', line 117

def delete_if
  super
  sync_keys!
  self
end

#eachObject



161
162
163
164
165
# File 'lib/urbane/vendor/ordered_hash.rb', line 161

def each
  return to_enum(:each) unless block_given?
  @keys.each {|key| yield [key, self[key]]}
  self
end

#each_keyObject



149
150
151
152
153
# File 'lib/urbane/vendor/ordered_hash.rb', line 149

def each_key
  return to_enum(:each_key) unless block_given?
  @keys.each { |key| yield key }
  self
end

#each_pairObject



167
168
169
170
171
# File 'lib/urbane/vendor/ordered_hash.rb', line 167

def each_pair
  return to_enum(:each_pair) unless block_given?
  @keys.each {|key| yield key, self[key]}
  self
end

#each_valueObject



155
156
157
158
159
# File 'lib/urbane/vendor/ordered_hash.rb', line 155

def each_value
  return to_enum(:each_value) unless block_given?
  @keys.each { |key| yield self[key]}
  self
end

#encode_with(coder) ⇒ Object



29
30
31
# File 'lib/urbane/vendor/ordered_hash.rb', line 29

def encode_with(coder)
  coder.represent_seq '!omap', map { |k,v| { k => v } }
end

#extractable_options?Boolean

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

Returns:

  • (Boolean)


52
53
54
# File 'lib/urbane/vendor/ordered_hash.rb', line 52

def extractable_options?
  true
end

#initialize_copy(other) ⇒ Object



98
99
100
101
102
# File 'lib/urbane/vendor/ordered_hash.rb', line 98

def initialize_copy(other)
  super
  # make a deep copy of keys
  @keys = other.keys
end

#inspectObject



213
214
215
# File 'lib/urbane/vendor/ordered_hash.rb', line 213

def inspect
  "#<OrderedHash #{super}>"
end

#invertObject



209
210
211
# File 'lib/urbane/vendor/ordered_hash.rb', line 209

def invert
  OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
end

#keysObject



133
134
135
# File 'lib/urbane/vendor/ordered_hash.rb', line 133

def keys
  @keys.dup
end

#merge(other_hash, &block) ⇒ Object



198
199
200
# File 'lib/urbane/vendor/ordered_hash.rb', line 198

def merge(other_hash, &block)
  dup.merge!(other_hash, &block)
end

#merge!(other_hash) ⇒ Object Also known as: update



187
188
189
190
191
192
193
194
# File 'lib/urbane/vendor/ordered_hash.rb', line 187

def merge!(other_hash)
  if block_given?
    other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
  else
    other_hash.each { |k, v| self[k] = v }
  end
  self
end

#nested_under_indifferent_accessObject



47
48
49
# File 'lib/urbane/vendor/ordered_hash.rb', line 47

def nested_under_indifferent_access
  self
end

#reject(&block) ⇒ Object



129
130
131
# File 'lib/urbane/vendor/ordered_hash.rb', line 129

def reject(&block)
  dup.reject!(&block)
end

#reject!Object



123
124
125
126
127
# File 'lib/urbane/vendor/ordered_hash.rb', line 123

def reject!
  super
  sync_keys!
  self
end

#replace(other) ⇒ Object

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



203
204
205
206
207
# File 'lib/urbane/vendor/ordered_hash.rb', line 203

def replace(other)
  super
  @keys = other.keys
  self
end

#shiftObject



181
182
183
184
185
# File 'lib/urbane/vendor/ordered_hash.rb', line 181

def shift
  k = @keys.first
  v = delete(k)
  [k, v]
end

#to_aObject



145
146
147
# File 'lib/urbane/vendor/ordered_hash.rb', line 145

def to_a
  @keys.map { |key| [ key, self[key] ] }
end

#to_hashObject



141
142
143
# File 'lib/urbane/vendor/ordered_hash.rb', line 141

def to_hash
  self
end

#to_yaml(opts = {}) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/urbane/vendor/ordered_hash.rb', line 33

def to_yaml(opts = {})
  if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
    return super
  end

  YAML.quick_emit(self, opts) do |out|
    out.seq(taguri) do |seq|
      each do |k, v|
        seq.add(k => v)
      end
    end
  end
end

#to_yaml_typeObject

:nodoc:



25
26
27
# File 'lib/urbane/vendor/ordered_hash.rb', line 25

def to_yaml_type
  "!tag:yaml.org,2002:omap"
end

#valuesObject



137
138
139
# File 'lib/urbane/vendor/ordered_hash.rb', line 137

def values
  @keys.collect { |key| self[key] }
end