Mash - Mocking Hash for total poser objects

Posted by on April 12th, 2008.

There are a number of times when I need something like an OpenStruct with a little more power. Often times this is for API-esque calls that don’t merit a full on ActiveResource. I wrote a small class for use with my ruby-github library and wanted to make it a separate gem because I think it’s pretty useful to have around.

Usage

Basically a Mash is a Hash that acts a little more like a full-fledged object when it comes to the keyed values. Using Ruby’s method punctuation idioms, you can easily create pseudo-objects that store information in a clean, easy way. At a basic level this just means writing and reading arbitrary attributes, like so:

author = Mash.new
author.name # => nil
author.name = "Michael Bleigh" 
author.name # => "Michael Bleigh" 
author.email = "michael@intridea.com" 
author.inspect # => <Mock name="Michael Bleigh" email="michael@intridea.com">

So far that’s pretty much how an OpenStruct behaves. And, like an OpenStruct, you can pass in a hash and it will convert it. Unlike an OpenStruct, however, Mash will recursively descend, converting Hashes into Mashes so you can assign multiple levels from a single source hash. Take this as an example:

hash = { :author => {:name => "Michael Bleigh", :email => "michael@intridea.com"},
       :gems => [{:name => "ruby-github", :id => 1}, {:name => "mash", :id => 2}]}

mash = Mash.new(hash)
mash.author.name # => "Michael Bleigh" 
mash.gems.first.name # => "ruby-github"

This can be really useful if you have just parsed out XML or JSON into a hash and just want to dump it into a richer format. It’s just that easy! You can use the ? operator at the end to check for whether or not an attribute has already been assigned:

mash = Mash.new
mash.name? # => false
mash.name = "Michael Bleigh" 
mash.name? # => true

A final, and a little more difficult to understand, method modifier is a bang (!) at the end of the method. This essentially forces the Mash to initialize that value as a Mash if it isn’t already initialized (it will return the existing value if one does exist). Using this method, you can set ‘deep’ values without the hassle of going through many lines of code. Example:

mash = Mash.new
mash.author!.name = "Michael Bleigh" 
mash.author.info!.url = "http://www.mbleigh.com/" 
mash.inspect # => <Mash author=<Mash name="Michael Bleigh" info=<Mash url="http://www.mbleigh.com/">>>
mash.author.info.url # => "http://www.mbleigh.com/"

One final useful way to use the Mash library is by extending it! Subclassing Mash can give you some nice easy ways to create simple record-like objects:

class Person < Mash
  def full_name
    "#{first_name}#{" " if first_name? && last_name?}#{last_name}" 
  end
end

bob = Person.new(:first_name => "Bob", :last_name => "Bobson")
bob.full_name # => "Michael Bleigh"

For advanced usage that I’m not quite ready to tackle in a blog post, you can override assignment methods (such as name= and this behavior will be picked up even when the Mash is being initialized by cloning a Hash.

Installation

It’s available as a gem on Rubyforge, so your easiest method will be:

sudo gem install mash

If you prefer to clone the GitHub source directly:

git clone git://github.com/mbleigh/mash.git

This is all very simple but also very powerful. I have a number of projects that will be getting some Mashes now that I’ve written the library, and maybe you’ll find a use for it as well.

Share:

9 Responses to “Mash - Mocking Hash for total poser objects”

  1. Mirko

    Thanks for sharing this! I've needed something like this several times in the past (usually when looking for a prettier way to deal with a parsed yaml file). I've used various hacky solutions, but this one seems very nice and convenient.
  2. malcontent

    Awesome. This is perfect for dealing with config files and lots of other uses. It's almost like javascript objects.
  3. Matt Todd

    Just a few things here: 1. Perhaps you should provide regular index accessors ([] and []=) that don't differentiate between symbol keys and string keys, and for unset values returns nil instead of a new Mash (currently Mash.new[:foo] returns a new Mash). 2. Merb already has a core extension called Mash which extends the behavior of Hash in order to provide string/symbol agnostic access(such as foo[:bar] and foo['bar'] being the same element). This could cause problems in some cases, but definitely will cause problems if Mash is used inside of Merb apps. 3. Perhaps you could rename Mash to MHash, or, alternatively, merge the two ideas and make Mash a dependency of Merb?
  4. Michael Bleigh

    @Matt Todd: Thanks for the suggestions! I had actually meant to remove the Mash-yielding [] method, so that will definitely be in the next release. I'm also investigating Merb integration. It would be great to say params.user_id in Merb apps, eh? Stay tuned for an update soon!
  5. Adam

    One thing that's always bugged me about Hashes is: test = {} test[:sdf] #=> nil test[:sdf][:test] #=> NoMethodError: undefined method `[]' for nil:NilClass Could Mashes help/take care of this?
  6. tim

    A suggestion. You should be able to do something like this. conf = Mash.new() conf.tasklist do task1="some task" task2="some other task" end This would be more convenient that conf.tasklist.task1! = 'lll' conf.takslist.taks2! = '222'
  7. Michael Bleigh

    @tim: great idea! I've created a ticket in the Mash Lighthouse project for this issue: http://mbleigh.lighthouseapp.com/projects/10112/tickets/2-use-blocks-to-quickly-build-out-sub-mashes
  8. Peterpunk

    I forked and renamed it to Mhash to make it compatible with merb. http://github.com/peterpunk/mhash/tree/master You can pull from if you are interested. great code! P
  9. Peterpunk

    I translated and reedited this article with the renamed Mash to Mhash. http://blogs.onrails.com.ar/2008/9/1/mash-it-up-mocking-hash P


Leave a Reply