class Hash
def _deep_transform_keys_in_object(object, &block)
def _deep_transform_keys_in_object(object, &block) case object when Hash object.each_with_object({}) do |(key, value), result| result[yield(key)] = _deep_transform_keys_in_object(value, &block) end when Array object.map {|e| _deep_transform_keys_in_object(e, &block) } else object end end
def _deep_transform_keys_in_object!(object, &block)
def _deep_transform_keys_in_object!(object, &block) case object when Hash object.keys.each do |key| value = object.delete(key) object[yield(key)] = _deep_transform_keys_in_object!(value, &block) end object when Array object.map! {|e| _deep_transform_keys_in_object!(e, &block)} else object end end
def as_json(options = nil) #:nodoc:
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
def assert_valid_keys(*valid_keys)
{ name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
{ name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
use strings for keys but assert symbols as keys, this will fail.
on a mismatch. Note that keys are NOT treated indifferently, meaning if you
Validate all keys in a hash match *valid_keys, raising ArgumentError
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
def deep_dup
hash[:a][:c] #=> nil
dup[:a][:c] = 'c'
dup = hash.deep_dup
hash = { a: { b: 'b' } }
Returns a deep copy of hash.
def deep_dup each_with_object(dup) do |(key, value), hash| hash[key.deep_dup] = value.deep_dup end end
def deep_merge(other_hash, &block)
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
h2 = { b: 250, c: { c1: 200 } }
h1 = { a: 100, b: 200, c: { c1: 100 } }
to merge values:
Like with Hash#merge in the standard library, a block can be provided
h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
h2 = { a: false, b: { x: [3, 4, 5] } }
h1 = { a: true, b: { c: [1, 2, 3] } }
Returns a new hash with +self+ and +other_hash+ merged recursively.
def deep_merge(other_hash, &block) dup.deep_merge!(other_hash, &block) end
def deep_merge!(other_hash, &block)
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
def deep_stringify_keys
hash.deep_stringify_keys
hash = { person: { name: 'Rob', age: '28' } }
nested hashes and arrays.
This includes the keys from the root hash and from all
Return a new hash with all keys converted to strings.
def deep_stringify_keys deep_transform_keys{ |key| key.to_s } end
def deep_stringify_keys!
This includes the keys from the root hash and from all
Destructively convert all keys to strings.
def deep_stringify_keys! deep_transform_keys!{ |key| key.to_s } end
def deep_symbolize_keys
hash.deep_symbolize_keys
hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
and from all nested hashes and arrays.
they respond to +to_sym+. This includes the keys from the root hash
Return a new hash with all keys converted to symbols, as long as
def deep_symbolize_keys deep_transform_keys{ |key| key.to_sym rescue key } end
def deep_symbolize_keys!
to +to_sym+. This includes the keys from the root hash and from all
Destructively convert all keys to symbols, as long as they respond
def deep_symbolize_keys! deep_transform_keys!{ |key| key.to_sym rescue key } end
def deep_transform_keys(&block)
hash.deep_transform_keys{ |key| key.to_s.upcase }
hash = { person: { name: 'Rob', age: '28' } }
nested hashes and arrays.
This includes the keys from the root hash and from all
Return a new hash with all keys converted by the block operation.
def deep_transform_keys(&block) _deep_transform_keys_in_object(self, &block) end
def deep_transform_keys!(&block)
This includes the keys from the root hash and from all
Destructively convert all keys by using the block operation.
def deep_transform_keys!(&block) _deep_transform_keys_in_object!(self, &block) end
def diff(other)
{}.diff(1 => 2) # => {1 => 2}
{1 => 2}.diff(1 => 3) # => {1 => 2}
{1 => 2}.diff(1 => 2) # => {}
Returns a hash that represents the difference between two hashes.
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
def encode_json(encoder) #:nodoc:
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
def except(*keys)
limiting a set of parameters to everything but a few known toggles:
Return a hash that includes everything but the given keys. This is useful for
def except(*keys) dup.except!(*keys) end
def except!(*keys)
def except!(*keys) keys.each { |key| delete(key) } self end
def extract!(*keys)
{ a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
Removes and returns the key/value pairs matching the given keys.
def extract!(*keys) keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } end
def extractable_options?
is extractable, Array#extract_options! pops it from
true to declare themselves as extractable. If a Hash
Subclasses of Hash may implement this method and return
By default, only instances of Hash itself are extractable.
def extractable_options? instance_of?(Hash) end
def from_trusted_xml(xml)
def from_trusted_xml(xml) from_xml xml, [] end
def from_xml(xml, disallowed_types = nil)
DisallowedType is raise if the XML contains attributes with type="yaml" or
# => {"hash"=>{"foo"=>1, "bar"=>2}}
hash = Hash.from_xml(xml)
XML
xml = <<-XML
its content
Returns a Hash containing a collection of pairs when the key is the node name and the value is
def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h end
def reverse_merge(other_hash)
This is particularly useful for initializing an options hash
options = { size: 25, velocity: 10 }.merge(options)
is equivalent to
options = options.reverse_merge(size: 25, velocity: 10)
Merges the caller into +other_hash+. For example,
def reverse_merge(other_hash) other_hash.merge(self) end
def reverse_merge!(other_hash)
def reverse_merge!(other_hash) # right wins if there is no left merge!( other_hash ){|key,left,right| left } end
def slice(*keys)
valid_keys = [:mass, :velocity, :time]
If you have an array of keys you want to limit to, you should splat them:
search(options.slice(:mass, :velocity, :time))
end
criteria.assert_valid_keys(:mass, :velocity, :time)
def search(criteria = {})
limiting an options hash to valid keys before passing to a method:
Slice a hash to include only the given keys. This is useful for
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
def slice!(*keys)
{ a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
Returns a hash containing the removed key/value pairs.
Replaces the hash with only the given keys.
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
def stringify_keys
hash.stringify_keys
hash = { name: 'Rob', age: '28' }
Return a new hash with all keys converted to strings.
def stringify_keys transform_keys{ |key| key.to_s } end
def stringify_keys!
Destructively convert all keys to strings. Same as
def stringify_keys! transform_keys!{ |key| key.to_s } end
def symbolize_keys
hash.symbolize_keys
hash = { 'name' => 'Rob', 'age' => '28' }
they respond to +to_sym+.
Return a new hash with all keys converted to symbols, as long as
def symbolize_keys transform_keys{ |key| key.to_sym rescue key } end
def symbolize_keys!
Destructively convert all keys to symbols, as long as they respond
def symbolize_keys! transform_keys!{ |key| key.to_sym rescue key } end
def to_query(namespace = nil)
are sorted lexicographically in ascending order.
The string pairs "key=value" that conform the query string
# => "user[name]=David&user[nationality]=Danish"
{name: 'David', nationality: 'Danish'}.to_query('user')
An optional namespace can be passed to enclose the param names:
# => "name=David&nationality=Danish"
{name: 'David', nationality: 'Danish'}.to_query
query string:
Returns a string representation of the receiver suitable for use as a URL
def to_query(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) end.sort * '&' end
def to_xml(options = {})
configure your own builder with the :builder option. The method also accepts
The default XML builder is a fresh instance of Builder::XmlMarkup. You can
By default the root node is "hash", but that's configurable via the :root option.
}
"Time" => "dateTime"
"DateTime" => "dateTime",
"Date" => "date",
"FalseClass" => "boolean",
"TrueClass" => "boolean",
"Float" => "float",
"BigDecimal" => "decimal",
"Bignum" => "integer",
"Fixnum" => "integer",
"Symbol" => "symbol",
XML_TYPE_NAMES = {
added as well according to the following mapping:
Unless the option :skip_types exists and is true, an attribute "type" is
+value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
* Otherwise, a node with +key+ as tag is created with a string representation of
# => "
{ foo: Foo.new }.to_xml(skip_instruct: true)
end
end
options[:builder].bar 'fooing!'
def to_xml(options)
class Foo
* If +value+ responds to +to_xml+ the method is invoked with +key+ as :root.
# => "foo"
'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
callable can add nodes by using options[:builder].
with +key+ as :root, and +key+ singularized as second argument. The
on the arity, the callable is invoked with the +options+ hash as first argument
* If +value+ is a callable object it must expect one or two arguments. Depending
and +key+ singularized as :children.
* If +value+ is an array there's a recursive call with +key+ as :root,
* If +value+ is a hash there's a recursive call with +key+ as :root.
the _values_. Given a pair +key+, +value+:
To do so, the method loops over the pairs and builds nodes that depend on
#
#
#
#
#
# =>
{'foo' => 1, 'bar' => 2}.to_xml
Returns a string containing an XML representation of its receiver:
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
def transform_keys
hash.transform_keys{ |key| key.to_s.upcase }
hash = { name: 'Rob', age: '28' }
Return a new hash with all keys converted using the block operation.
def transform_keys result = {} each_key do |key| result[yield(key)] = self[key] end result end
def transform_keys!
Destructively convert all keys using the block operations.
def transform_keys! keys.each do |key| self[yield(key)] = delete(key) end self end
def with_indifferent_access
Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver:
def with_indifferent_access ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) end