# Methods

Functions in Ruby provide organized, reusable code to preform a set of actions. Functions simplify the coding process, prevent redundant logic, and make code easier to follow. This topic describes the declaration and utilization of functions, arguments, parameters, yield statements and scope in Ruby.

# Yielding to blocks

You can send a block to your method and it can call that block multiple times. This can be done by sending a proc/lambda or such, but is easier and faster with yield:

def simple(arg1,arg2)
  puts "First we are here:  #{arg1}"
  yield
  puts "Finally we are here:  #{arg2}"
  yield
end
simple('start','end') { puts "Now we are inside the yield" }

#> First we are here:  start
#> Now we are inside the yield
#> Finally we are here:  end
#> Now we are inside the yield

Note that the { puts ... } is not inside the parentheses, it implicitly comes after. This also means we can only have one yield block. We can pass arguments to the yield:

def simple(arg)
  puts "Before yield"
  yield(arg)
  puts "After yield"
end
simple('Dave') { |name| puts "My name is #{name}" }

#> Before yield
#> My name is Dave
#> After yield

With yield we can easily make iterators or any functions that work on other code:

def countdown(num)
  num.times do |i|
    yield(num-i)
  end
end

countdown(5) { |i| puts "Call number #{i}" }

#> Call number 5
#> Call number 4
#> Call number 3
#> Call number 2
#> Call number 1

In fact, it is with yield that things like foreach, each and times are generally implemented in classes.

If you want to find out if you have been given a block or not, use block_given?:

class Employees
  def names
    ret = []
    @employees.each do |emp|
      if block_given?
        yield(emp.name)
      else
        ret.push(emp.name) 
      end
    end

    ret
  end
end

This example assumes that the Employees class has an @employees list that can be iterated with each to get objects that have employee names using the name method. If we are given a block, then we'll yield the name to the block, otherwise we just push it to an array that we return.

# Default parameters

def make_animal_sound(sound = 'Cuack')
    puts sound
end

#

make_animal_sound('Mooo') # Mooo
make_animal_sound         # Cuack

It's possible to include defaults for multiple arguments:

def make_animal_sound(sound = 'Cuack', volume = 11)
    play_sound(sound, volume)
end

make_animal_sound('Mooo') # Spinal Tap cow

However, it's not possible to supply the second (opens new window) without also supplying the first. Instead of using positional parameters, try keyword parameters:

def make_animal_sound(sound: 'Cuack', volume: 11)
    play_sound(sound, volume)
end

make_animal_sound(volume: 1) # Duck whisper

Or a hash parameter that stores options:

def make_animal_sound(options = {})
    options[:sound]  ||= 'Cuak'
    options[:volume] ||= 11
    play_sound(sound, volume)
end

make_animal_sound(:sound => 'Mooo') 

Default parameter values can be set by any ruby expression. The expression will run in the context of the method, so you can even declare local variables here. Note, won't get through code review. Courtesy of caius for pointing this out (opens new window).

def make_animal_sound( sound = ( raise 'TUU-too-TUU-too...' ) ); p sound; end

make_animal_sound 'blaaaa' # => 'blaaaa'
make_animal_sound          # => TUU-too-TUU-too... (RuntimeError)

# Optional parameter(s) (splat operator)

def welcome_guests(*guests)
    guests.each { |guest| puts "Welcome #{guest}!" }
end

#

welcome_guests('Tom')    # Welcome Tom!
welcome_guests('Rob', 'Sally', 'Lucas') # Welcome Rob!
                                        # Welcome Sally!
                                        # Welcome Lucas!

Note that welcome_guests(['Rob', 'Sally', 'Lucas']) will output Welcome ["Rob", "Sally", "Lucas"]!
Instead, if you have a list, you can do welcome_guests(*['Rob', 'Sally', 'Lucas']) and that will work as welcome_guests('Rob', 'Sally', 'Lucas').

# Defining a method

Methods are defined with the def keyword, followed by the method name and an optional list of parameter names in parentheses. The Ruby code between def and end represents the body of the method.

def hello(name)
  "Hello, #{name}"
end

A method invocation specifies the method name, the object on which it is to be invoked (sometimes called the receiver), and zero or more argument values that are assigned to the named method parameters.

hello("World")
# => "Hello, World"

When the receiver is not explicit, it is self.

Parameter names can be used as variables within the method body, and the values of these named parameters come from the arguments to a method invocation.

hello("World")
# => "Hello, World"
hello("All")
# => "Hello, All"

# Single required parameter

def say_hello_to(name)
    puts "Hello #{name}"
end

#

say_hello_to('Charles')    # Hello Charles

# Required default optional parameter mix

def my_mix(name,valid=true, *opt)
    puts name
    puts valid
    puts opt
end

Call as follows:

my_mix('me')
# 'me'
# true
# []

my_mix('me', false)
# 'me'
# false
# []

my_mix('me', true, 5, 7) 
# 'me'
# true
# [5,7]

