Methods in Ruby

A method is a block of reusable that performs a certain set of instructions. The usage of methods reduces code redundancy and makes the code more readable.

Defining a Method

A function is defined using the keyword def

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

A method can be referenced by using its name, and if required we can pass the parameters required for the function as well.
 

Default parameters

When no arguments are explicitly passed into the function, default parameters are used to pass default parameters when the function is invoked.

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

Use function as a block

 

Many functions in ruby accept input as a block. We can also turn a function into a block 

. 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]

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').

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]

Single required parameter

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

say_hello_to('Charles')    # Hello Charles

Tuple Arguments

A method can take an array parameter and destructure it immediately into named local variables.

def feed( amount, (animal, food) )

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

end

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

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!" }

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.

Multiple required parameters

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

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