Class: Hashie::Mash

Inherits:
Hash
  • Object
show all
Extended by:
Extensions::KeyConflictWarning
Includes:
Extensions::RubyVersionCheck
Defined in:
lib/hashie/mash.rb

Overview

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.
  • Truthiness (?): Returns true or false depending on the truthiness of the attribute, or false if the key is not 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 new Mash 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 = => {:b => 23, :d => {:e => "abc"}, :f => [=> 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.new mash.author!.name = "Michael Bleigh" mash.author # =>

== Under Bang Example

mash = Mash.new mash.author # => nil mash.author_ # => mash.author_.name # => nil

mash = Mash.new mash.author_.name = "Michael Bleigh" (assigned to temp object) mash.author # =>

Constant Summary collapse

ALLOWED_SUFFIXES =
%w[? ! = _].freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extensions::KeyConflictWarning

disable_warnings, disable_warnings?, disabled_warnings, inherited

Methods included from Extensions::RubyVersionCheck

included

Methods inherited from Hash

#to_hash, #to_json, #to_mash

Methods included from Extensions::StringifyKeys

#stringify_keys, #stringify_keys!

Methods included from Extensions::StringifyKeys::ClassMethods

#stringify_keys, #stringify_keys!, #stringify_keys_recursively!

Methods included from Extensions::PrettyInspect

#hashie_inspect, included

Constructor Details

#initialize(source_hash = nil, default = nil, &blk) ⇒ Mash

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.


100
101
102
103
# File 'lib/hashie/mash.rb', line 100

def initialize(source_hash = nil, default = nil, &blk)
  deep_update(source_hash) if source_hash
  default ? super(default) : super(&blk)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &blk) ⇒ Object

rubocop:disable Style/MethodMissing


298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/hashie/mash.rb', line 298

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

Class Method Details

.load(path, options = {}) ⇒ Object

Raises:

  • (ArgumentError)

70
71
72
73
74
75
76
77
78
79
# File 'lib/hashie/mash.rb', line 70

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)

  options = options.dup
  parser = options.delete(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
  @_mashes[path] = new(parser.perform(path, options)).freeze
end

.quiet(*method_keys) ⇒ Object

Creates a new anonymous subclass with key conflict warnings disabled. You may pass an array of method symbols to restrict the disabled warnings to. Hashie::Mash.quiet.new(hash) all warnings disabled. Hashie::Mash.quiet(:zip).new(hash) only zip warning is disabled.


111
112
113
114
115
116
# File 'lib/hashie/mash.rb', line 111

def self.quiet(*method_keys)
  @memoized_classes ||= {}
  @memoized_classes[method_keys] ||= Class.new(self) do
    disable_warnings(*method_keys)
  end
end

Instance Method Details

#assign_property(name, value) ⇒ Object

Assigns a value to a key


259
260
261
# File 'lib/hashie/mash.rb', line 259

def assign_property(name, value)
  self[name] = value
end

#compactObject

Returns a new instance of the class it was called on, with nil values removed.


335
336
337
# File 'lib/hashie/mash.rb', line 335

def compact
  self.class.new(super)
end

#custom_reader(key) {|value| ... } ⇒ Object Also known as: []

Retrieves an attribute set in the Mash. Will convert a key passed in as a symbol to a string before retrieving.

Yields:

  • (value)

125
126
127
128
129
130
# File 'lib/hashie/mash.rb', line 125

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

#custom_writer(key, value, convert = true) ⇒ Object Also known as: []=

Sets an attribute in the Mash. Symbol keys will be converted to strings before being set, and Hashes will be converted into Mashes for nesting purposes.


135
136
137
138
# File 'lib/hashie/mash.rb', line 135

def custom_writer(key, value, convert = true) #:nodoc:
  log_built_in_message(key) if key.respond_to?(:to_sym) && log_collision?(key.to_sym)
  regular_writer(convert_key(key), convert ? convert_value(value) : value)
end

#deep_merge(other_hash, &blk) ⇒ Object Also known as: merge

Performs a deep_update on a duplicate of the current mash.


210
211
212
# File 'lib/hashie/mash.rb', line 210

def deep_merge(*other_hashes, &blk)
  dup.deep_update(*other_hashes, &blk)
end

#deep_update(other_hash, &blk) ⇒ Object Also known as: deep_merge!, update

Recursively merges this mash with the passed in hash, merging each hash in the hierarchy.


216
217
218
219
220
221
# File 'lib/hashie/mash.rb', line 216

def deep_update(*other_hashes, &blk)
  other_hashes.each do |other_hash|
    _deep_update(other_hash, &blk)
  end
  self
end

#delete(key) ⇒ Object


166
167
168
# File 'lib/hashie/mash.rb', line 166

def delete(key)
  super(convert_key(key))
end

#dig(*keys) ⇒ Object


325
326
327
# File 'lib/hashie/mash.rb', line 325

def dig(*keys)
  super(*keys.map { |key| convert_key(key) })
end

#dupObject

Duplicates the current mash as a new mash.


195
196
197
# File 'lib/hashie/mash.rb', line 195

def dup
  self.class.new(self, default, &default_proc)
end

#extractable_options?Boolean

play nice with ActiveSupport Array#extract_options!

Returns:

  • (Boolean)

316
317
318
# File 'lib/hashie/mash.rb', line 316

def extractable_options?
  true
end

#fetch(key, *args) ⇒ Object


162
163
164
# File 'lib/hashie/mash.rb', line 162

def fetch(key, *args)
  super(convert_key(key), *args)
end

#initializing_reader(key) ⇒ Object

This is the bang method reader, it will return a new Mash if there isn't a value already assigned to the key requested.


145
146
147
148
149
# File 'lib/hashie/mash.rb', line 145

def initializing_reader(key)
  ck = convert_key(key)
  regular_writer(ck, self.class.new) unless key?(ck)
  regular_reader(ck)
end

#invertObject

Returns a new instance of the class it was called on, using its keys as values, and its values as keys. The new values and keys will always be strings.


177
178
179
# File 'lib/hashie/mash.rb', line 177

def invert
  self.class.new(super)
end

#key?(key) ⇒ Boolean Also known as: has_key?, include?, member?

Returns:

  • (Boolean)

200
201
202
# File 'lib/hashie/mash.rb', line 200

def key?(key)
  super(convert_key(key))
end

#prefix_method?(method_name) ⇒ Boolean

Returns:

  • (Boolean)

293
294
295
296
# File 'lib/hashie/mash.rb', line 293

def prefix_method?(method_name)
  method_name = method_name.to_s
  method_name.end_with?(*ALLOWED_SUFFIXES) && key?(method_name.chop)
end

#regular_dupObject


193
# File 'lib/hashie/mash.rb', line 193

alias regular_dup dup

#regular_key?Object


199
# File 'lib/hashie/mash.rb', line 199

alias regular_key? key?

#reject(&blk) ⇒ Object

Returns a new instance of the class it was called on, containing elements for which the given block returns false.


183
184
185
# File 'lib/hashie/mash.rb', line 183

def reject(&blk)
  self.class.new(super(&blk))
end

#replace(other_hash) ⇒ Object


277
278
279
280
281
# File 'lib/hashie/mash.rb', line 277

def replace(other_hash)
  (keys - other_hash.keys).each { |key| delete(key) }
  other_hash.each { |key, value| self[key] = value }
  self
end

#respond_to_missing?(method_name, *args) ⇒ Boolean

Returns:

  • (Boolean)

283
284
285
286
287
288
289
290
291
# File 'lib/hashie/mash.rb', line 283

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

#reverse_merge(other_hash) ⇒ Object

another ActiveSupport method, see issue #270


321
322
323
# File 'lib/hashie/mash.rb', line 321

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

#select(&blk) ⇒ Object

Returns a new instance of the class it was called on, containing elements for which the given block returns true.


189
190
191
# File 'lib/hashie/mash.rb', line 189

def select(&blk)
  self.class.new(super(&blk))
end

#shallow_merge(other_hash) ⇒ Object

Performs a shallow_update on a duplicate of the current mash


264
265
266
# File 'lib/hashie/mash.rb', line 264

def shallow_merge(other_hash)
  dup.shallow_update(other_hash)
end

#shallow_update(other_hash) ⇒ Object

Merges (non-recursively) the hash from the argument, changing the receiving hash


270
271
272
273
274
275
# File 'lib/hashie/mash.rb', line 270

def shallow_update(other_hash)
  other_hash.each_pair do |k, v|
    regular_writer(convert_key(k), convert_value(v, true))
  end
  self
end

#to_module(mash_method_name = :settings) ⇒ Object


81
82
83
84
85
86
87
88
# File 'lib/hashie/mash.rb', line 81

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

#transform_values(&blk) ⇒ Object


329
330
331
# File 'lib/hashie/mash.rb', line 329

def transform_values(&blk)
  self.class.new(super(&blk))
end

#underbang_reader(key) ⇒ Object

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.


153
154
155
156
157
158
159
160
# File 'lib/hashie/mash.rb', line 153

def underbang_reader(key)
  ck = convert_key(key)
  if key?(ck)
    regular_reader(ck)
  else
    self.class.new
  end
end

#values_at(*keys) ⇒ Object


170
171
172
# File 'lib/hashie/mash.rb', line 170

def values_at(*keys)
  super(*keys.map { |key| convert_key(key) })
end

#with_accessors!Object


90
91
92
# File 'lib/hashie/mash.rb', line 90

def with_accessors!
  extend Hashie::Extensions::Mash::DefineAccessors
end