Last updated on June 30th, 2024 at 08:12 pm
Table of Contents
- Methods
- Method Visibility
- Class Methods
- Methods ending in ?, ! and =
- Calling a Method
- Deleting Methods
- Aliasing Methods
- Static Methods
- Exiting a Method
Methods
Basic Method Definition
A method is created using the def
statement and terminated using the end
statement. Between the def
and the end
statements, the world is your oyster.
def method_name
xx = "assigned value"
end
Method Naming
Methods should be named in snake_case. Refer to the Ruby Style Guide for more information.
Passing Arguments
As with just about any other programming language, you will want to be able to pass your methods arguments that they can operate on. Normally, arguments are passed by position. Although the parenthesis are not required, it is considered desirable styling to include them.
def my_method(height, weight)
# Brilliant ideas are realized...
end
This operates very much as you would expect. When you invoke the method, you would place the arguments in the sequence used when the method was defined, separating the values by a comma.
Calling the method above using the statement my_method 12, 14
, the value 12 is assigned to the variable height and the value 14 is assigned to the variable weight. You may notice that the example I provided didn’t surround the arguments with parenthesis. This is because parenthesis are optional when defining and invoking methods. However – be advised that there are some situations where Ruby can get confused if you don’t wrap your arguments in parenthesis, for example when using the ternary operator. As well, parenthesis are also required if you want to chain methods.
Because Ruby is not a strongly typed language, there are no restrictions or controls on what types of values you can pass. This means that if you make a mistake and pass the wrong object type, i.e. a string where an integer is expected, you won’t find out until you try to use the value when the method is invoked during runtime.
Default Values for Arguments
Ruby gives you the option of providing default values on arguments, but with some restrictions. In a nutshell, if you provide default values, you have to put these arguments at the end of the of the def
statement, as with the following example.
def my_method(height, weight = 12, density = nil)
# Do your magic....
end
You cannot, however, have something like the following:
def my_method(height=1, weight, density = nil)
# Interpreter will complain about this....
end
Thinking about this, it is pretty clear that when the method is invoked, the interpreter would never know how to assign arguments that are passed. Additionally, once you invoke the method and start taking advantage of the default argument values, you cannot selectively determine which arguments get defaults and which don’t. That is, once a default argument is used, all remaining arguments need to use defaults as well.
Let’s have a look at what this means using the first example of my_method
. I can invoke my_method with the statement my_method(1,2 3)
or my_method(1, 2)
, but I cannot use something like my_method(1, , 3)
.
Named Arguments
Ruby also allows you to name your arguments and then pass your arguments using their names.
def my_method(height, weight:, density:)
# Do your magic...
end
You will notice that the first argument wasn’t named, which means that naming arguments is similar to giving arguments defaults. Not all arguments have to be named, but once you start, all remaining arguments have to be named.
You can also give named arguments default values.
def my_method(height, weight:, density:, name: "Robert")
# Do your magic....
end
Passing a Variable Number of Arguments
Ruby also has the interesting ability to pass a method a variable number of arguments. By preceding the last argument with an asterisk, those arguments will be dealt with as an Array.
def my_method(height, weight, *everything_else)
# height and weight are passed as objects.
# the parameter everything_else is treated as an array
end
Although Ruby will allow you to define an argument with a default value, the default can only be taken if that given argument and all following it are missing.
That is, you cannot do something like my_method(1,2, ,4, 5,6,7,8,9)
to try and use the default value for the third argument and then pass values for everything_else
.
Passing Methods as Arguments
Ruby allows you to pass a method as an argument. This is as simple as passing the name of the method in the argument list and executing it in the called method.
# Define a class with some methods
class MathOperations
def add(a, b)
a + b
end
def subtract(a, b)
a - b
end
end
# Define a method that accepts another method as an argument
def perform_operation(a, b, operation_method)
operation_method.call(a, b)
end
# Create an instance of the class
math = MathOperations.new
# Get method objects for the add and subtract methods
add_method = math.method(:add)
subtract_method = math.method(:subtract)
# Perform operations by passing the method objects
result_add = perform_operation(5, 3, add_method)
result_subtract = perform_operation(5, 3, subtract_method)
puts "Addition Result: #{result_add}" # Output: Addition Result: 8
puts "Subtraction Result: #{result_subtract}" # Output: Subtraction Result: 2
or, alternatively
def called_method
puts "I am being called"
end
def calling_method(passed_method)
method(passed_method).call
end
calling_method(:called_method) # Output: I am being called
Passing Blocks as Arguments
Additionally, you are able to pass a ruby code block as the last argument. The block can either be declared explicitly with def perform_operation(a, b, &block)
or implicitly with def perform_operation(a, b)
. If a block is explicitly passed, it is converted into a Proc, which can then be called by name. Obviously, when the block is called, it must expect the same number of parameters as you pass to it on the call.
# Define a method that accepts a block and yields to it
def perform_operation(a, b) # alternatively => perform_operation(a, b, &block)
if block_given?
yield(a, b) # alternatively => block.call(a, b)
else
puts "No block given!"
end
end
# Pass a block to perform addition
result_add = perform_operation(5, 3) do |x, y|
x + y
end
# Pass a block to perform subtraction
result_subtract = perform_operation(5, 3) do |x, y|
x - y
end
# Output the results
puts "Addition Result: #{result_add}" # Output: Addition Result: 8
puts "Subtraction Result: #{result_subtract}" # Output: Subtraction Result: 2
# Pass a block to perform multiplication
result_multiply = perform_operation(5, 3) do |x, y|
x * y
end
# Output the result
puts "Multiplication Result: #{result_multiply}" # Output: Multiplication Result: 15
# Call the method without passing a block
perform_operation(5, 3) # Output: No block given!
For a thorough discussion of methods and arguments, I suggest you invest in Jeremy Evans’ book, Polished Ruby Programming and have a look at Chapter 4, “Methods and Their Arguments”.
Returning Values from Methods
Although a method can be written that performs an action that doesn’t return any value, that is somewhat rare. Normally, we want a method to return a value. In Ruby, you have a few options. First, you can use the return
statement anywhere in the method’s code block. This will halt the execution of the method and, optionally, return values.
return my_value
Additionally, Ruby automatically considers the last statement of a method as a return value. You may add the return
statement if you wish, but it is not required.
def my_method
"This is the return value"
end
Endless Methods
Ruby 3.0 introduced the concept of Endless Methods. These are basically one-line methods.
# Define an endless method for adding two numbers
def add(a, b) = a + b
# Define an endless method for subtracting two numbers
def subtract(a, b) = a - b
# Define an endless method for multiplying two numbers
def multiply(a, b) = a * b
# Define an endless method for dividing two numbers
def divide(a, b) = a / b.to_f
# Call the endless methods and print the results
puts add(5, 3) # Output: 8
puts subtract(5, 3) # Output: 2
puts multiply(5, 3) # Output: 15
puts divide(5, 3) # Output: 1.6666666666666667
Method Visibility
There are three levels of visibility within Ruby – public, protected and private. Whether a method should be private, public or protected depends more upon your personal opinion or company policy than anything else – see Noel Rappin’s blog post for an interesting perspective on when a method should be public. It is important to know that regardless of how you set the visibility on an object, this can be overridden by simply using the send method.
Public
Public visibility is the default value for all methods. If a method is public it can be accessed by any user of the object created from the class.
Protected
If a method is declared as protected, it can only be called from a subclass of the receiver or the receiver must be a subclass of the sender. Otherwise a NoMethodError will be raised.
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def compare_age(other_person)
if self.age > other_person.age
"#{self.name} is older than #{other_person.name}"
elsif self.age < other_person.age
"#{self.name} is younger than #{other_person.name}"
else
"#{self.name} and #{other_person.name} are the same age"
end
end
protected
def age
@age
end
end
alice = Person.new("Alice", 30)
bob = Person.new("Bob", 25)
puts alice.compare_age(bob) # "Alice is older than Bob"
class Student < Person
def compare_age_with_parent(other_person)
if self.age > other_person.age
"#{self.name} is older than #{other_person.name}"
elsif self.age < other_person.age
"#{self.name} is younger than #{other_person.name}"
else
"#{self.name} and #{other_person.name} are the same age"
end
end
end
charlie = Student.new("Charlie", 20)
dave = Person.new("Dave", 40)
puts charlie.compare_age_with_parent(dave) # "Charlie is younger than Dave"
begin
puts alice.age
rescue NoMethodError => e
puts e.message # "protected method `age' called for #<Person:...>"
end
Private
If a method is declared as private, it is only available from within the Class and cannot be used via the object created from the class.
class Person
attr_accessor :name, :age
def initialize(name, age)
@name = name
@age = age
end
def compare_age(other_person)
if older_than?(other_person)
"#{self.name} is older than #{other_person.name}"
elsif younger_than?(other_person)
"#{self.name} is younger than #{other_person.name}"
else
"#{self.name} and #{other_person.name} are the same age"
end
end
private
def older_than?(other_person)
age > other_person.age
end
def younger_than?(other_person)
age < other_person.age
end
end
alice = Person.new("Alice", 30)
bob = Person.new("Bob", 25)
puts alice.compare_age(bob) # "Alice is older than Bob"
begin
puts alice.older_than?(bob)
rescue NoMethodError => e
puts e.message # "private method `older_than?' called for #<Person:...>"
end
Declaring Visibility
There are three ways to declare method visibility, normal, postfactum and inline.
# Normal definition
private
def method1
end
def method2
end
def method3
# Postfactum definition
def method4
end
def method5
end
def method6
end
private :method4, :method5, :method6
# Inline definition
private def method7
end
private def method8
end
private def method9
end
Class Methods
A class without methods is pretty much useless. Methods are code within a class that performs a function related to the class. NOTE – You can define methods in a class that have absolutely nothing to do with the class, but that would normally be considered a code smell. Defining methods within a class is accomplished using the def
statement. The def
is followed by the name of the method, typically in snake_case and terminated with an end
statement. Method names should always begin with a lower case letter and use an underscore (_) to separate words. The Ruby style guide suggests two space indentation.
class ExampleClass
def my_first_method
end
end
Methods can take any number of parameters. Parameters can take on default values by assigning them on the def
statement. Parameters with default values must always be at the end of the parameter list. You can also call the method using the parameters in any order, if you use their names.
class ExampleClass
def show_this_and_that(start, this: "Something", that: "Something else")
puts "start is : #{start}, this is : #{this}, that is: #{that}"
end
end
my_object = ExampleClass.new
my_object.show_this_and_that("aaa")
my_object.show_this_and_that("bbb", this: "something new")
my_object.show_this_and_that("ccc", that: "something completely different")
Methods ending in ?, ! and =
The Ruby style guide suggests that methods which don’t modify anything and return a true / false value end with a question mark (?).
def is_something_true?
# Your code
end
If a method will modify the object that is calling it, it should end in an exclamation point (!).
def modify_the_object!
# Your code
end
If the method is a setter method for a class variable (which we will cover later in this guide), then it must end with an equals sign (=).
def variable_setter=(var)
# Your code
end
Calling a Method
There are a few different ways to call methods in Ruby. The first, and most common mechanism is to use the method name, followed by a left parenthesis “(“, followed by the arguments, and closing with a right parenthesis “)”.
my_method( param1, param2, param3)
The wrapping parenthesis are actually optional, but desirable. The method above can be rewritten to:
my_method param1, param2, param3
Ruby is even intelligent enough to handle conditional method calls, such as:
my_method param1, param2, param3 if something_is_true
This is not advisable and things break down if you want to chain methods. In this case, the parenthesis are necessary.
Using send
Another mechanism to make method calls is the send
statement. send
is a method that every object responds to and it takes an argument which is the name of the method to be called, followed by the arguments to be passed to the method. The name of the method can be a symbol or a string.
One very important thing to note with send is that it bypasses privacy restrictions on methods. That is, I can issue a send
against a method that is marked as private and it will work fine.
my_object.send("my_method", param1, param2, param3)
Using call
Ruby also allows you to define a method and assign it to a variable, which can later be executed using the call
method. This would most commonly be used for Procs and Lambdas.
my_proc = Proc.new {|name| "Hello, #{name}"}
puts my_proc.call("Herbert") # => "Hello, Herbert"
Differences between send and call
Context:
send
is used for calling methods on objects dynamically.call
is used to executeProc
orlambda
objects.
Target:
send
targets methods defined on an object.call
targetsProc
orlambda
objects.
Method Visibility:
send
can invoke private and protected methods.call
has no concept of method visibility since it’s used withProc
orlambda
.
Syntax:
send
:object.send(:method_name, *arguments)
call
:proc_or_lambda.call(*arguments)
Which methods will an object respond to?
To see which methods an object responds to, simply use the object’s methods
method.
To see if a Class responds to a given method, use the respond_to?
method.
Deleting Methods
You also have the ability to delete methods using the undef_method
statement.
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
"Hello, my name is #{@name}."
end
def age
@age
end
end
# Creating an instance of Person
alice = Person.new("Alice", 30)
# Calling the greet method
puts alice.greet # "Hello, my name is Alice."
# Undefining the greet method
class Person
undef_method :greet
end
# Trying to call the greet method after it has been undefined
begin
puts alice.greet
rescue NoMethodError => e
puts e.message # "undefined method `greet' for #<Person:...>"
end
# The age method is still available
puts alice.age # 30
Aliasing Methods
Finally, you can alias methods using the alias statement. There are a few good reasons for aliases.
Backward Compatibility: When you rename a method, existing code that relies on the old method name might break. Aliasing allows you to provide the new method name while still supporting the old one.
Enhancing or Modifying Behavior: You might want to add additional behavior to an existing method without altering its original implementation. Aliasing the method allows you to extend its functionality while preserving the original behavior.
class Person
def initialize(name, age)
@name = name
@age = age
end
def greet
"Hello, my name is #{@name}."
end
alias :original_greet :greet
def greet
original_message = original_greet
"#{original_message} Nice to meet you!"
end
end
alice = Person.new("Alice", 30)
puts alice.greet # "Hello, my name is Alice. Nice to meet you!"
Improving Method Names: Sometimes, the original method name might not be intuitive. Aliasing allows you to provide a more descriptive or meaningful name without changing the original method name.
Static Methods
Occasionally you will have methods that don’t access any class variables, which can then be defined as static methods. This is accomplished in Ruby by prefixing the method name with self.
class ExampleClass
def self.static_method
end
end
Although the static method cannot access any instance variables because it doesn’t have a corresponding object, it can access constants and class variables.
Exiting a Method
Like almost any other programming language, Ruby executes statements in the sequence of their appearance in your code. Also, as with any other language, there are statements that can be used for flow control and forcing a method exit. A Ruby method will execute until its last statement and can, but doesn’t need to, explicitly end the method with the return
statement. The return
statement can be used by itself or with values. If the return
statement is issued without any value, the method will return the value of nil.
You can interrupt program execution anywhere and force method termination by placing the return statement at the appropriate point.
def my_method
expressions ….
if return_early
return
end
more expressions …
end
You can also establish a block of code that will always be executed when the method completes, by using the ensure
statement.
def my_method
puts "one"
return
ensure
puts "two"
end