Class: Hash

Inherits:
Object show all
Defined in:
lib/active_support/json/encoding.rb,
lib/active_support/core_ext/hash/diff.rb,
lib/active_support/core_ext/hash/keys.rb,
lib/active_support/core_ext/hash/slice.rb,
lib/active_support/core_ext/hash/except.rb,
lib/active_support/core_ext/object/blank.rb,
lib/active_support/core_ext/hash/deep_merge.rb,
lib/active_support/core_ext/object/deep_dup.rb,
lib/active_support/core_ext/object/to_param.rb,
lib/active_support/core_ext/object/to_query.rb,
lib/active_support/core_ext/hash/conversions.rb,
lib/active_support/core_ext/hash/reverse_merge.rb,
lib/active_support/core_ext/array/extract_options.rb,
lib/active_support/core_ext/hash/indifferent_access.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_trusted_xml(xml) ⇒ Object

Builds a Hash from XML just like Hash.from_xml, but also allows Symbol and YAML.



112
113
114
# File 'lib/active_support/core_ext/hash/conversions.rb', line 112

def from_trusted_xml(xml)
  from_xml xml, []
end

.from_xml(xml, disallowed_types = nil) ⇒ Object

Returns a Hash containing a collection of pairs when the key is the node name and the value is its content

xml = <<-XML
  <?xml version="1.0" encoding="UTF-8"?>
    <hash>
      <foo type="integer">1</foo>
      <bar type="integer">2</bar>
    </hash>
XML

hash = Hash.from_xml(xml)
# => {"hash"=>{"foo"=>1, "bar"=>2}}

DisallowedType is raise if the XML contains attributes with type="yaml" or type="symbol". Use Hash.from_trusted_xml to parse this XML.



107
108
109
# File 'lib/active_support/core_ext/hash/conversions.rb', line 107

def from_xml(xml, disallowed_types = nil)
  ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end

Instance Method Details

#as_json(options = nil) ⇒ Object

:nodoc:



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/active_support/json/encoding.rb', line 280

def as_json(options = nil) #:nodoc:
  # create a subset of the hash by applying :only or :except
  subset = if options
    if attrs = options[:only]
      slice(*Array(attrs))
    elsif attrs = options[:except]
      except(*Array(attrs))
    else
      self
    end
  else
    self
  end

  # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
  encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
  Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
end

#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 'lib/active_support/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_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"


41
42
43
44
45
# File 'lib/active_support/core_ext/object/deep_dup.rb', line 41

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 = { a: true, b: { c: [1, 2, 3] } }
h2 = { a: false, b: { x: [3, 4, 5] } }

h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }

Like with Hash#merge in the standard library, a block can be provided to merge values:

h1 = { a: 100, b: 200, c: { c1: 100 } }
h2 = { b: 250, c: { c1: 200 } }
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
# => { a: 100, b: 450, c: { c1: 300 } }


16
17
18
# File 'lib/active_support/core_ext/hash/deep_merge.rb', line 16

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.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/active_support/core_ext/hash/deep_merge.rb', line 21

def deep_merge!(other_hash, &block)
  other_hash.each_pair do |current_key, other_value|
    this_value = self[current_key]

    self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
      this_value.deep_merge(other_value, &block)
    else
      if block_given? && key?(current_key)
        block.call(current_key, this_value, other_value)
      else
        other_value
      end
    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 and arrays.

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

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


101
102
103
# File 'lib/active_support/core_ext/hash/keys.rb', line 101

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 and arrays.



108
109
110
# File 'lib/active_support/core_ext/hash/keys.rb', line 108

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 and arrays.

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

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


120
121
122
# File 'lib/active_support/core_ext/hash/keys.rb', line 120

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 and arrays.



127
128
129
# File 'lib/active_support/core_ext/hash/keys.rb', line 127

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 and arrays.

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

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


82
83
84
# File 'lib/active_support/core_ext/hash/keys.rb', line 82

def deep_transform_keys(&block)
  _deep_transform_keys_in_object(self, &block)
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 and arrays.



89
90
91
# File 'lib/active_support/core_ext/hash/keys.rb', line 89

def deep_transform_keys!(&block)
  _deep_transform_keys_in_object!(self, &block)
end

#diff(other) ⇒ Object

Returns a hash that represents the difference between two hashes.

{1 => 2}.diff(1 => 2)         # => {}
{1 => 2}.diff(1 => 3)         # => {1 => 2}
{}.diff(1 => 2)               # => {1 => 2}
{1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}


