2013-12-02

This entry is part 5 of 5 in the series Getting Started with Ruby

We’ve covered some of Ruby’s most important object types in the last three posts – Strings, Integers & Floats and collections such as Arrays, Ranges and Hashes. We’ve also looked at the methods that give these objects their functionality. In this post, we’re going to look at creating our own methods.

To define a method we use the def keyword, followed by the name of the method. The code for the method comes next before finishing with the end keyword. Let’s have a look at some examples in IRB:

This method is called say_hello and returns the string “Hello Ruby!”. The last line of a method in Ruby is its return value which is the value that is returned by the method when it is called. To call a method, simply enter its name:

Methods are useful. They can make your code easier to read (as long as you give your methods descriptive names) and mean that you don’t have to write repetitive blocks of code over and over again. Also, if you want some of the functionality to change, then you only need to update the method in one place. This is known as the DRY (Don’t Repeat Yourself) principle and it is important to keep in mind when programming.

Adding Parameters

Methods can be made more effective by including parameters. These are values that are provide for the method to use. For example we can improve the say_hello method by adding a ‘name’ parameter:

Parameters are added to the method definition after the method name (the parentheses are optional but recommended). In the body of the function, the name of the parameter acts like a variable equal to the value that is passed to the method as an argument when it is called:

In this case, the string “Sinatra” is provided as an argument and we use string interpolation to insert the arguument into the string that is returned by the method.

We can provide a default value for a parameter by putting it equal to the default value in the method definition:

This means we can now call the say_hello method with or without arguments:

We can add more parameters, some with default arguments and some without (but the ones without must come first). Here is another funtion called greet that uses multiple parameters:

We can also create methods with an unspecified number of arguments by placing a ‘*’ in front of the last parameter in the method definition. Any number of arguments will then be stored as an array with the same name as the parameter. The following method will take any number of names as an argument and store them in an array called names:

Notice that the return value of the method is the names array.

Another option is to use keyword arguments instead (although these are only available from Ruby version 2.0 onwards). These act by using a hash like syntax for the parameters, as demonstrated in an updated version of our greet method:

The arguments can then be entered in any order using the keywords:

Notice that the order of the keyword arguments doesn’t matter and, if some of them are omitted then, the default value is used instead.

We can also add an extra parameter at the end with ** in front of it. This will collect any extra keyword arguments that are not specified in the method definition in a hash with the same name as the parameter:

It’s also possible to add a block as a parameter to a method, by placing a & before its name. The block can then be accessed in the method definition by referring to it. This is useful if you want to run some specific code when a method is called. Here’s a basic example of a method called repeat which accepts a block of code as well as an argument telling it how many times to run that code:

The block is optional and there is a handy method called block_given? that allows us to check if the block is given when the method is called. Here is a method called roll_dice that takes the number of sides to the dice as an argument as well as a block that can be used to do something with the value rolled on the dice:

If the method is called without a block, then it simple returns the number rolled on the dice:

If we want to mimic a 20-sided dice then we can enter a sides argument:

Now say we want to roll the dice, but then double the result and then add 5. We can use a block to do this:

Another example of using a block might be if we want to return whether the result of rolling the dice was odd or even:

Using blocks as parameters can make methods extremely flexible and powerful. The route handlers in Sinatra take blocks as arguments. The method definition for a GET request looks similar to this:

The route argument is a string that tells us the route to match. This is followed by a hash of options set to an empty hash by default. Last of all, the method takes a block, which is the code that we want to run when the route is visited.

Refactoring Play Your Cards Right

We’re going to have a go at refactoring the code for the ‘Play Your Cards Right’ Sinatra app that we created in the last tutorial. Refactoring code is the process of improving its structure and maintainability without changing its behaviour.

What we’re going to do is replace some of the chunks of code with methods. This will make the code easier to follow and easier to maintain – if we want to make a change with the functionality, we just need to change the method in one place.

Sinatra uses helper methods to describe methods that are used in route handlers and views. These are placed in a helpers block that can go anywhere in the code, but is usually placed near the beginning and looks like this:

