May 20, 2015
Table of contents:
So far in this Ruby series we have already touched upon using classes and methods a number of times, but we haven’t really looked at writing our own.
A class is like the blueprint of an object and describes it’s properties and behaviours.
If you are already familiar with object-oriented programming, using classes and methods will seem like second nature to you.
However, if you’re new to object-oriented programming, understanding the importances of classes and methods can open the door to a whole new world of understanding.
Before we get into looking at classes, today we will be looking at writing Ruby methods.
def hello_world
puts 'Hello World'
end
The code above is a Ruby method that will print the string "Hello World"
to the screen. A Method is defined used the def..end
syntax.
In other programming languages, a method usually refers to the function of an object. However because everything is an object in Ruby, what normally looks like a function, is actually also a method.
The first thing to understand about Ruby methods is that the last line of the method will automatically be returned.
So for example:
def book(name)
return name
end
The method above does not need the return
keyword as the name
variable will automatically be returned:
def book(name)
name
end
puts book("Harrington on Hold'em")
# => "Harrington on Hold'em"
If you’re coming to Ruby from a language like PHP, one of the things that might catch you out is the fact that Ruby does not require that you use parenthesis when you call a method.
For example, to call the method above, we can simply write:
hello_world
If we were to change the method to accept a parameter:
def hello_world(name)
puts "Hello #{name}"
end
We can still call the method without the parenthesis:
hello_world 'Philip'
There are certain conventions in Ruby for when you should and should not use things like parenthesis. Over time you will pick up these conventions, but if you want a head start, I’d recommend having a read of this Ruby Style Guide.
Imagine we have the following method that creates a new Hash that represents a book:
def book(name, publisher = nil, tags = [])
{ name: name, publisher: publisher, tags: tags }
end
In this method we have a required parameter of name
, an optional parameter of publisher
and an optional tags
parameter, which defaults to an empty Array.
We can call this method in the following ways:
puts book 'The 4 Hour Work Week'
puts book "The Innovator's Dilemma", 'HBS'
puts book 'Zero to One', 'Random House', ['business']
puts book 'The Long Tail', nil, ['business']
This works fine for the first three calls to this method, but when we want to supply a tags
Array, and not a publisher
things start to fall apart because you need to also send the nil
placeholder.
Another downside of defining arguments like this is that you need to memorise the order in which they should be supplied. This is obviously going to be impossible once you have hundreds of methods and so you will find yourself wasting a lot of time looking up the order of the arguments.
Traditionally this problem was solved in Ruby by using an options Hash:
def book(name, options = {})
publisher = options[:publisher] || nil
tags = options[:tags] || []
{ name: name, publisher: publisher, tags: tags }
end
puts book 'The 4 Hour Work Week'
puts book "The Inovator's Dilemma", { publisher: 'HBS' }
puts book 'Zero to One', { publisher: 'Random House', tags: ['business'] }
puts book 'The Long Tail', { tags: ['business'] }
This works fine, but can be annoying if you want to set default values for the arguments in the hash.
Ruby 2.0 introduced a better way to deal with this problem in keyword arguments:
def book(name, publisher: nil, tags: [])
{ name: name, publisher: publisher, tags: tags }
end
puts book 'The 4 Hour Work Week'
puts book "The Innovator's Dilemma", publisher: 'HBS'
puts book 'Zero to One', publisher: 'Random House', tags: ['business']
puts book 'The Long Tail', tags: ['business']
This method definition is using the new Ruby Hash syntax that looks a lot like JSON. Remember, publisher:
and tags:
are simply Ruby symbols.
With this syntax you can set the defaults as part of the method definition.
You can also pass the options hash in any order, so you don’t need to memorise the order of the arguments:
puts book 'The Black Swan', tags: ['investing'], publisher: 'Penguin'
Sometimes you will need to write a method that should accept an arbitrary number of arguments.
For example, image we needed to accept the name
of a book and one or more tags
.
You could implement that using the Splat Operator:
def book(name, *tags)
{ name: name, tags: tags }
end
In this method definition, all arguments after the name
argument will be scooped up and placed into the tags
Array.
We can call this method like this:
puts book 'The Lean Startup', 'Business', 'Startups'
The Splat Operator allows you to accept an Array of parameters without having the method be passed an Array when it is called.
In Ruby 2.0 the double splat operator was introduced. This works in a similar way to the single splat operator, but it is for creating a Hash of arguments, rather than an Array.
For example, image we have the following method:
def book(name, *tags, **meta)
{ name: name, tags: tags, meta: meta }
end
The *tags
will scoop up normal arguments and place them in the the tags
Array and the **meta
will scoop up the keyword arguments and place them in the meta
Hash.
You can see this in action by making the following calls to the method:
puts book 'The Art of the Start'
# => {:name=>"The Art of the Start", :tags=>[], :meta=>{}}
If we call the method with only a name
, the tags
Array and the meta
Hash are empty.
puts book 'Little Bets', 'Business', 'Technology'
# => {:name=>"Little Bets", :tags=>["Business", "Technology"], :meta=>{}}
If we call the method with a name
and two random arguments, those arguments will be scooped up and placed into the tags
Array.
puts book 'Linchpin', 'Business', author: 'Seth Godin'
# => {:name=>"Linchpin", :tags=>["Business"], :meta=>{:author=>"Seth Godin"}}
If we call the method and pass keyword arguments they will be scooped up and placed into the meta
Hash.
As with all programming languages there are many interesting and unique characteristics of Ruby.
However in the grand scheme of things, it’s important that you get the basics down and then cross each bridge as you come to it.
I prefer to think about programming as learning how to use a tool. Once you’ve got the basics down, you need to start using the tool.
As we progress with learning Ruby we will likely encounter a whole load more of interesting things to do with methods.
But for now, we will move on to the next topic. In next week’s tutorial we will look at writing Ruby classes.