DEV Community

Cover image for The Simplest Guide on Ruby Methods Arguments
Pimp My Ruby
Pimp My Ruby

Posted on

The Simplest Guide on Ruby Methods Arguments

Creating functions is fundamental in our developer profession. With Ruby, we are fortunate to have a language that is very flexible in composing the definition of these methods.

In this article, we will explore all the different types of arguments a function can receive.

Table of Contents

Positional Arguments

Positional Arguments are the most commonly used and are defined in the order they appear. They are directly related to the order of method parameters. Let's look at an example:

def add(a, b, c)
  puts a + b + c
end

add(1, 2, 3) # => 6
Enter fullscreen mode Exit fullscreen mode

You can also assign them a default value with this syntax:

def add(a, b = 2, c = 3)
  puts a + b + c
end

add(1) # => 6
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: It is impossible to wrap a Positional Argument around two Positional Arguments with a default value.

def add(a = 1, b, c = 3) # => SyntaxError
  puts a + b + c
end
Enter fullscreen mode Exit fullscreen mode

Keyword Arguments

Keyword arguments allow you to explicitly specify which variable each value is intended for. This makes the method call more readable and flexible. Here's how it works:

def greet(name:, age:)
  puts "Hello #{name}! You are #{age} years old."
end

greet(name: "Alice", age: 30) # => Hello Alice! You are 30 years old.
Enter fullscreen mode Exit fullscreen mode

You can mix Positional and Keyword arguments:

def greet(name, age:)
  puts "Hello #{name}! You are #{age} years old."
end

greet("Alice", age: 30) # => Hello Alice! You are 30 years old.
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: It is impossible to place a Positional argument after a Keyword Argument.

def greet(name:, age) # => SyntaxError
  puts "Hello #{name}! You are #{age} years old."
end
Enter fullscreen mode Exit fullscreen mode

The great advantage of using Keywords is that it does not allocate memory for the Hash sent to it. I'll explain this with an example:

def greet(options = {}) # will allocate memory if no args
  puts "Hello #{options.fetch(:name)}! You are #{options.fetch(:age)} years old."
end

greet({name: "Alicia", age: 33}) # you just created a hash!

def greet(name:, age:) # won't allocate memory if no args
  puts "Hello #{name}! You are #{age} years old."
end

greet(name: "Alicia", age: 33) # no memory allocation
Enter fullscreen mode Exit fullscreen mode

Positional and Keywords are the two most commonly used types of arguments. So far, we haven't seen the true power of Ruby. Before we go further, let's take a moment to recall some rules regarding the use of Positional and Keywords arguments.

When to use Positional / Keyword?

For this, I have a simple rule:

  1. If my method is public → Keyword Argument Examples of methods called outside the context of my class, the initialize method, the call method for Service Objects.
  2. If my method is private → Positional Argument Methods internal to a class are there to unload actions performed in public methods. Since we have total control over callers and callees, we can afford to use only Positional Arguments.

Named Rest Arguments

The Splat Operator (*) and the Double Splat Operator (**) help us manage a variable number of arguments to receive in our function.

Let's look at an example:

def display_info(*args, **kwargs)
  puts "Positional Arguments: #{args}"
  puts "Keyword Arguments: #{kwargs}"
end

display_info(1, 2, {fake: :love}, name: "Bob", age: 25)
# => Positional Arguments: [1, 2, {:fake=>:love}]
# => Keyword Arguments: {:name=>"Bob", :age=>25}
Enter fullscreen mode Exit fullscreen mode
  • *args means: Take all Positional Arguments and put them into the args variable as a list.
  • **kwargs means: Take all Keyword Arguments and put them into the kwargs variable as a Hash.

args and kwargs are named as such by convention; you are free to rename them as you see fit.


Anonymous Rest Arguments

In the previous case, we saw what happens when we name our use of Splat Operators in our method definition.

In reality, there is a second use for splat operators when we don't assign them to anything:

