Cool Ruby Tricks
So it's been about a month and a half now since I started using ruby and rails professionally. While its been hard work getting up to speed on the domain of contract packaging, while getting up to speed on ruby, and learning vim, it is also just a joy after working with java and c# for so long.
One of the cool things about pair programming is you pick up peoples tricks and styles very quickly. Here is a quick list of some of my favorite ruby and rails tricks I have learned over the last month and a half.
1) Checking if a variable is one of many things
This has been a pet peeve of mine for a long time now.
if (variableName == "option1" ||
variableName == "option2" ||
variableName == "option3")
{
//do stuff
}
I always felt that was a failure in language design, not even remotely DRY. Awhile back I tried this in ruby, because I knew that the or operator was way more powerful then in c#, but it totally doesn't do what I was hoping
if variable_name == ("option1" || "option2" || "option3")
All that will do is the stuff in the parens will evaluate to "option1", since the boolean value of a string is true. What I was looking for was this
["option1", "option2", "option3"].include? variable_name
A bit nicer version of that would be to use the word array syntax
%w{ option1 option2 option3 }.include? variable_name
2) try, omfg I love you
So in c#, probably a fifth of your code is null checking in some form or another. Things like this are pretty normal
Class2Name obj = Class1Name.DoSomething();
if (obj
{
obj.DoSomethingElse();
}
In ruby (with active support), you would do something like this
ModName.do_something.try(:do_something_else)
What that will do, is if do_something returns nil, the call to try will return nil. If it isn't, the try basically works the same way as a send.
This is a prime example of the lisp philosophy, that if you make a language powerful enough, libraries can implement language features.
3) alias_method_chain, just a better way to do it
There is a method called alias_method that takes two symbols, the first being a new method name, the second being an existing method name. What this does is the equivilent of a unix hard link, it will have two method names pointing to the same method. That way you can re=define one of them, and the old one sticks around. We have a concept called 'alias method chaining', where what we want to do is add a pre or post hook to a method that doesn't have one. It usually looks something like this
alias_method :old_foo, :foo
def foo
puts 'this is redefined'
old_foo
end
This works great... until the next guy walks in and does the same thing in a different place. Your re=defd foo becomes old_foo, crushing the origional foo. His foo calls your foo, which calls itself, blows the stack, and you have a mysterious stack overflow exception that is exceptionally difficult to debug.
To get around this, rails has a method called alias_method_chain. What this does is takes two arguments, the first is the method, the second is a unique name for what you want to do with the method. alias_method_chain will first alias the method to method_name_without_second_arg. then it will alias the origional method to method_name_with_second_arg that you are responsible for defining. An example would look like this
def foo_with_hack
puts 'a bit of a safer way to handle things'
foo_without_hack
end
alias_method_chain :foo, :hack
This has a few benefits. First, since you aren't aliasing the old one to something generic, there is a much lower chance someone will blow it away with their aliasing. Secondly, since you are aliasing your method to something with a unique name, the order of the chaining call has a better chance of staying in tact. Thirdly, it is much more clear what your intention is.
4) Handling an arg that may or may not be an array
Array has a method called flatten. What it does is turns any array into a single dimensional array. Here is an example in irb
irb(main):001:0> [[1, 2, 3], ['a', 'b', 'c']].flatten
=> [1, 2, 3, "a", "b", "c"]
irb(main):002:0> [1, 2, 3].flatten
=> [1, 2, 3]
Pretty cool, right? With a bit of creativity, we can use it like this
def foo bar
[bar].flatten.each do |b|
#do stuff
end
end
So, if bar is an object, wrapping it in square brackets will make it an array, flatten won't do anything, and we iterate once. If bar is an array, wrapping it in square brackets will turn it into an array with a single element, which is the bar array. flatten effectively gets rid of that extra level, then we iterate over it.