To start with, we’re going to rewrite the app using the names of methods to describe the behaviour we want. Creat a new file called ‘playyourcardsrightrefactored.rb’ and add the following code:

This is very similar to the last piece of code (and it has exactly the same functionality) except that it’s much easier to see what’s going on in each route handler. This is because we have replaced a lot of the Ruby code with methods and chosen some descriptive method names, making the code more readable. It almost looks like pseudocode.

Let’s have a look at each route handler in turn to see what it does:

This route handler sets up the game then redirects to the ‘/play’ route … in fact, this shouldn’t even need explaining because it says it right there in the code! The method names tell us exactly what is happening – all of the code has been extracted into a method called set_up_game which needs to be created. Place the following inside the helpers block:

This sets up the session variables that we’ll need in the game. The deck is shuffled, the guesses are set to -1 (because it gets incremented by 1 on the first play, even though a correct guess hasn’t been made) and the value variable is set to 0.

redirect and to are both methods built into Sinatra and have been purposefully named so that the code in a route handler reads almost like English.

Now let’s have a look at the start of the route handler that deals with the gameplay:

First of all we need to draw a card, so we write a method to do that. Add the following in the helpers block:

This is a very short method, but it gives us more descriptive code.

Next we want to find out the value of the card. The code that we used for this in part 4 was quite a long case statement to check for picture cards. We can extract this into a method that takes a card as an argument and then returns the value of that card. The following code also goes in the helpers block:

Next, we have an if statement to check if the player has guess correctly or not:

This also uses the name of the methods to make the code very descriptive. The first method is called player_has_a_losing and it has a parameter named value. This combination of name and parameter name makes it read nicely. This method also needs to go in the helpers block:

This returns true or false depending on whether the value entered as an arguement is higher or lower than the value stored in session[:value] (the previous card’s value) compared to the player’s guess, which is stored in the params hash with a key of :guess.

If the player_has_a_losing method returns false, then we move on to two more functions. The first is update_session_with, which takes an argument of value. This does exactly as it says, as well as updating the number of guesses by 1. It also goes in the helpers block:

The next method that needs to go in the helpers block is ask_about. This simply asks the player whether the card is higher or lower. It takes the current card as the argument:

That’s the last of all our helper methods. If you try running the code by typing ruby play_your_cards_right_refactored.rb into a terminal and then visit http://localhost:4567 you should see the same game as before. This is exactly what we want to happen when we refactor our code – no change on the outside, but more readable and maintainable code on the inside.

Scope

In some of the helper methods we just used, we had to supply either the card or value as a parameter to the methods. You might be wondering why we had to do this when card and value both existed already as variables. This is because a variable only exists inside a method if it has been created in the method or if it has been entered as an argument. This can be seen in the following example (check it in IRB):

When puts is called outside the method name is ‘Walt’ and job is ‘teacher’, but as soon as you go inside the method name becomes ‘Heisenberg’ and job is ‘cook’.

A method doesn’t have any access to any variable created outside of it (they need to be entered as arguments to the method)· You also can’t access any variable created inside a method from outside of the method either – any values you want to access after the method has been called should be returned by the method. The places in the code where a variable is accessible is known as the variable’s scope.

That’s All Folks

Hopefully this tutorial has helped to introduce methods and shown how useful they can be in making your code more flexible, maintainable, reusable, and easier to read (as long as they are well named).

The methods we were writing in this tutorial were actually more like functions. As I mentioned in the very first post, Ruby is actually an object orientated language and the methods should be methods of objects. The methods we have been writing are actually all methods of the special main object (See this post by Pat for more in-depth info about this).

In the next post, we’ll be getting classy and looking at how Ruby’s class system works. We’ll go over how to add methods to existing classes such as Strings and Integers and how to create your own classes with their own public and private methods. In the meantime, please leave any comments or questions that you might have in the comments section below.

The post GSwR V: Methods to the Madness appeared first on SitePoint.

Show more