We often write Ruby functions that take an
options Hash, remove some of its values, then pass the hash further down. Unfortunately we also often introduce a very difficult to catch bug. Here’s why you always want to
dup input options in Ruby that come in as a Hash.
Consider the following example from iex-ruby-client. We pluck
range out of
options to use in an HTTP request path, then pass the rest of the options as query string parameters. We want to
range key to avoid passing its value further down because the API does not expect it, and could have all kinds of unfortunate side-effects. We also don’t know all the possible arguments to the API, especially not the future ones, as we want to be forward-compatible.
Seems innocent enough. But consider this code.
You can see the problem: the second call to
historical_prices has incorrect options as
range was removed by the first call. Here’s a fix.
You can optimize a little to avoid
.dup in all cases, but I don’t think it’s worth it because it does not provide any meaningful performance improvement unless the function gets called a lot.
If you use Rails, you can use
except. I like how clean this is.
.clone are both shallow, but that doesn’t matter as long as you only use
.delete and remove values. If you were to modify the
options Hash in any other way, you may need to deep_dup it instead. So, don’t modify an object that your method doesn’t own and remember that Ruby passes objects such as
Array by reference, and not by value into functions.
All that said, if you know the finite list of parameters to your functions, use Ruby 2 Keyword Arguments instead.