Method Signature You Can Sink Your Teeth Into

Share this article

sinkteeth
Hello 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

Frequently Asked Questions (FAQs) about Method Signatures in Ruby

What is the importance of method signatures in Ruby?

Method signatures in Ruby are crucial as they define the way a method can be called. They specify the number of parameters a method can take, their order, and their type. This helps in maintaining the consistency and predictability of the method’s behavior. It also aids in debugging, as it becomes easier to trace the source of an error when the method signatures are clearly defined.

How do method signatures differ in Ruby compared to other programming languages?

Unlike statically typed languages like Java or C++, Ruby is a dynamically typed language. This means that the type of a variable is checked during runtime, not at compile time. Therefore, Ruby method signatures do not explicitly declare the type of parameters. This provides more flexibility but can also lead to potential runtime errors if not handled properly.

Can you explain the concept of default values in Ruby method signatures?

In Ruby, you can assign default values to parameters in the method signature. This means that if no argument is provided for that parameter when calling the method, the default value will be used. This can be particularly useful when you want to make certain parameters optional.

What are keyword arguments in Ruby method signatures?

Keyword arguments are a feature in Ruby that allows you to pass arguments to a method in a key-value pair format. This can make the code more readable and self-explanatory, as it’s clear what each argument represents. It also provides flexibility in the order of arguments when calling the method.

How does method overloading work in Ruby?

Unlike some other languages, Ruby does not support method overloading, i.e., defining multiple methods with the same name but different parameters. However, Ruby’s flexibility with method signatures, such as optional parameters and keyword arguments, can be used to achieve similar results.

What is the role of the splat operator in Ruby method signatures?

The splat operator (*) in Ruby method signatures is used to handle methods which have a variable number of parameters. It collects all remaining parameters into an array, allowing you to handle each parameter within the method as needed.

How can I handle unexpected keyword arguments in Ruby?

Ruby 2.7 introduced a way to handle unexpected keyword arguments using the double splat (**) operator. This operator collects all unexpected keyword arguments into a hash, allowing you to handle them as needed within the method.

What is the significance of method signatures in Ruby’s metaprogramming?

Method signatures play a key role in Ruby’s metaprogramming, which involves writing code that generates or modifies other code. By dynamically defining method signatures, you can create methods on the fly, modify existing methods, or even create Domain Specific Languages (DSLs).

How can I document method signatures in Ruby?

You can document method signatures in Ruby using comments, but a more formal way is to use tools like RDoc or YARD. These tools generate documentation from your code, including method signatures, making it easier for others to understand your code.

How can I enforce type checking in Ruby method signatures?

While Ruby is a dynamically typed language, you can enforce type checking in method signatures using various techniques. One way is to use explicit type checking within the method. Another way is to use tools like Sorbet or RBS, which provide static type checking for Ruby.

Jonathan JacksonJonathan Jackson
View Author

Jonathan Jackson is a Ruby/Rails developer, working in Jacksonville Beach at Hashrocket. He often writes about Ruby development at his blog jonathan-jackson.net, which features articles designed to enlighten newbies and veterens alike. When he's not crunching code, he enjoys gaming, reading, and ultimate frisbee.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week