# Use a function as a block

Many functions in Ruby accept a block as an argument. E.g.:

[0, 1, 2].map {|i| i + 1}
 => [1, 2, 3]

If you already have a function that does what you want, you can turn it into a block using &method(:fn):

def inc(num)
   num + 1
end

[0, 1, 2].map &method(:inc)
 => [1, 2, 3]

# Tuple Arguments

A method can take an array parameter and destructure it immediately into named local variables. Found on Mathias Meyer's blog (opens new window).

def feed( amount, (animal, food) )

    p "#{amount} #{animal}s chew some #{food}"

end

feed 3, [ 'rabbit', 'grass' ] # => "3 rabbits chew some grass"

# Multiple required parameters

def greet(greeting, name)
    puts "#{greeting} #{name}"
end

#

greet('Hi', 'Sophie')    # Hi Sophie

# Method Definitions are Expressions

Defining a method in Ruby 2.x returns a symbol representing the name:

class Example
  puts def hello
  end
end

#=> :hello

This allows for interesting metaprogramming techniques. For instance, methods can be wrapped by other methods:

class Class
  def logged(name)
    original_method = instance_method(name)
    define_method(name) do |*args|
      puts "Calling #{name} with #{args.inspect}."
      original_method.bind(self).call(*args)
      puts "Completed #{name}."
    end
  end
end

class Meal
  def initialize
    @food = []
  end
  
  logged def add(item)
    @food << item
  end
end

meal = Meal.new
meal.add "Coffee"
# Calling add with ["Coffee"].
# Completed add.

# Capturing undeclared keyword arguments (double splat)

The ** operator works similarly to the * operator but it applies to keyword parameters.

def options(required_key:, optional_key: nil, **other_options)
  other_options
end

options(required_key: 'Done!', foo: 'Foo!', bar: 'Bar!')
#> { :foo => "Foo!", :bar => "Bar!" }

In the above example, if the **other_options is not used, an ArgumentError: unknown keyword: foo, bar error would be raised.

def without_double_splat(required_key:, optional_key: nil)
  # do nothing
end

without_double_splat(required_key: 'Done!', foo: 'Foo!', bar: 'Bar!')
#> ArgumentError: unknown keywords: foo, bar

This is handy when you have a hash of options that you want to pass to a method and you do not want to filter the keys.

def options(required_key:, optional_key: nil, **other_options)
  other_options
end

my_hash = { required_key: true, foo: 'Foo!', bar: 'Bar!' }

options(my_hash)
#> { :foo => "Foo!", :bar => "Bar!" }

It is also possible to unpack a hash using the ** operator. This allows you to supply keyword directly to a method in addition to values from other hashes:

my_hash = { foo: 'Foo!', bar: 'Bar!' }

options(required_key: true, **my_hash)
#> { :foo => "Foo!", :bar => "Bar!" }

# Remarks

A method is a named block of code, associated with one or more objects and generally identified by a list of parameters in addition to the name.

def hello(name)
  "Hello, #{name}"
end

A method invocation specifies the method name, the object on which it is to be invoked (sometimes called the receiver), and zero or more argument values that are assigned to the named method parameters. The value of the last expression evaluated in the method becomes the value of the method invocation expression.

hello("World")
# => "Hello, World"

When the receiver is not explicit, it is self.

self
# => main

self.hello("World")
# => "Hello, World"

As explained in the Ruby Programming Language book, many languages distinguish between functions, which have no associated object, and methods, which are invoked on a receiver object. Because Ruby is a purely object-oriented language, all methods are true methods and are associated with at least one object.

# Overview of Method Parameters

Type Method Signature Call Example Assignments
Required def fn(a,b,c) fn(2,3,5) a=2, b=3, c=5
Variadic def fn(*rest) fn(2,3,5) rest=[2, 3, 5]
Default def fn(a=0,b=1) fn(2,3) a=2, b=3
Keyword def fn(a:0,b:1) fn(a:2,b:3) a=2, b=3

These argument types can be combined in virtually any way you can imagine to create variadic functions. The minimum number of arguments to the function will equal the amount of required arguments in the signature. Extra arguments will be assigned to default parameters first, then to the *rest parameter.

Type Method Signature Call Example Assignments
R,D,V,R def fn(a,b=1,*mid,z) fn(2,97) a=2, b=1, mid=[], z=97
fn(2,3,97) a=2, b=3, mid=[], z=97
fn(2,3,5,97) a=2, b=3, mid=[5], z=97
fn(2,3,5,7,97) a=2, b=3, mid=[5,7], z=97
R,K,K def fn(a,g:6,h:7) fn(2) a=2, g=6, h=7
fn(2,h:19) a=2, g=6, h=19
fn(2,g:17,h:19) a=2, g=17, h=19
VK (opens new window) def fn(**ks) fn(a:2,g:17,h:19) ks={a:2, g:17, h:19}
fn(four:4,five:5) ks={four:4, five:5}