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.

[View source]

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

[View source]

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)
[View source]

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.

[View source]

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

[View source]

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

def assign_property(name, value)
  self[name] = value
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)
[View source]

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.

[View source]

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.

[View source]

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.

[View source]

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

[View source]

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

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

#dupObject

Duplicates the current mash as a new mash.

[View source]

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)
[View source]

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

def extractable_options?
  true
end

#fetch(key, *args) ⇒ Object

[View source]

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.

[View source]

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.

[View source]

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)
[View source]

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)
[View source]

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

[View source]

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

alias regular_dup dup

#regular_key?Object

[View source]

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.

[View source]

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

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

#replace(other_hash) ⇒ Object

[View source]

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)
[View source]

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

[View source]

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.

[View source]

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

[View source]

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

[View source]

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

[View source]

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

#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.

[View source]

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

[View source]

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

[View source]

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

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