def process_data(*, processing_method: "default")
  puts "Processing with the method: #{processing_method}"
end

process_data("Hello", "World", processing_method: "custom")
# => Processing with the method: custom
Enter fullscreen mode Exit fullscreen mode

In the example above, you saw that nothing was done with the sent Positional Arguments.

The behavior is the same with the Double Splat Operator:

def process_data(card, method, **)
  puts "#{card} was debited with #{method} method"
end

process_data("Hello", "World", processing_method: "custom")
# => Hello was debited with World method
Enter fullscreen mode Exit fullscreen mode

But then you must be wondering, what's the use?

  1. Delegate to underlying functions by ignoring the content of the arguments.
  2. Ignore "extra" arguments and avoid raising an exception.

Let's look at an example that explains these two use cases:

def greet(**)
  greet_kindly(**)
  greet_wickedly(**)
end

def greet_kindly(name:, **)
  puts "Hi #{name}, nice to see you!"
end

def greet_wickedly(name:, surname:, **)
  puts "Hi #{name}, what a stupid surname #{surname}..."
end

greet(name: "Hugo", surname: "The V")
# => Hi Hugo, nice to see you!
# => Hi Hugo, what a stupid surname The V...
Enter fullscreen mode Exit fullscreen mode

The greet_kindly method received name and surname but extracted name and sent surname into the void thanks to the ** notation. Without **, an ArgumentError would be raised.

There is another method for delegating arguments; it's called Argument Forwarding, with the ... notation:

def greet(...)
  greet_kindly(...)
  greet_wickedly(..)
end
[...]
Enter fullscreen mode Exit fullscreen mode

The only difference is that * and ** only look at one type of argument (Positional / Keyword), while ... considers all types of arguments.


Nil Splat Operator Arguments

If we combine the Double Splat Operator and nil, what do you think happens?

def greet(name, **nil)
  puts "Hello #{name}!"
end

greet("Charlie", other: "argument?") # => ???
Enter fullscreen mode Exit fullscreen mode

I'll give you the answer: an exception no keywords accepted (ArgumentError) is raised.

How is this useful?

Well, you will be surprised to learn that Keyword Arguments have a very interesting property. If the method expects a Positional Argument, and a Keyword Argument is sent, the Keyword Argument is cast into a Hash and promoted to the rank of Position Argument.

def greet(name)
  puts "Hello #{name}!"
end

greet(name: "Hugo") # => Hello {:name=>"Hugo"}
Enter fullscreen mode Exit fullscreen mode

If you want to prevent this behavior in your methods, you need to add **nil at the end of your parameter declaration!


Block Argument

This section could be an entire article. Managing Blocks is what makes Ruby so appreciated.

The promise of the Block Argument is to be able to delegate a part of the internal body of your method externally. This offers incredible flexibility. The best part? You can directly send information to the Block:

def do_action
  yield(1, 2)
end

do_action do |a, b|
  pp "What does #{a} + #{b} ? #{a+b}"
end

Enter fullscreen mode Exit fullscreen mode

In this example, I used my Block anonymously, but you can also name it as follows:

def do_action(&block)
  yield block
end

do_action do
  "Hello World!"
end
# => Hello World!

Enter fullscreen mode Exit fullscreen mode

These are very simple examples, but keep in mind that Block Arguments open up a huge range of possibilities in your applications.


TL;DR

Here's a concise summary of each type of argument covered today!

  • Positional Arguments (a, b, c)
  • Keyword Arguments (name:, age:)
  • Named Rest Arguments (*args, **kwargs)
  • Anonymous Rest Arguments (*, **)
  • Argument Forwarding (...)
  • nil kwargs (**nil)
  • Block Argument (&block)

That's it for today!

Feel free to experiment with these different types of arguments in your own Ruby projects and share your discoveries. By mastering these subtleties, you will be better equipped to design functions that are more robust and flexible, truly tailored to the needs of your applications.

Top comments (0)