Ruby 101: Hashes are Cool 17
Of all the data structures in Ruby, the Hash is probably the most famous, especially when it comes to Rails programming. Ruby syntax is well-suited to hash containers, making the hash easy to use for experienced Rubyists, and, well, not so easy to use for those new to Ruby. And those who are new to Rails are almost always new to Ruby as well. As a result, the onslaught of hashes in Rails is one of the first hurdles that every Rails developer must jump over if they want to get past the “newbie” stage and into the “developer” stage.
One of the first exposures a new Rails developer has to hashes is when a line like this is seen in a book (or in the Rails documention, in this case):
has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
When I first started seeing Rails code like this, I loved it. It was obvious what it meant – this object will be related to many people, represented by the class Person, but only for those who are not deleted, and we will see them sorted by name.
But what was the Ruby syntax doing? And how in the world could I have known to write code like this? I had no clue, and I certainly had no idea that a Hash was being used in that code. No way.
No Cheech and Chong jokes, I promise
Let’s step back for a second. Hopefully, this code is already understandable to you:
faves = {}
faves['color'] = 'purple'
faves['sport'] = 'hockey'
faves['framework'] = 'rails'
Here we create an empty hash, then populate it with key-value pairs one at a time.
The hash can actually be a heterogeneous structure if you want – you can use any type of key and any type of value in the same hash structure:
faves[:color] = 'purple'
faves[1] = Hockey.new
Here I’ve used a symbol and an integer as keys, and a string and an object as values.
In Rails hashes, you’ll find that symbols are the preferred key data type of choice instead of strings. Symbols are more eco-friendly than strings (less memory consumption and less garbage collection) when you need a human-readable identifier without any of the string manipulation features.
Hashes used as function arguments
The real interesting use of a hash is as a function argument. Let’s look at how I used to write a function in C# – I’m sure you can think of a similar equivalent in your previous language of choice.
public void SetFile(String filename, Int32 linesToRead)
This code uses positional parameters. The calling code needs to pass its arguments in the exact order as declared by the Initialize function, or it’s not going to work. This is one reason why statically typed languages were thought to be helpful, because if the calling code pased an integer followed by a string, the compiler would catch it (this was back in the stone age of programming, before unit testing was widely understood).
The problem came when I wanted the client to supply another argument. I had to either write another overloaded version of the Initialize function that took the extra parameter, or change the existing code and make existing clients break until they fixed their code as well:
public void SetFile(bool openNow, String filename, Int32 linesToRead)
Ruby also can take positional parameters, so you face the same dilemma in Ruby as well. Actually it’s a bit worse in Ruby, since method overloading isn’t supported. So if you need to change a method’s parameters, you’re stuck with either breaking existing client code, or adding another method to your class. Not good.
Enter stage right, the hash. Suppose I ask my client to pass a hash of values all at once, instead of passing them one at a time:
def set_file(options)
if options[:open_now]
# open the file
end
# etc.
end
# The client code...
options = {}
options[:open_now] = true
options[:filename] = 'jeff.txt'
options[:lines_to_read] = 50
set_file(options)
Here the client code passes in a hash, where the parameters are passed by providing key/value pairs. The client doesn’t have to worry about what order to pass arguments anymore. But it now has a different burden, which is that valid hash keys must be used in order for the function to operate correctly.
So where’s the win?
Ruby 934, C# 0
Ok, I haven’t really kept score for the past 10 months… but here again Ruby wins out. First, realize that my demonstration of populating a hash is just about the most cumbersome way possible in Ruby. Here’s a more typical way you create a hash with a few key/value pairs in it:
options = { :open_now => true, :filename => 'jeff.txt', :lines_to_read => 50 }
Beautiful. One line, easy to read. Now the client code looks like this:
# The client code...
options = { :open_now => true, :filename => 'jeff.txt', :lines_to_read => 50 }
set_file(options)
Good Ruby programmer that you are, you might be tempted to get this into one line and factor away the temporary variable:
# The client code...
set_file({ :open_now => true, :filename => 'jeff.txt', :lines_to_read => 50 })
Now, it’s a matter of taste, but in these situations I like to take advantage of the fact that Ruby usually doesn’t require parentheses:
# The client code...
set_file { :open_now => true, :filename => 'jeff.txt', :lines_to_read => 50 }
Not bad. Very readable, right? You can practically just read it left to right like English.
Actually, it’s terrible – those curly braces are driving me crazy! I want perfection! But we can’t get rid of them. Those braces are what tells Ruby that we’re creating a Hash.
Or can we? It turns out, if the last parameter you are supplying is a hash, then you can actually omit the curly braces! Ruby will automatically collect up your comma-delimited list of key/value pairs into a hash for you.
set_file :open_now => true, :filename => 'jeff.txt', :lines_to_read => 50
Ahhhh. Beatiful, easy to read code.
So let’s go back to the has_many example I started with.
has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
We can see now that we’re calling a method named has_many, and we’re passing it two arguments. The first is :people. The second is a hash, containing the keys :class_name, :conditions, and order. Internally, the Rails code looks for these keys in your hash, and acts according to the values you’ve stored there.
Even better, future version of Rails can add features to this method by simply documenting which new key/value pairs are supported. Existing client code won’t break and the Rails API wont’t have to get another new method.
Using hashes instead of positional parameters is one way the Rails codebase has avoided code bloat, in spite of the number of releases it’s enjoyed so far.
Ready to learn more about the synergies between Ruby and Rails? Sign up now for our two-day Ruby on Rails workshop Essential Rails before it’s too late.



