There’s one Ruby gem that make it into practically every Ruby project I write, my friend Ara’s Map. Really, it’s a coincidence that I know Ara, this gem speeds up development and I would be using it anyway.

At it’s core, Map is a fancy hash:

1
2
3
4
5
6
7
8
m = Map.new(x:1, y:42, z: 'foo')
 => {"x"=>1, "y"=>42, "z"=>"foo"}
m[:x]
 => 1
m[:a] = 999
 => 999
m
 => {"x"=>1, "y"=>42, "z"=>"foo", "a"=>999}

In fact, it tells the world it’s a hash:

1
2
m.kind_of? Hash
 => true

And it works like a HashWithIndifferentAccess:

1
2
3
4
m[:y]
 => 42
m['y']
 => 42

The first thing that makes Map different then a Hash is that each key automatically becomes an accessor:

1
2
3
4
5
6
m.x
 => 1
m.z = 1066
 => 1066
m
 => {"x"=>1, "y"=>42, "z"=>1066, "a"=>999}

This behavior extends to nested data:

1
2
3
4
m = Map.new(x: 1, foo: {bar: 42 })
 => {"x"=>1, "foo"=>{"bar"=>42}}
m.foo.bar
 => 42

If you’re not sure a key is there you can use Map#get:

1
2
3
4
5
6
7
8
9
m.q
NoMethodError: undefined method `q' for {"x"=>1, "foo"=>{"bar"=>42}}
m.get(:q)
 => nil
# Nested as well
m.foo.baz
NoMethodError: undefined method `baz' for {"bar"=>42}
m.get(:foo,:baz)
 => nil

All together, this means I can take something hash-like and turn it into something that acts more like an object, without having to define a one-off class:

1
2
3
4
5
6
7
8
9
10
11
# Get some JSON from an API
response = http.request(request)
# decode it into a Hash and use that to build a map
thing = Map.for(MultiJson.decode(response.body))
if thing.status != 'OK
  ... # Whatever, whatever.
else
  name = "#{thing.first} #{thing.last}"
  city = thing.billing_address.city
  # and so on
end

When you need more that a one-off, Map is also a nice to inherent from, allowing you to create a hash-like object with a bit of behavior.

1
2
3
4
5
6
7
8
9
class Customer < Map
  def full_name
    "#{first_name} #{last_name}"
  end
end
c = Customer.new first_name: 'Alice', last_name: 'Smith'
 => {"first_name"=>"Alice", "last_name"=>"Smith"}
c.full_name
 => "Alice Smith"

I really like that I can get a class with what are, in effect, nested attributes without having to write a hairy initializer.

Map has some other nice features including a handy options parser and depth first iteration. Check out the README for more.

Whether you use it to create a one-off object or as the basis for a class, Map provides a clean way to treat hash like data in a object fashion.

Comments