class Hashie::Mash
Mash
allows you to create pseudo-objects that have method-like accessors for hash keys. This is useful for such implementations as an API-accessing library that wants to fake robust objects without the overhead of actually doing so. Think of it as OpenStruct with some additional goodies.
A Mash
will look at the methods you pass it and perform operations based on the following rules:
-
No punctuation: Returns the value of the hash for that key, or nil if none exists.
-
Assignment (
=
): Sets the attribute of the given method name. -
Existence (
?
): Returns true or false depending on whether that key has been set. -
Bang (
!
): Forces the existence of this key, used for deep Mashes. Think of it as “touch” for mashes. -
Under Bang (
_
): Like Bang, but returns a newMash
rather than creating a key. Used to test existance in deep Mashes.
Basic Example¶ ↑
mash = Mash.new mash.name? # => false mash.name = "Bob" mash.name # => "Bob" mash.name? # => true
Hash
Conversion Example¶ ↑
hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]} mash = Mash.new(hash) mash.a.b # => 23 mash.a.d.e # => "abc" mash.f.first.g # => 44 mash.f.last # => 12
Bang Example¶ ↑
mash = Mash.new mash.author # => nil mash.author! # => <Mash> mash = Mash.new mash.author!.name = "Michael Bleigh" mash.author # => <Mash name="Michael Bleigh">
Under Bang Example¶ ↑
mash = Mash.new mash.author # => nil mash.author_ # => <Mash> mash.author_.name # => nil mash = Mash.new mash.author_.name = "Michael Bleigh" (assigned to temp object) mash.author # => <Mash>
Constants
- ALLOWED_SUFFIXES
Public Class Methods
Disable the logging of warnings based on keys conflicting keys/methods
@api semipublic @return [void]
# File lib/hashie/mash.rb, line 76 def self.disable_warnings raise CannotDisableMashWarnings if self == Hashie::Mash @disable_warnings = true end
Checks whether this class disables warnings for conflicting keys/methods
@api semipublic @return [Boolean]
# File lib/hashie/mash.rb, line 85 def self.disable_warnings? @disable_warnings ||= false end
Inheritance hook that sets class configuration when inherited.
@api semipublic @return [void]
# File lib/hashie/mash.rb, line 93 def self.inherited(subclass) super subclass.disable_warnings if disable_warnings? end
# File lib/hashie/mash.rb, line 98 def self.load(path, options = {}) @_mashes ||= new return @_mashes[path] if @_mashes.key?(path) raise ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path) parser = options.fetch(:parser) { Hashie::Extensions::Parsers::YamlErbParser } @_mashes[path] = new(parser.perform(path)).freeze end
If you pass in an existing hash, it will convert it to a Mash
including recursively descending into arrays and hashes, converting them as well.
# File lib/hashie/mash.rb, line 123 def initialize(source_hash = nil, default = nil, &blk) deep_update(source_hash) if source_hash default ? super(default) : super(&blk) end
Public Instance Methods
Assigns a value to a key
# File lib/hashie/mash.rb, line 227 def assign_property(name, value) self[name] = value end
Retrieves an attribute set in the Mash
. Will convert any key passed in to a string before retrieving.
# File lib/hashie/mash.rb, line 135 def custom_reader(key) default_proc.call(self, key) if default_proc && !key?(key) value = regular_reader(convert_key(key)) yield value if block_given? value end
Performs a deep_update
on a duplicate of the current mash.
# File lib/hashie/mash.rb, line 202 def deep_merge(other_hash, &blk) dup.deep_update(other_hash, &blk) end
Recursively merges this mash with the passed in hash, merging each hash in the hierarchy.
# File lib/hashie/mash.rb, line 209 def deep_update(other_hash, &blk) other_hash.each_pair do |k, v| key = convert_key(k) if regular_reader(key).is_a?(Mash) && v.is_a?(::Hash) custom_reader(key).deep_update(v, &blk) else value = convert_value(v, true) value = convert_value(yield(key, self[k], value), true) if blk && key?(k) custom_writer(key, value, false) end end self end
# File lib/hashie/mash.rb, line 178 def delete(key) super(convert_key(key)) end
# File lib/hashie/mash.rb, line 294 def dig(*keys) super(*keys.map { |key| convert_key(key) }) end
Duplicates the current mash as a new mash.
# File lib/hashie/mash.rb, line 188 def dup self.class.new(self, default, &default_proc) end
play nice with ActiveSupport Array#extract_options!
# File lib/hashie/mash.rb, line 284 def extractable_options? true end
# File lib/hashie/mash.rb, line 174 def fetch(key, *args) super(convert_key(key), *args) end
This is the bang method reader, it will return a new Mash
if there isn't a value already assigned to the key requested.
# File lib/hashie/mash.rb, line 157 def initializing_reader(key) ck = convert_key(key) regular_writer(ck, self.class.new) unless key?(ck) regular_reader(ck) end
# File lib/hashie/mash.rb, line 193 def key?(key) super(convert_key(key)) end
# File lib/hashie/mash.rb, line 266 def method_missing(method_name, *args, &blk) # rubocop:disable Style/MethodMissing return self.[](method_name, &blk) if key?(method_name) name, suffix = method_name_and_suffix(method_name) case suffix when '='.freeze assign_property(name, args.first) when '?'.freeze !!self[name] when '!'.freeze initializing_reader(name) when '_'.freeze underbang_reader(name) else self[method_name] end end
# File lib/hashie/mash.rb, line 261 def prefix_method?(method_name) method_name = method_name.to_s method_name.end_with?(*ALLOWED_SUFFIXES) && key?(method_name.chop) end
# File lib/hashie/mash.rb, line 245 def replace(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end
# File lib/hashie/mash.rb, line 251 def respond_to_missing?(method_name, *args) return true if key?(method_name) suffix = method_suffix(method_name) if suffix true else super end end
another ActiveSupport method, see issue #270
# File lib/hashie/mash.rb, line 289 def reverse_merge(other_hash) self.class.new(other_hash).merge(self) end
Performs a shallow_update
on a duplicate of the current mash
# File lib/hashie/mash.rb, line 232 def shallow_merge(other_hash) dup.shallow_update(other_hash) end
Merges (non-recursively) the hash from the argument, changing the receiving hash
# File lib/hashie/mash.rb, line 238 def shallow_update(other_hash) other_hash.each_pair do |k, v| regular_writer(convert_key(k), convert_value(v, true)) end self end
# File lib/hashie/mash.rb, line 108 def to_module(mash_method_name = :settings) mash = self Module.new do |m| m.send :define_method, mash_method_name.to_sym do mash end end end
This is the under bang method reader, it will return a temporary new Mash
if there isn't a value already assigned to the key requested.
# File lib/hashie/mash.rb, line 165 def underbang_reader(key) ck = convert_key(key) if key?(ck) regular_reader(ck) else self.class.new end end
# File lib/hashie/mash.rb, line 182 def values_at(*keys) super(*keys.map { |key| convert_key(key) }) end
Protected Instance Methods
# File lib/hashie/mash.rb, line 301 def method_name_and_suffix(method_name) method_name = method_name.to_s if method_name.end_with?(*ALLOWED_SUFFIXES) [method_name[0..-2], method_name[-1]] else [method_name[0..-1], nil] end end
# File lib/hashie/mash.rb, line 310 def method_suffix(method_name) method_name = method_name.to_s method_name[-1] if method_name.end_with?(*ALLOWED_SUFFIXES) end
Private Instance Methods
# File lib/hashie/mash.rb, line 339 def log_built_in_message(method_key) return if self.class.disable_warnings? method_information = Hashie::Utils.method_information(method(method_key)) Hashie.logger.warn( 'You are setting a key that conflicts with a built-in method ' \ "#{self.class}##{method_key} #{method_information}. " \ 'This can cause unexpected behavior when accessing the key as a ' \ 'property. You can still access the key via the #[] method.' ) end
# File lib/hashie/mash.rb, line 352 def log_collision?(method_key) respond_to?(method_key) && !self.class.disable_warnings? && !(regular_key?(method_key) || regular_key?(method_key.to_s)) end