Class: Hash

Inherits:
Object show all
Defined in:
motion/core_ext/hash/keys.rb,
motion/core_ext/hash/slice.rb,
motion/core_ext/hash/except.rb,
motion/core_ext/object/blank.rb,
motion/core_ext/object/to_query.rb,
motion/core_ext/object/to_param.rb,
motion/core_ext/hash/deep_merge.rb,
motion/core_ext/object/deep_dup.rb,
motion/core_ext/hash/reverse_merge.rb,
motion/core_ext/hash/deep_delete_if.rb,
motion/core_ext/hash/indifferent_access.rb

Instance Method Summary collapse

Instance Method Details

#assert_valid_keys(*valid_keys) ⇒ Object

Validate all keys in a hash match *valid_keys, raising ArgumentError on a mismatch. Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols as keys, this will fail.

{ name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
{ name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
{ name: 'Rob', age: '28' }.assert_valid_keys(:name, :age)   # => passes, raises nothing

67
68
69
70
71
72
# File 'motion/core_ext/hash/keys.rb', line 67

def assert_valid_keys(*valid_keys)
  valid_keys.flatten!
  each_key do |k|
    raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
  end
end

#deep_delete_if(&block) ⇒ Object

Returns a new hash with keys deleted if they match a criteria

h1 = { x: { y: [ { z: 4, y: 1 }, 5, 6] }, a: { b: 2 }  }

h1.deep_delete { |k,v| k == :z } #=> { x: { y: [ { y: 1 }, 5, 6] }, a: { b: 2 }  }
h1.deep_delete { |k,v| k == :y } #=> { x: {}, a: { b: 2 }  }

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'motion/core_ext/hash/deep_delete_if.rb', line 7

def deep_delete_if(&block)
  result = {}
  each do |key, value|
    next if block.call(key, value)

    result[key] = if value.is_a?(Hash)
      value.deep_delete_if(&block)
    elsif value.is_a?(Array)
      value.map { |v| v.is_a?(Hash) ? v.deep_delete_if(&block) : v }
    else
     value
    end
  end

  result
end

#deep_dupObject

Returns a deep copy of hash.

hash = { a: { b: 'b' } }
dup  = hash.deep_dup
dup[:a][:c] = 'c'

hash[:a][:c] #=> nil
dup[:a][:c]  #=> "c"

39
40
41
42
43
# File 'motion/core_ext/object/deep_dup.rb', line 39

def deep_dup
  each_with_object(dup) do |(key, value), hash|
    hash[key.deep_dup] = value.deep_dup
  end
end

#deep_merge(other_hash, &block) ⇒ Object

Returns a new hash with self and other_hash merged recursively.

h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
h2 = { x: { y: [7,8,9] }, z: 'xyz' }

h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"}
h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
#=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}

11
12
13
# File 'motion/core_ext/hash/deep_merge.rb', line 11

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

#deep_merge!(other_hash, &block) ⇒ Object

Same as deep_merge, but modifies self.


16
17
18
19
20
21
22
23
24
25
26
# File 'motion/core_ext/hash/deep_merge.rb', line 16

def deep_merge!(other_hash, &block)
  other_hash.each_pair do |k,v|
    tv = self[k]
    if tv.is_a?(Hash) && v.is_a?(Hash)
      self[k] = tv.deep_merge(v, &block)
    else
      self[k] = block && tv ? block.call(k, tv, v) : v
    end
  end
  self
end

#deep_stringify_keysObject

Return a new hash with all keys converted to strings. This includes the keys from the root hash and from all nested hashes.

hash = { person: { name: 'Rob', age: '28' } }

hash.deep_stringify_keys
# => { "person" => { "name" => "Rob", "age" => "28" } }

121
122
123
# File 'motion/core_ext/hash/keys.rb', line 121

def deep_stringify_keys
  deep_transform_keys{ |key| key.to_s }
end

#deep_stringify_keys!Object

Destructively convert all keys to strings. This includes the keys from the root hash and from all nested hashes.


128
129
130
# File 'motion/core_ext/hash/keys.rb', line 128

def deep_stringify_keys!
  deep_transform_keys!{ |key| key.to_s }
end

#deep_symbolize_keysObject

Return a new hash with all keys converted to symbols, as long as they respond to to_sym. This includes the keys from the root hash and from all nested hashes.

hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }

hash.deep_symbolize_keys
# => { person: { name: "Rob", age: "28" } }

140
141
142
# File 'motion/core_ext/hash/keys.rb', line 140

def deep_symbolize_keys
  deep_transform_keys{ |key| key.to_sym rescue key }
end

#deep_symbolize_keys!Object

Destructively convert all keys to symbols, as long as they respond to to_sym. This includes the keys from the root hash and from all nested hashes.


147
148
149
# File 'motion/core_ext/hash/keys.rb', line 147

def deep_symbolize_keys!
  deep_transform_keys!{ |key| key.to_sym rescue key }
end

#deep_transform_keys(&block) ⇒ Object

Return a new hash with all keys converted by the block operation. This includes the keys from the root hash and from all nested hashes.

hash = { person: { name: 'Rob', age: '28' } }

hash.deep_transform_keys{ |key| key.to_s.upcase }
# => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }

82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'motion/core_ext/hash/keys.rb', line 82

def deep_transform_keys(&block)
  result = {}
  each do |key, value|
    result[yield(key)] = if value.is_a?(Hash)
      value.deep_transform_keys(&block)
    elsif value.is_a?(Array)
      value.map { |v| v.is_a?(Hash) ? v.deep_transform_keys(&block) : v }
    else
     value
    end
  end
  result
end

#deep_transform_keys!(&block) ⇒ Object

Destructively convert all keys by using the block operation. This includes the keys from the root hash and from all nested hashes.


99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'motion/core_ext/hash/keys.rb', line 99

def deep_transform_keys!(&block)
  keys.each do |key|
    value = delete(key)
    self[yield(key)] = if value.is_a?(Hash)
      value.deep_transform_keys(&block)
    elsif value.is_a?(Array)
      value.map { |v| v.is_a?(Hash) ? v.deep_transform_keys(&block) : v }
    else
     value
    end
  end
  self
end

#except(*keys) ⇒ Object

Return a hash that includes everything but the given keys. This is useful for limiting a set of parameters to everything but a few known toggles:

@person.update(params[:person].except(:admin))

6
7
8
# File 'motion/core_ext/hash/except.rb', line 6

def except(*keys)
  dup.except!(*keys)
end

#except!(*keys) ⇒ Object

Replaces the hash without the given keys.


11
12
13
14
# File 'motion/core_ext/hash/except.rb', line 11

def except!(*keys)
  keys.each { |key| delete(key) }
  self
end

#extract!(*keys) ⇒ Object

Removes and returns the key/value pairs matching the given keys.

{ a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
{ a: 1, b: 2 }.extract!(:a, :x)             # => {:a=>1}

37
38
39
# File 'motion/core_ext/hash/slice.rb', line 37

def extract!(*keys)
  keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
end

#reverse_merge(other_hash) ⇒ Object

Merges the caller into other_hash. For example,

options = options.reverse_merge(size: 25, velocity: 10)

is equivalent to

options = { size: 25, velocity: 10 }.merge(options)

This is particularly useful for initializing an options hash with default values.


12
13
14
# File 'motion/core_ext/hash/reverse_merge.rb', line 12

def reverse_merge(other_hash)
  other_hash.merge(self)
end

#reverse_merge!(other_hash) ⇒ Object Also known as: reverse_update

Destructive reverse_merge.


17
18
19
20
# File 'motion/core_ext/hash/reverse_merge.rb', line 17

def reverse_merge!(other_hash)
  # right wins if there is no left
  merge!( other_hash ){|key,left,right| left }
end

#slice(*keys) ⇒ Object

Slice a hash to include only the given keys. This is useful for limiting an options hash to valid keys before passing to a method:

def search(criteria = {})
  criteria.assert_valid_keys(:mass, :velocity, :time)
end

search(options.slice(:mass, :velocity, :time))

If you have an array of keys you want to limit to, you should splat them:

valid_keys = [:mass, :velocity, :time]
search(options.slice(*valid_keys))

15
16
17
18
# File 'motion/core_ext/hash/slice.rb', line 15

def slice(*keys)
  keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
  keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end

#slice!(*keys) ⇒ Object

Replaces the hash with only the given keys. Returns a hash containing the removed key/value pairs.

{ a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
# => {:c=>3, :d=>4}

25
26
27
28
29
30
31
# File 'motion/core_ext/hash/slice.rb', line 25

def slice!(*keys)
  keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
  omit = slice(*self.keys - keys)
  hash = slice(*keys)
  replace(hash)
  omit
end

#stringify_keysObject

Return a new hash with all keys converted to strings.

hash = { name: 'Rob', age: '28' }

hash.stringify_keys
#=> { "name" => "Rob", "age" => "28" }

31
32
33
# File 'motion/core_ext/hash/keys.rb', line 31

def stringify_keys
  transform_keys{ |key| key.to_s }
end

#stringify_keys!Object

Destructively convert all keys to strings. Same as stringify_keys, but modifies self.


37
38
39
# File 'motion/core_ext/hash/keys.rb', line 37

def stringify_keys!
  transform_keys!{ |key| key.to_s }
end

#symbolize_keysObject Also known as: to_options

Return a new hash with all keys converted to symbols, as long as they respond to to_sym.

hash = { 'name' => 'Rob', 'age' => '28' }

hash.symbolize_keys
#=> { name: "Rob", age: "28" }

48
49
50
# File 'motion/core_ext/hash/keys.rb', line 48

def symbolize_keys
  transform_keys{ |key| key.to_sym rescue key }
end

#symbolize_keys!Object Also known as: to_options!

Destructively convert all keys to symbols, as long as they respond to to_sym. Same as symbolize_keys, but modifies self.


55
56
57
# File 'motion/core_ext/hash/keys.rb', line 55

def symbolize_keys!
  transform_keys!{ |key| key.to_sym rescue key }
end

#to_param(namespace = nil) ⇒ Object

Returns a string representation of the receiver suitable for use as a URL query string:

{name: 'David', nationality: 'Danish'}.to_param
# => "name=David&nationality=Danish"

An optional namespace can be passed to enclose the param names:

{name: 'David', nationality: 'Danish'}.to_param('user')
# => "user[name]=David&user[nationality]=Danish"

The string pairs “key=value” that conform the query string are sorted lexicographically in ascending order.

This method is also aliased as to_query.


53
54
55
56
57
# File 'motion/core_ext/object/to_param.rb', line 53

def to_param(namespace = nil)
  collect do |key, value|
    value.to_query(namespace ? "#{namespace}[#{key}]" : key)
  end.sort * '&'
end

#to_queryObject


25
# File 'motion/core_ext/object/to_query.rb', line 25

alias_method :to_query, :to_param

#transform_keysObject

Return a new hash with all keys converted using the block operation.

hash = { name: 'Rob', age: '28' }

hash.transform_keys{ |key| key.to_s.upcase }
# => { "NAME" => "Rob", "AGE" => "28" }

8
9
10
11
12
13
14
# File 'motion/core_ext/hash/keys.rb', line 8

def transform_keys
  result = {}
  each_key do |key|
    result[yield(key)] = self[key]
  end
  result
end

#transform_keys!Object

Destructively convert all keys using the block operations. Same as transform_keys but modifies self.


18
19
20
21
22
23
# File 'motion/core_ext/hash/keys.rb', line 18

def transform_keys!
  keys.each do |key|
    self[yield(key)] = delete(key)
  end
  self
end

#with_indifferent_accessObject Also known as: nested_under_indifferent_access

Returns an MotionSupport::HashWithIndifferentAccess out of its receiver:

{ a: 1 }.with_indifferent_access['a'] # => 1

5
6
7
# File 'motion/core_ext/hash/indifferent_access.rb', line 5

def with_indifferent_access
  MotionSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
end