8
9
10
11
12
13
# File 'lib/active_support/core_ext/hash/diff.rb', line 8

def diff(other)
  ActiveSupport::Deprecation.warn "Hash#diff is no longer used inside of Rails, and is being deprecated with no replacement. If you're using it to compare hashes for the purpose of testing, please use MiniTest's assert_equal instead."
  dup.
    delete_if { |k, v| other[k] == v }.
    merge!(other.dup.delete_if { |k, v| has_key?(k) })
end

#encode_json(encoder) ⇒ Object

:nodoc:



299
300
301
302
303
304
305
306
307
# File 'lib/active_support/json/encoding.rb', line 299

def encode_json(encoder) #:nodoc:
  # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
  # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);

  # on the other hand, we need to run as_json on the elements, because the model representation may contain fields
  # like Time/Date in their original (not jsonified) form, etc.

  "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
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 'lib/active_support/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 'lib/active_support/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}


39
40
41
# File 'lib/active_support/core_ext/hash/slice.rb', line 39

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

#extractable_options?Boolean

By default, only instances of Hash itself are extractable. Subclasses of Hash may implement this method and return true to declare themselves as extractable. If a Hash is extractable, Array#extract_options! pops it from the Array when it is the last element of the Array.

Returns:

  • (Boolean)


7
8
9
# File 'lib/active_support/core_ext/array/extract_options.rb', line 7

def extractable_options?
  instance_of?(Hash)
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 'lib/active_support/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 'lib/active_support/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 'lib/active_support/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
32
33
# File 'lib/active_support/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)
  hash.default      = default
  hash.default_proc = default_proc if default_proc
  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 'lib/active_support/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 'lib/active_support/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 'lib/active_support/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 'lib/active_support/core_ext/hash/keys.rb', line 55

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

#to_param(namespace = nil) ⇒ Object Also known as: to_query

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 'lib/active_support/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_xml(options = {}) ⇒ Object

Returns a string containing an XML representation of its receiver:

{'foo' => 1, 'bar' => 2}.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
#   <foo type="integer">1</foo>
#   <bar type="integer">2</bar>
# </hash>

To do so, the method loops over the pairs and builds nodes that depend on the values. Given a pair key, value:

  • If value is a hash there’s a recursive call with key as :root.

  • If value is an array there’s a recursive call with key as :root, and key singularized as :children.

  • If value is a callable object it must expect one or two arguments. Depending on the arity, the callable is invoked with the options hash as first argument with key as :root, and key singularized as second argument. The callable can add nodes by using options[:builder].

    'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
    # => "<b>foo</b>"
    
  • If value responds to to_xml the method is invoked with key as :root.

    class Foo
      def to_xml(options)
        options[:builder].bar 'fooing!'
      end
    end
    
    { foo: Foo.new }.to_xml(skip_instruct: true)
    # => "<hash><bar>fooing!</bar></hash>"
    
  • Otherwise, a node with key as tag is created with a string representation of value as text node. If value is nil an attribute “nil” set to “true” is added. Unless the option :skip_types exists and is true, an attribute “type” is added as well according to the following mapping:

    XML_TYPE_NAMES = {
      "Symbol"     => "symbol",
      "Fixnum"     => "integer",
      "Bignum"     => "integer",
      "BigDecimal" => "decimal",
      "Float"      => "float",
      "TrueClass"  => "boolean",
      "FalseClass" => "boolean",
      "Date"       => "date",
      "DateTime"   => "dateTime",
      "Time"       => "dateTime"
    }
    

By default the root node is “hash”, but that’s configurable via the :root option.

The default XML builder is a fresh instance of Builder::XmlMarkup. You can configure your own builder with the :builder option. The method also accepts options like :dasherize and friends, they are forwarded to the builder.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/active_support/core_ext/hash/conversions.rb', line 71

def to_xml(options = {})
  require 'active_support/builder' unless defined?(Builder)

  options = options.dup
  options[:indent]  ||= 2
  options[:root]    ||= 'hash'
  options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])

  builder = options[:builder]
  builder.instruct! unless options.delete(:skip_instruct)

  root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)

  builder.tag!(root) do
    each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
    yield builder if block_given?
  end
end

#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 'lib/active_support/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 'lib/active_support/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 ActiveSupport::HashWithIndifferentAccess out of its receiver:

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


8
9
10
# File 'lib/active_support/core_ext/hash/indifferent_access.rb', line 8

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