Lesson Weekend

Ultimately, our goal is to define our own classes and methods: that's the real power of programming. However, we're not quite ready to define our own classes yet. For now, our methods will be defined in Ruby's main. Whenever a Ruby application opens, it creates a top-level object called main. We won't go into the particulars of how main works. However, it's important to understand that the methods we're creating are still being called on objects.

In fact, our very first method will demonstrate that the method is being called on main:

def what_is_self
  self
end

Go ahead and input this method into IRB. Note that if you type the method into IRB yourself, a multi-line prompt will appear like this:

irb(main):001:0> def what_is_self
irb(main):002:1>

The multi-line prompt will not close until you complete the method definition with end.

Let's call our new method:

what_is_self
=> main

Let's go over what's happening here. First, we use the keyword def. def is a signal to the Ruby interpreter that we are about to define a method. Methods start with def and end with the end keyword. what_is_self is the name of the method. The code between the method name and the end keyword is called a block.

Inside our method, we have a single word: self. But what exactly is self referring to? self is the object that the method is being called on. It's similar to JavaScript's this keyword and it's a very powerful and useful keyword that we'll use frequently in Ruby. For now, it's just confirming that our new method is being called on main.

One other thing: how did our method actually return a value?

In Ruby, we can take advantage of implicit return. The implicit return of a Ruby method is the final statement inside the method. In other words, the method will return the result of the final line before the end keyword.

If we wanted, we could also use an explicit return:

def what_is_self
  return self
end

The result of the method is exactly the same. Generally, we'll use the implicit return. However, the return keyword can be very useful, particularly when we want to return a value from a method that isn't the final statement. This is common with conditionals, which we'll cover soon.

We've written our first method but it's not very useful — it's just a learning tool to understand the basics of what self is.

Let's write a more useful method so we can modify a string. In order to do that, we'll need to write a method with a parameter. While we can write methods that are called on the receiver, that involves knowing more about classes — and we aren't quite there yet. So for now, all our methods will be called on main and will take arguments.

def scramble(string)
  reversed_string = string.reverse()
  upcased_string = reversed_string.upcase()
  upcased_string
end

Let's call this method and then take a look at how it works:

scramble("hello")
=> "OLLEH"

We pass "hello" as an argument into our method's parameter. Then we reverse string (the parameter) and save this value in a variable called reversed_string. Next, we call upcase() on the reversed string and save that in another variable. Finally, we use the implicit return to return the value of upcased_string.

We should always strive to refactor our code and make it better. We can take advantage of both method chaining and the implicit return to reduce the code inside the method to just one line:

def scramble(string)
  string.reverse().upcase()
end

Here we reverse string and then upcase the return value. Since this is the final (and only) line of the statement, the method returns it. By the way, the parens are optional if we aren't passing an argument into a method — so we can rewrite the above like this:

def scramble(string)
  string.reverse.upcase
end

Let's try one more example with arguments. We'll create an addiply method that adds three numbers together and then multiplies them by 3.

def addiply(num1, num2)
  (num1 + num2) * 3
end

A few things to note here. We pass in multiple parameters — and this looks exactly the same as when we pass in multiple parameters to a JavaScript function.

Also, note the use of parentheses in the statement itself. Multiplication is computed before addition, so we need to add the parentheses to make sure the addition happens first. In general, the order of mathematical operations is similar to JavaScript and what we learned in math class when we were younger.

We can pass any number of arguments into a method. We can even pass in an indefinite number of parameters by using the * symbol, which is also known as a splat. In other words, we could do this: *arguments. This would pass an array of arguments into the parameter. You probably won't be using the splat much — if at all — in this module, but it's good to know it exists.

Arity in Ruby


While we can pass in any number of arguments, we need to be careful. Unlike JavaScript, Ruby is strict about arity.

Arity is a fancy word for the number of arguments a method can take. JavaScript is very flexible when it comes to arity. Ruby is flexible in many ways, but not when it comes to arity. Let's take a look at examples from JavaScript and Ruby.

JavaScript doesn't care about arity. You can pass as many arguments as you like into a function. Let's take a look:

function numberOfArguments(a,b,c) {
  return `Here are the values of a: ${a}, b: ${b}, and c: ${c}.`
}

The example above uses template literals. Template literals are very similar to Ruby's use of string interpolation, which we'll cover in a moment.

Let's try calling the function with a different number of arguments:

numberOfArguments(1,2);
"Here are the values of a: 1, b: 2, and c: undefined."

numberOfArguments(1,2,3,4);
"Here are the values of a: 1, b: 2, and c: 3."

In the first example, the function happily accepts just two arguments. Any additional parameters that don't take arguments will be undefined. In the second example, the function accepts four arguments; any additional arguments simply won't be passed into a parameter.

Let's write a similar function in IRB with Ruby:

def number_of_arguments(a,b,c)
  "The value of a is #{a}, b is #{b}, c is #{c}."
end

In this function, we use string interpolation, which means we can interpolate a variable or other statements inside a string. In order to do this, we need to use double-quotes (""), not single quotes. To interpolate a value into the string, we use #{value goes here.}. Template literals in ES6 do almost exactly the same thing, except we use backticks and ${}.

Now let's try calling the Ruby function with a different number of arguments:

number_of_arguments(1,2,3)
=> "The value of a is 1, b is 2, c is 3."

number_of_arguments(1,2)
ArgumentError: wrong number of arguments (given 2, expected 3)

As you can see, Ruby complains when we try to pass in the wrong number of arguments. You'll see this error often, especially when you first start with Ruby. When you do see it, it means the number of arguments being passed into the method or function doesn't equal the number of parameters.

Terminology


Argument: A value passed into a variable.

Arity: the number of arguments a method can take.

Block: A piece of code between do and end.

Explicit return: A return statement that uses the return keyword.

Implicit receiver: When we call a method on Ruby without explicitly stating the receiver, the method will be called on self. This is because self is implicitly the receiver.

Implicit return. The final statement inside a Ruby method before the end will always be returned, even if we don't explicitly add a return.

main: A top-level object called main that's created when a Ruby application is opened.

Parameters: Variables attached to methods where arguments can be passed in.

self: Similar to JavaScript's this keyword. self refers to the scope it is called in.

Splat: The * symbol, which let's us pass in any number of arguments.

String interpolation: The process of inserting Ruby code into a string. We use "#{ }" for string interpolation. We must always use double quotes for string interpolation, not single quotes.

Lesson 4 of 10
Last updated August 7, 2022