Ruby's Method#parameters
Ruby methods can define their parameters in a few different ways. Conveniently, due to some of Ruby’s metaprogramming features, we can actually look at the parameters of any method!
However, the documentation on Method#parameters
is missing a few cases. In this post we will look at all types of parameters, and what Method#parameters
returns for them.
Ruby’s Method Method
To start, we need to look at the Object#method
method defined in Ruby’s Object class. It returns a Method
object.
irb > method(:puts).class
=> Method
Method
objects are super neat, and I’ll most likely write a few future posts about them. Just for a brief teaser, here are all the public methods specific to a method object:
irb > pp method(:puts).public_methods - Object.public_methods
[:unbind,
:owner,
:super_method,
:<<,
:>>,
:to_proc,
:[],
:arity,
:call,
:curry,
:receiver,
:source_location,
:parameters,
:original_name]
Method#parameters
The method we need to focus on for now is the Method#parameters
method. This is, after all, the output we’re looking to understand more deeply. It’ll tell us which parameters a method takes, and the parameter names. We can first look at a method which takes no args:
irb > method(:nil?).parameters
=> []
Straightforward enough. nil?
has no parameters. We can confirm this:
irb > nil?("some argument")
....
ArgumentError (wrong number of arguments (given 1, expected 0))
What about a method which does have parameters?
irb > method(:puts).parameters
=> [[:rest]]
Hmm. We know we can supply any number of arguments to puts
, so why only one parameter? And why is it in a nested array?
irb > puts "any", :number, "of", :args
any
number
of
args
=> nil
It’s because puts
takes splat args. We’ll go over splat args in more depth further in this post. But, for now, we can create our own example method to confirm:
def splat_args(*splat)
splat
end
=> :splat_args
irb > splat_args("any", :number, "of", :args)
=> ["any", :number, "of", :args]
irb > method(:splat_args).parameters
=> [[:rest, :splat]]
Hmmmm. This also has :rest
in the result, but it’s not exactly the same as puts
. We’re getting one parameter, but it has the name :splat
. What if we left the parameter unnamed?
irb > def unnamed_splat_args(*); end
=> :unnamed_splat_args
irb > method(:unnamed_splat_args).parameters
=> [[:rest]]
irb > method(:unnamed_splat_args).parameters == method(:puts).parameters
=> true
Aha. Exactly the same. So puts
has one, unnamed splat arg parameter, denoted in the returned array as :rest
.
All parameters
But, there are more parameters than just splat args. Let’s make a method which has every different type of parameter, and we can see what happens.
Before we do, it’s important to cover all of the parameters that Ruby methods can take. Parameters can either:
-
be keyworded
(keyworded:)
or positional(positional)
:
after the parameter name will determine if it is keyworded. If so, when calling the method, we must name the argument:def keyworded(arg:); arg; end irb > keyworded(arg: "argument") => "argument"
def positional(arg); arg; end irb > positional("argument") => "argument"
When calling methods with positional arguments, the ordering of the arguments matters. We must supply the arguments in the order they are named. When calling methods with keyword arguments, the order of calling does not matter.
-
have default values or no default values:
If an argument does not have a default value, it must be passed. If it does have a default value, it is optional If the argument is keyworded, the default value simply follows the colon:
(keyworded: "default")
If it’s not keyworded, the syntax is(not_keyworded = "default")
def default(arg = "default"); arg; end irb > default => "default" irb > default("some other value") => "some other value"
def no_default(arg); arg; end irb > no_default ArgumentError (wrong number of arguments (given 0, expected 1)) irb > no_default("some value") => "some value"
-
be splat args (unlimited number of arguments)
Ruby also allows for methods which can take a variable number of args, using the
*
operator. If these arguments are not keyworded, they will evaluate to an array:def splat_args(*splat); splat; end irb > splat_args("any", :number, "of", :args) => ["any", :number, "of", :args]
If they are keyworded, we use the double splat
**
operator, and they will evaluate to a hash:def double_splat_args(**double_splat); double_splat; end irb > double_splat_args(some: "keyword", args: nil) => {:some=>"keyword", :args=>nil}
Note that we cannot pass keyworded args to a method expecting a splat:
irb > double_splat_args("not keyworded") ArgumentError (wrong number of arguments (given 1, expected 0))
And passing keyworded args to a method with a splat parameter will result in a hash for that argument in the array of args:
irb > splat_args(keyworded: "args") => [{:keyworded=>"args"}]
-
be block args
The last type of argument we can pass is a block, denoted with an
&
:def block_args(&block); yield; end irb > block_args { "calling the block" } => "calling the block"
Back to Method#parameters
This has all really been buildup for Method#parameters
. How do all of these combinations of keyword / positional / optional / required / splat / double splat / block args actually look when we call Method#parameters
? To find out, let’s write a jumbo method, passing all types of arguments:
def jumbo_method(
required_positional,
optional_positional = nil,
*splat_args,
required_keyword:,
optional_keyword: nil,
**double_splat_args,
&block
)
<<-ARGS
required_positional: #{required_positional}
optional_positional: #{optional_positional}
splat_args: #{ssplat_args}
required_keyword: #{required_keyword}
optional_keyword: #{optional_keyword}
double_splat_args: #{double_splat_args}
block: #{yield}
ARGS
end
puts jumbo_method(
"required_positional",
"optional_positional",
"any", "number", "of", "positional", "args",
required_keyword: "required_keyword",
optional_keyword: "optional_keyword",
any: "number", of: "keyword", args: "!",
) { "block" }
=> required_positional: required_positional
optional_positional: optional_positional
splat_args: ["any", "number", "of", "positional", "args"]
required_keyword: required_keyword
optional_keyword: optional_keyword
double_splat_args: {:any=>"number", :of=>"keyword", :args=>"!"}
block: block
and calling .parameters
on this method we’ll get:
irb > method(:jumbo_method).parameters
[[:req, :required_positional],
[:opt, :optional_positional],
[:rest, :splat_args],
[:keyreq, :required_keyword],
[:key, :optional_keyword],
[:keyrest, :double_splat_args],
[:block, :block]]
This is the output we were looking for! The array of paramaters contains arrays of size two where the first element is the type of parameter, and the second is the name of the parameter. Since we named all of our parameters descriptively, we can use it to see exactly how Method#parameters
refers to each type. For example, optional positional parameters are :opt
while optional keyword parameters are :key
.