Optional method parameters in Ruby

One of the things I love about Ruby is the flexibility it provides when it comes to method parameters. It’s dead simple to provide default values for one or more parameters, to handle variable length argument lists, and to pass blocks of code into a method. But perhaps my favorite is the ability to tack hash key/value pairs onto the end of a method call, and have those options combined into a Hash on the other side.

def some_method(required_1, required_2, options={})
  # Do something awesome!
end

some_method("foo", "bar")
some_method("foo", "bar", :option_1 => false, :option_2 => true)
some_method("foo", 
            "bar",
            :option_1 => false,
            :option_2 => true,
            :option_3 => "something",
            :option_4 => "something else")

This may not look like much. However, this feature alone is capable of producing some very readable code, and is used extensively in APIs throughout the Ruby ecosystem. Consider for a moment what these APIs would look like if Ruby did not have this capability, which isn’t hard to imagine for those of us with a background in a language like Java. You would either be forced to require that each parameter be specified:

# What is this code doing?  What do the nil values,
# or even the true and false values map to?
some_method("foo", "bar", false, nil, true, nil)

or accept a hash or a request object that contains all of the necessary parameters:

# This is much more readable, but requires that the
# options hash be created on its own line.
options = {:option_1 => true, :option_2 => false}
some_method("foo", "bar", options)

Providing optional parameters via hash key/value paris at the end of a method call produces code that is incredibly readable. You have the names of the attributes right next to their corresponding values! There is no ambiguity whatsoever as to which values match up with which parameters.

It is also very flexible. The order of the attributes in the hash does not matter, like it does for required attributes. And, it is very easy to add new options, or delete old ones.

This approach also makes it easy to specify default values for options that were not specified when calling the method:

def some_method(required_1, required_2, options={})
  defaults = {
    :option_1 => "option 1 default",
    :option_2 => "option 2 default",
    :option_3 => "option 3 default",
    :option_4 => "option 4 default"
  }
  options = defaults.merge(options)

  # Do something awesome!
end

There are however a few minor drawbacks to this approach. The first is documentation. Methods that take a hash of options as a parameter do not convey any information about the valid options in the method definition alone. And, it is possible that the method in question simply forwards the options to another method, sending you on a wild goose chase to determine the set of valid options the code supports.

# Looking for a list of valid option keys...no help here.
def some_method(required_1, required_2, options={})
  do_something_awesome_with_the_options(options)
end

This is why it is so important do document your public API if you are using this approach. Take a look at the ActiveRecord::Associations::ClassMethods documentation. This page documents, in a very clear and easy to read mannor, all of the supported options for each method.

It is also worth pointing out that while this approach is great for optional parameters, it is ill suited for required parameters. Required parameters should be specified outside of the options hash, making it clear that values for the required parameters must be provided. While it’s true that stuffing all of your parameters inside a hash means you’ll never have to look at another wrong number of arguments error again, it will make your code difficult to understand, and easy to misuse.

Be Sociable, Share!

    Leave a Reply

    Your email address will not be published. Required fields are marked *