Class: InsensitiveHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/insensitive_hash/insensitive_hash.rb,
lib/insensitive_hash/version.rb

Overview

Insensitive Hash.

Author:

Defined Under Namespace

Classes: KeyClashError

Constant Summary collapse

VERSION =
"0.3.3"
DEFAULT_ENCODER =
proc { |key|
  case key
  when String, Symbol
    key.to_s.downcase.gsub(' ', '_')
  else
    key
  end
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Hash

#insensitive

Constructor Details

#initialize(default = nil, &block) ⇒ InsensitiveHash

Returns a new instance of InsensitiveHash.



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/insensitive_hash/insensitive_hash.rb', line 21

def initialize default = nil, &block
  if block_given?
    raise ArgumentError.new('wrong number of arguments') unless default.nil?
    super(&block)
  else
    super
  end

  @key_map = {}
  @safe    = false
  @encoder = DEFAULT_ENCODER
end

Instance Attribute Details

#encoder#call

Returns Key encoder. Determines the level of insensitivity.

Returns:

  • (#call)

    Key encoder. Determines the level of insensitivity.



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/insensitive_hash/insensitive_hash.rb', line 5

class InsensitiveHash < Hash
  attr_reader :encoder

  # Thrown when safe mode is on and another Hash with conflicting keys cannot be merged safely
  class KeyClashError < Exception
  end

  DEFAULT_ENCODER = proc { |key|
    case key
    when String, Symbol
      key.to_s.downcase.gsub(' ', '_')
    else
      key
    end
  }

  def initialize default = nil, &block
    if block_given?
      raise ArgumentError.new('wrong number of arguments') unless default.nil?
      super(&block)
    else
      super
    end

    @key_map = {}
    @safe    = false
    @encoder = DEFAULT_ENCODER
  end

  # Sets whether to detect key clashes
  # @param [Boolean]
  # @return [Boolean]
  def safe= s
    raise ArgumentError.new("Neither true nor false") unless [true, false].include?(s)
    @safe = s
  end

  # @return [Boolean] Key-clash detection enabled?
  def safe?
    @safe
  end

  # @param [#call] prc Key encoder. Determines the level of insensitivity.
  # @return [#call]
  def encoder= prc
    raise ArgumentError, "Encoder must respond to :call" unless prc.respond_to?(:call)

    kvs = to_a
    clear
    @encoder = prc
    kvs.each do |pair|
      store(*pair)
    end

    prc
  end

  # Returns a normal, sensitive Hash
  # @return [Hash]
  def to_hash
    {}.merge self
  end
  alias sensitive to_hash

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def self.[] *init
    h = Hash[*init]
    self.new.tap do |ih|
      ih.merge_recursive! h
    end
  end

  %w[[] assoc has_key? include? key? member?].each do |symb|
    class_eval <<-EVAL
      def #{symb} key
        super lookup_key(key)
      end
    EVAL
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def []= key, value
    delete key
    ekey = encode key
    @key_map[ekey] = key
    super key, value
  end
  alias store []=

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def merge! other_hash
    detect_clash other_hash
    other_hash.each do |key, value|
      store key, value
    end
    self
  end
  alias update! merge!

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def merge other_hash
    self.class.new.tap do |ih|
      ih.replace self
      ih.merge! other_hash
    end
  end
  alias update merge

  # Merge another hash recursively.
  # @param [Hash|InsensitiveHash] other_hash
  # @return [self]
  def merge_recursive! other_hash
    detect_clash other_hash
    other_hash.each do |key, value|
      deep_set key, value
    end
    self
  end
  alias update_recursive! merge_recursive!

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def delete key, &block
    super lookup_key(key, true), &block
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def clear
    @key_map.clear
    super
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def replace other
    super other

    self.safe = other.respond_to?(:safe?) ? other.safe? : safe?
    self.encoder = other.respond_to?(:encoder) ? other.encoder : DEFAULT_ENCODER

    @key_map.clear
    self.each do |k, v|
      ekey = encode k
      @key_map[ekey] = k
    end
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def shift
    super.tap do |ret|
      @key_map.delete_if { |k, v| v == ret.first }
    end
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def values_at *keys
    keys.map { |k| self[k] }
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def fetch *args, &block
    args[0] = lookup_key(args[0]) if args.first
    super(*args, &block)
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def dup
    super.tap { |copy|
      copy.instance_variable_set :@key_map, @key_map.dup
    }
  end

  # @see http://www.ruby-doc.org/core-1.9.3/Hash.html Hash
  def clone
    super.tap { |copy|
      copy.instance_variable_set :@key_map, @key_map.dup
    }
  end

private
  def deep_set key, value
    wv = wrap value
    self[key] = wv
  end

  def wrap value
    case value
    when InsensitiveHash, self.class
      value.tap { |ih|
        ih.safe = safe?
        ih.encoder = encoder
      }
    when Hash
      self.class.new.tap { |ih|
        ih.safe = safe?
        ih.encoder = encoder
        ih.merge_recursive!(value)
      }
    when Array
      value.map { |v| wrap v }
    else
      value
    end
  end

  def lookup_key key, delete = false
    ekey = encode key
    if @key_map.has_key?(ekey)
      delete ? @key_map.delete(ekey) : @key_map[ekey]
    else
      key
    end
  end

  def encode key
    @encoder.call key
  end

  def detect_clash hash
    hash.keys.map { |k| encode k }.tap { |ekeys|
      raise KeyClashError.new("Key clash detected") if ekeys != ekeys.uniq
    } if @safe
  end
end

Class Method Details

.[](*init) ⇒ Object

See Also:



70
71
72
73
74
75
# File 'lib/insensitive_hash/insensitive_hash.rb', line 70

def self.[] *init
  h = Hash[*init]
  self.new.tap do |ih|
    ih.merge_recursive! h
  end
end

Instance Method Details

#[]=(key, value) ⇒ Object Also known as: store

See Also:



86
87
88
89
90
91
# File 'lib/insensitive_hash/insensitive_hash.rb', line 86

def []= key, value
  delete key
  ekey = encode key
  @key_map[ekey] = key
  super key, value
end

#clearObject

See Also:



131
132
133
134
# File 'lib/insensitive_hash/insensitive_hash.rb', line 131

def clear
  @key_map.clear
  super
end

#cloneObject

See Also:



176
177
178
179
180
# File 'lib/insensitive_hash/insensitive_hash.rb', line 176

def clone
  super.tap { |copy|
    copy.instance_variable_set :@key_map, @key_map.dup
  }
end

#delete(key, &block) ⇒ Object

See Also:



126
127
128
# File 'lib/insensitive_hash/insensitive_hash.rb', line 126

def delete key, &block
  super lookup_key(key, true), &block
end

#dupObject

See Also:



169
170
171
172
173
# File 'lib/insensitive_hash/insensitive_hash.rb', line 169

def dup
  super.tap { |copy|
    copy.instance_variable_set :@key_map, @key_map.dup
  }
end

#fetch(*args, &block) ⇒ Object

See Also:



163
164
165
166
# File 'lib/insensitive_hash/insensitive_hash.rb', line 163

def fetch *args, &block
  args[0] = lookup_key(args[0]) if args.first
  super(*args, &block)
end

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

See Also:



105
106
107
108
109
110
# File 'lib/insensitive_hash/insensitive_hash.rb', line 105

def merge other_hash
  self.class.new.tap do |ih|
    ih.replace self
    ih.merge! other_hash
  end
end

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

See Also:



95
96
97
98
99
100
101
# File 'lib/insensitive_hash/insensitive_hash.rb', line 95

def merge! other_hash
  detect_clash other_hash
  other_hash.each do |key, value|
    store key, value
  end
  self
end

#merge_recursive!(other_hash) ⇒ self Also known as: update_recursive!

Merge another hash recursively.

Parameters:

Returns:

  • (self)


116
117
118
119
120
121
122
# File 'lib/insensitive_hash/insensitive_hash.rb', line 116

def merge_recursive! other_hash
  detect_clash other_hash
  other_hash.each do |key, value|
    deep_set key, value
  end
  self
end

#replace(other) ⇒ Object

See Also:



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/insensitive_hash/insensitive_hash.rb', line 137

def replace other
  super other

  self.safe = other.respond_to?(:safe?) ? other.safe? : safe?
  self.encoder = other.respond_to?(:encoder) ? other.encoder : DEFAULT_ENCODER

  @key_map.clear
  self.each do |k, v|
    ekey = encode k
    @key_map[ekey] = k
  end
end

#safe=(s) ⇒ Boolean

Sets whether to detect key clashes

Parameters:

  • (Boolean)

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


37
38
39
40
# File 'lib/insensitive_hash/insensitive_hash.rb', line 37

def safe= s
  raise ArgumentError.new("Neither true nor false") unless [true, false].include?(s)
  @safe = s
end

#safe?Boolean

Returns Key-clash detection enabled?.

Returns:

  • (Boolean)

    Key-clash detection enabled?



43
44
45
# File 'lib/insensitive_hash/insensitive_hash.rb', line 43

def safe?
  @safe
end

#shiftObject

See Also:



151
152
153
154
155
# File 'lib/insensitive_hash/insensitive_hash.rb', line 151

def shift
  super.tap do |ret|
    @key_map.delete_if { |k, v| v == ret.first }
  end
end

#to_hashHash Also known as: sensitive

Returns a normal, sensitive Hash

Returns:



64
65
66
# File 'lib/insensitive_hash/insensitive_hash.rb', line 64

def to_hash
  {}.merge self
end

#values_at(*keys) ⇒ Object

See Also:



158
159
160
# File 'lib/insensitive_hash/insensitive_hash.rb', line 158

def values_at *keys
  keys.map { |k| self[k] }
end