Method Signature You Can Sink Your Teeth Into

sinkteethHello everyone. I recently began to dig more deeply into Ruby 2.0. The most interesting feature to me is the addition of real keyword arguments. As I searched for information on how other languages implement and use keyword arguments, I realized how powerful this addition is. In short, it is very powerful.

Here, I want to discuss how Ruby allows parameters to be set, what patterns Rubyists follow when creating methods, and finally to discuss how fun and flexible Ruby 2.0’s keyword arguments are.

Some of this may be rudimentary for the advanced developer as this article is aimed at helping the budding Rubyist. However, I’ve found that giving names to some of the concepts we take for granted can be helpful even for the most hardened veterans.

In an Article[1] back in 2009, Alan Skorkin categorizes arguments as being either:

  • Required
  • Optional
  • Arguments with default values

I think this is a good way to think about how Ruby handles arguments. The interesting part here is that all (or at least most) of the argument handling techniques in this article will fall into these broad definitions. That being said, some, if not all, of the techniques can be used with one another.

For example, I could have a method with ordinal arguments, arguments with defaults, and an options hash argument. Ruby is a powerful language, and one of its strengths is flexibility. Make sure you explore and use that flexibility to its fullest extent.

Here is a list of the types of argument handling that we’ll be discussing, and will act as a table of contents for this post:

  • No Params
  • Ordinal Params
  • Params with default values
  • Hash Param
  • Arguments as Array
  • Keyword Arguments (Ruby 2.0)

A Brief Introduction

Before we begin, we need to establish the proper syntax of method creation. In Ruby, methods are declared by the def keyword. After the def keyword we have the method name, which must begin with a lowercase letter. Convention establishes the use of snake-case for method names that contain multiple words. After the method_name, we pass our params inside parentheses. The keyword end, like for class and module, signals the end of the method and must be preceded by a newline or semi-colon.

def example_method(args); end
#

#OR

#
def example_method(args)
end

(figure a.)

Now that you’ve gone through a quick re-cap, we can start talking about argument processing.

No Params

This will likely be the shortest section, because it is the least interesting path. You define a method with no arguments and call it with no arguments.

def example_method; puts "Hello, World."; end
#
example_method

#=> Hello, World

(figure b.)

And that’s it.

Ordinal Params

Ordinal params are so called because the arguments supplied to the method must be sent in the order established in the method’s signature. Technically, order matters for all argument types (except solo || no parameters). However, when I use the word ordinal here I’m referring to required parameters passed in order. For example:

def example_method(a,b)
  puts a,b
end
#

# We must pass the arguments in proper order, so that they
# reference what we expect.

#
example_method("This is A", "This is B")

#=> This is A

#=> This is B

(figure c.)

It is not a syntax error to pass the arguments out of order, but internal to the method they will reference the values as they are sent. Since both of these params are required params, failure to pass two arguments will raise an ArgumentError.

Params with Default Values.

Ruby allows us to pass default values as parameters to methods. To do so is quite simple: inside the method declaration use the equal sign (=) to set the default value. For example:

def example_method(a,b="default_b_value")
end

(figure d.)

Since b now has a default value, the method can be called example_method("a_value") or example_method("a_value", "b_value"). Here example_method is still an ordinal method because it depends on the order of the arguments. However, this technique gives us much more control over the interface to this method.

Hash Params

At some point, you’ll want to be able to handle an arbitrary number of arguments to a given method. Ruby provides a few ways to do this. The most common way to specify an arbitrary number of arguments is to use a hash as an argument. This creates something akin to keywords (which we’ll discuss in greater detail later). Truth be told, I’m not sure I can say this is the most common way to pass arguments, but it is certainly a favorite of many well respected Rubyists.

def example_method(options)
  puts options[:message]
end
example_method({message: "Hello, World!"})

#=> Hello, World!

(figure e.)

Ruby does some smart things here. If your options hash is the last or only param, you may leave off the braces. This is why this technique is sometimes referred to as “Pseudo-Keyword” arguments, because it resembles keywords, but is really just hiding a hash.

The examples below illustrate how you can use this to write some pretty looking methods.

# the above method could be called like so (in 1.9)

example_method(message: "Hello, World!")

(figure e.2.)

Or with options at the end of a list of params.

def example_method(ordinal_a, ordinal_b, options)
  p [ordinal_a, ordinal_b, options]
end
example_method("Yo", "Sup", message: "Hello, World!")

#=> ["Yo", "Sup", {:message=>"Hello, World!"}]

(figure e.3.)

Using this pattern allows for pretty interfaces. However, it is not without its downsides. Below are a few examples of abberant behavior (some of which are alleviated through use of Ruby 2.0’s keyword args)

If options are defaulted but the method is called passing an unexpected value (i.e. Nil), then that value will be used without complaint. In the example below we anticipate the options parameter to be a hash, unfortunately our user passed nil instead.[3] This results in an error.

def display_name(options={})
  p options[:first_name]
end
display_name(nil)

#=> NoMethodError: undefined method `[]' for nil:NilClass

#=> from (pry):2:in `display_name'

(figure e.4.)