I wonder if with the new Ruby 1.9 release, if Rails will support the updated Hash syntax.
For example:
<macro:code> hasmany :people, :classname => "Person", :conditions => "deleted = 0", :order => "name" </macro>
turns to
<macro:code> hasmany :people, classname: "Person", conditions: "deleted = 0", order: "name" </macro>
Thanks for the article. I never new that this was due to Ruby. I thought Rails was doing something funky to allow this to happen :)
thanks for taking the time to write this, I found it very helpful =)
There's a couple of errors in your examples,
:opennow = true, :filename => 'jeff.txt', :linesto_read = 50
should be
:opennow => true, :filename => 'jeff.txt', :linesto_read => 50
@barrym: Thanks for that; I've updated the article.
There's another error in your example:
setfile { :opennow => true, :filename => 'jeff.txt', :lines_to_read => 50 }
Is invalid because the Ruby interpreter sees the curly braces as the beginning of a block for the method. If you want to reverse the order and peel off the {} before the (), your example will be ok.
Great article but I still don't really get why passing a hash into every function is a good thing. You can do this in any language just like ruby.
C#
SetFile(Hashtable options) would do a very similar thing. Then you would never have to change your method signature either. But is this necessarily a good thing? I agree ruby's syntax is cleaner... but other than that it seems like a hack for not having method overloading.
You miss one of my favorites points, with only one more line you can have defaults values to your method. You just need merge the default hash parameter and the user hash parameter
[1] : http://theplana.wordpress.com/2007/08/28/default-values-and-hash-parameters
corbin: my thoughts exactly.
In C# you could also optionally use params and pass in any number of objects. Sure they're untyped, but that doesn't seem to be a concern here...
Note that the Ruby developers have stated that omission of parentheses on multi-parameter methods may be disallowed in future Ruby versions. Even though the hash might be considered a single-parameter method.
You just put another piece of the Ruby puzzle together for me. This page is priceless!
A word of caution, while hashes are very useful they are also easy to abuse. They can lead to very sloppy, very confusing code quickly if a developer starts using a hash for every function signature instead of actually thinking about intent. It becomes very easy to just shove one more argument inside a hash when you should probably refactor the function into several smaller ones or move some of the functionality into a new class. A hash is not a replacement for a well thought out function signature.
My usual rule of thumb is if you can no longer say that it's a collection of specific things then you need to refactor. A hash of table mappings? That's awesome. A hash of "arguments"? Stinking, bad smelling code.
Also, fewer lines of code does not equal better code nor does the line count determine code bloat. In fact one of my favorite quotes from "The Rails Way" blog is, "Now, this is a lot more lines of code than the original action. That’s not a bad thing! More lines of code are always better, if they make the code easier to read and maintain."
I think that is something that is important for people coming to Ruby, and programming in general, to understand that it's not about getting things down to as few lines as possible, it's about expressing intent as clearly as possible.
"it's about expressing intent as clearly as possible." <- pure gold.
Another option that isn't mentioned here is Ruby's form of method overloading: default args.
So to change:
setfile(filename, linesto_read)
to:
setfile(opennow, filename, lines_to_read)
You could actually define it as:
setfile(filename, linestoread, opennow=false)
By including multiple default args, you can effectively overload a method like other languages. It also allows for the refactoring you desire. I would write this code to treat open_now=false like the original method behaves.
Gosh darnit, this is about the best gosh darn explanation of hashes, symbols, passing hashes to methods, etc. that this ol’ boy has ever seen!
Thanks!
Because of the relation between the architectonic of pure reason and the things in themselves, it remains a mystery why our judgements prove the validity of the empirical objects in space and time; for these reasons, metaphysics, insomuch as philosophy relies on our faculties, can never furnish a true and demonstrated science, because, like the Ideal of practical reason, it is what first gives rise to analytic principles.
Since some of the Antinomies are problematic, Aristotle tells us that, in particular, metaphysics stands in need of space, and the objects in space and time have lying before them our ideas.