How Ruby can do what you don’t expect
Ruby is a very clever language. It will take the code you’ve written and do it’s best to make sure it runs. Take for example the following method call:
method_one('Hello', param_two: ' world')
That looks simple enough as it takes two arguments, one positional and one keyword. Now, lets take a look at what the method is actually doing:
def method_one(param_one, param_two)
p param_one, param_two
end
Interesting. At first glance it might appear that this method would print Hello world
, but there is a subtle difference between how the method has been defined and how it is called. Let’s see what it actually outputs:
"Hello"
{:param_two=>" world"}
So what is going on here? Well, it’s because when we call method_one
we are incorrectly assuming that param_two
is a keyword argument and are providing the name alongside the value. This is where Ruby tries to be clever as it transforms that into the hash {:param_two=>" world"}
, which is not what we were expecting. There are a few ways you can deal with this, but the one this post is about is adding a double splat nil to the end of the list of arguments.
Double Splat Nil
**nil
might look a bit strange if you haven’t seen it before, so lets break it down. A double splat (**
) is used to indicate that you are expecting a quantity of keyword arguments to be provided when a method is invoked. For example:
def double_splat(**args)
p args
end
double_splat(arg_one: 123, arg_two: 'Four Five Six', arg_three: 789)
That prints out the following hash:
{:arg_one=>123, :arg_two=>"Four Five Six", :arg_three=>789}
This has loads of uses, but as we’ve already seen the conversion into a hash can have unintended consequences. That is where **nil
comes in. This tells Ruby that we are expected no keyword arguments when this method is called. Let’s use this to write a version of method_one
that doesn’t accept keywords:
def method_two(param_one, param_two, **nil)
p param_one, param_two
end
Now, what happens if we call it with the unexpected positional argument?
method_two('Hello', param_two: ' world')
=> no keywords accepted (ArgumentError)
Success! Now, we get an error raised saying something has been called with a keyword when it shouldn’t have been which means we don’t have to deal with unexpected hashes.