Here we have a hash with a default value for first name. If we pass a hash, the default hash is ignored. Since the default hash is ignored the first_name key is never used.

def display_name(options={:first_name => "George"})
  p options[:first_name]
end
display_name(:last_name => "Jetson")

#=> nil

(figure e.5.)

Alright, so there are issues. That being said, there are patterns Ruby developers use to get around some of these shortcomings. Let’s look at another example.

class HelloWorld
  def initialize(options=nil)
    options ||= {}
    @message = opts.fetch(:message,"Hello, World!")
  end

  def to_s
    puts @message
  end
end

(figure e.6.)

A few things are happening in the code above. The initialize method sets up HelloWorld#new with a default parameter of {}. This is done so that we are able to instantiate this object without passing any arguments. The next interesting thing is when we set the @message var. Since we know that the argument to #new is defaulted to a hash, we can call Hash methods on it. Since we want HelloWorld to say something when asked, we can default it with Hash#fetch[2].

  • Using Hash#fetch allows us to default the value. Solves figure e.5
  • Assigning options to nil, then explicitly re-assigning it to our default (options ||= {}) ensures that, internally, our options hash will be a hash. Solves figure e.4

Arguments as an Array

What if I want to pass in an arbitrary number of arguments to my method. Well, this can be done with the *(splat) operator. The splat operator will take an arbitrary number of arguments, and collect them as an array. At first, this might seem a little hairy let’s take a look at an example to solidify the idea in our minds.

def example_method(*args)
  p args
end
-> example_method("argument_one", "argument_two")

#=> ["argument_one", "argument_two"]

(figure e.)

You can call this method with any number of arguments (including none). This allows us to do some fancy things when calling our methods. An important note is that since the splat operator will greedily snatch up all values at (position..position+n), it should only be use as the last or only parameter.

Keyword Arguments (Ruby 2.0)

This is where it gets good. You get to start using some of Ruby 2.0’s new features. To use keyword arguments, you must specify them in the signature using the new (1.9) style hash syntax, though you can call it with the classic style. Since we have a decent grounding in what keywords should look like (after looking at the “Pseudo-Keyword” style), we’ll jump straight into an example.

Look at the problem from figure e.5 again. In it you’ll want to set a default value for :first_name and still allow :last_name to be passed. With keyword arguments, you can simply define the method like so:

def display_name(first_name: "George", last_name: "Jetson")
  p "#{last_name}, #{first_name}"
end
display_name(last_name: "Jetson")

#=>

This method could be invoked in any of the following ways:

display_name

#=> "Jetson, George"

#
display_name(first_name: "Judy")

#=> "Jetson, Judy"

#
display_name(last_name: "Jefferson")

#=> "Jefferson, George"

#
display_name(last_name: "Flinstone", first_name: "Fred")

#=> "Flinstone, Fred"

In order to replicate this method pre-Ruby 2.0 you’d have to:

  • Default an options parameter
  • Create a hash filled with your defaults
  • Merge options parameter into default hash

Keyword arguments let you be incredibly concise when declaring a method. With Ruby 2.0, the method is created as above instead of the more verbose equivalent in 1.9.x:

def display_name(options=nil)
  options ||= {}
  {first_name: "George", last_name: "Jetson"}.merge(options)
  p "#{last_name}, #{first_name}"
end

If this were the only way to use keyword arguments, I’d be sold, but there is more! BLOCKS and PROCS! Say you want to make display_name a proc (for whatever reason). It’s as easy as:

display_name = Proc.new {|first_name: "George", last_name: "Jetson"| p "#{last_name}, #{first_name}" }
display_name.call

#=> "Jetson, George"
display_name.call(first_name: "Judy")

#=> "Jetson, Judy"

Neat huh. ^_^

What happens if you pass a keyword argument that isn’t specified in the method’s signature? Let’s take a look.

def example_method(first_keyword: "example")
end
example_method(last_keyword: "Last")

#=> ArgumentError: unknown keyword: last_keyword

#...

We get an ArgumentError. If you want access to those unassigned keyword arguments, you can use the ** (splat splat) operator which groups any remaining keyword arguments into a hash.[4]

def example_method(first_keyword: "example", **other_keywords)
  p other_keywords
end
example_method(last_keyword: "test")

#=> {:last_keyword=>"test"}

Conclusion

Glad you made it this far. I hope that you are now as excited as I am about Ruby 2.0. More importantly, I hope this has helped you to better understand how Ruby works. Ruby’s flexible nature allows for some wild variations on even the simplest of tasks. This is one of its strongest assets and one of the reasons I fell in love with the language in the first place.

Thanks for reading.

References

  1. 2010, Alan Skorkin, “Method Arguments In Ruby”
  2. Ruby-Doc, 1.9.3, “Hash#fetch””
  3. 2011, Jacob Swanner, “Optional Parameter Gotchas”
  4. 2012, Chris Zetter, “Keyword Arguments in Ruby 2.0″

Additional References

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.redbubble.com/people/paulramnora Paul Ramnora

    Very interesting article. Thanks Been a while since I looked at Ruby; it made for some good catching up. ;-)

  • http://rahulchandra.com Rahul Chandra

    Very helpful.. thanks,