Getting Oriented with Objects in Ruby
Tags: beginners, ruby, programming
Published: 2022-06-08 17:06:55

This post is co-authored by @juliannatetreault and @ridhwana.
 
In our last [post](https://dev.to/ridhwana/introducing-rediscovering-ruby-53bk) we briefly touched upon a few topics such as: object orientation, dynamic languages and memory management. However, we barely scratched the surface of these concepts, and so in this post, we’d like to dive deeper into object orientation and how it all comes together in Ruby. 

## Ruby Objects 

As we mentioned in our previous post, Ruby is centered around the concept of “objects”. Everything in Ruby is an object - this includes Booleans, Strings, Integers and more. 

A Ruby Object will contain its own set of attributes, which you can think of as data, and its own set of methods, which you can think of as the behaviour. 

Ruby Objects communicate by sending messages to each other. These messages can be thought of as instructions. When we talk about “sending a message from Object A to Object B”, what we actually mean is that we “call or invoke a method on Object B”. These terms can be used interchangeably. 

If you take a second to look up from reading this article and glance around your surroundings, you’ll see that there are objects everywhere in the world. 

As I look up at this moment, I see -  my 😼, a 🪴, a 📘, a 🖨 and a 🛋 are some of the objects that jump out to me. All of these have the potential to be modelled as Objects each with its own data and behaviour, and sometimes these objects send messages to each other, like when my cat chews on a leaf from my plant. 

Some of the concepts that we’ll need to discuss first before fully understanding Ruby objects are classes, attributes and methods.

## What is a Class?

A Class is a blueprint for the construction of similar objects - you can think of it as a template that we use to create objects. The template provides the set of attributes and methods for an object, which will then help each object to define its own set of data and shared behaviour.   

For example, we may want to represent my cat. In order to do this, we’d first create a blueprint that we could use to represent all cats. 

_Below we represent the Class-y Cat, haha get it?_

![The Cat Class](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4isysblro5kn17vcimv.png)

This blueprint will contain the attributes that a cat may have like name, age, colour and weight. It will also contain behaviour like the sound that a cat will make or how it will move its tail. In the future, we may want our blueprint to be more general, so we may want to make an Animal blueprint and use it to create both a cat and a dog object - but that’s for later. 

In reality, the code for the Cat Class may look something like this:

```ruby
class Cat 
  def initialize(name, age, colour, weight)
    @name = name
    @age = age
    @colour = colour
    @weight = weight
  end

  def make_sound()
    return "meow"
  end

  def move_tail()
   return "twitching"
  end 
end
```

In order to describe my cat, we would use this template to instantiate a new object. In Ruby the new method is used for instantiating an object. This now creates my object as an instance of the `Cat` Class.

```ruby
zeus = Cat.new("Zeus", 2, "brown", 5)  
```

Now this may not make sense to you as yet, but let’s break down the code above by describing the attributes and methods.

### Attributes (Data)

`name`, `age`, `colour` and `weight` in the parenthesis are referred to as parameters. They describe the characteristics of the object.

When we instantiate an object, like in the code below, we pass through the values for these parameters in the form of arguments. The arguments are what allow us to provide specific values for that object. 

![Cat Class with initialize method and obhect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4knkw6xsyjbavuizarp.png)

When we call the method `new` on a class, the class will create a new instance of itself. Internally, it then calls the method `initialize` on the new object, and it will pass all the arguments to the method `initialize`. 
 
The `initialize` method as outlined in the image above is then responsible for preparing the instance data for the object. `@name`, `@age`, `@color` and `@weight` are referred to as instance variables - they can be identified by the `@` prefix. Instance variable data is unique to the object.

Instance variables by default cannot be accessed from outside the object:

```ruby
zeus = Cat.new(“Zeus”, 2, “brown”, 5)  
zeus.age
=> NoMethodError: undefined method `age` 
```

![OMG Cat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i11yg2831ghbsx7dn5n6.jpg)
_But what if we really need to print out Zeus’s age from outside?_

Well, there are definitely times when we may want to access (read and write) these instance variables from the outside. In these cases we can access the attributes with attribute accessors. 

There are three attribute accessors that we will discuss:

**attribute_reader**

In order to be able to read an attribute from the outside without the need to change it then you can use an `attribute_reader`. 

```ruby
class Cat
  attribute_reader :age

  // rest of the code
end
```

When you add `attribute_reader` to your code, Ruby then creates a method on your behalf that looks like the code below, and allows you to read the attribute. The symbol for the attribute_reader is used as the name for this getter_method as defined below. 

```ruby
class Cat
  def age
    @age
  end
  // rest of the code
end

zeus = Cat.new(“Zeus”, 2, “brown”, 5)  
zeus.age
=> 2
```

You’ll learn more about methods in the next section. 

**attribute_writer**

Likewise, in order to be able to write to an attribute from the outside then you can use an `attribute_writer`. Just like with `attribute_reader` Ruby then creates a method on your behalf that looks like the code below, and allows you to write to the attribute.

```ruby
class Cat
  def age=(value)
    @age = value
  end
end

zeus = Cat.new(“Zeus”, 2, “brown”, 5)  
Zeus.age = 4
```

**attribute_accessor**

In order to be able to both read and write to an attribute, we would use `attr_accesor`. Ruby then creates both methods on your behalf. 

Now that we’ve discussed attributes, let’s talk about methods. 
 
### Behaviour (Methods)

Methods allow you to bundle code that can perform tasks and return results in a named, DRY (don’t repeat yourself!) way. Methods can be called from other code, allowing programmers to use the bundled code without having to rewrite it in every place it’s needed.

When it comes to crafting a method, there are a few things you should know about them: 

First, methods are defined using the `def` and `end` keywords. `def`, followed by a name, represents the start of a method. The `end` keyword does just that – it ends the method. What comes in between the `def` and `end` keywords is the bundled code or logic. 

```ruby
def method_name
   // insert some behaviour here 
end
```

Next, methods have their own set of naming conventions. When creating a method, it is important to keep these naming conventions in mind, as it’ll make it easier to understand the intent of the method for the next programmer (or you, 6 months down the road!). The naming conventions we’d like to draw your attention to are: naming your method, the capitalization of your method, and what to do when your method name is longer than a single word.

Let’s begin with naming your method. When brainstorming method name ideas, it is important to ensure that the method is aptly named – the method’s name should be explanatory, giving insight into the method’s purpose. If you’re creating a method that will give your `Cat` Object the ability to meow, then naming the method `meow` is a good place to start. Now let’s touch upon method capitalization, or the lack thereof. Method names should consist of all lowercase letters. Finally, let’s discuss what to do when your method name is longer than a single word. Multi-word method names in Ruby are written in snake case. For example, if you had a method that both made your `Cat` Object meow and move its tail, you might write it as `meow_and_move_tail`, where the underscores between each word represent snake casing. 

```ruby
def meow_and_move_tail
   // insert some behaviour here 
end
```

Finally, the logic within a method is always indented. The rule of thumb when it comes to indentation is four spaces or a tab to the right. While there is an ongoing feud within the programming community about which approach is right (hint: neither are!), it all boils down to preference. 

_Tabs or spaces - which side are you on?_

On the topic of crafting methods, one thing that is important to mention is that methods can accept default arguments and values, which can level up your method and make it more reusable. A default argument allows a method to be called with or without the provided argument, allowing for flexibility. It also gives you the option to reassign the value passed to the default argument, making the method more extensible. To pass a method a default argument with an assigned value, open a set of parentheses after the method name and define the argument and its value like so:

```ruby
def make_sound(sound = “Meow”)
  // insert some behaviour here 
end
```

In the above `make_sound` method, we pass a default argument `sound`, with the value `meow`. By passing this default argument, if we were to lean on the default argument in the method’s logic, we could expect that the string “Meow” would be returned whenever the method is called.

```ruby
def make_sound(sound = “Meow”)
  puts “#{sound}!”
end
```

Now you may be wondering what those squiggly brackets and hashtag are doing within the `make_sound` method–the use of this is called string interpolation, which can briefly be explained as embedding the value within the string that it is a part of. This is something we may touch upon in a later post, so stay tuned, folks!

Similar to default arguments, methods can also accept required parameters. While default arguments and values are optional, required parameters are just that - required. Just like default arguments, required parameters are defined within a set of parentheses that come right after a method’s name. Unlike default arguments though, a required parameter is not given a default value, rather its value is defined when the method is invoked.

```ruby
def make_sound(sound)
  puts “#{sound}!”
end

make_sound(“Purr”)
```

So, method naming conventions, default arguments, and string interpolation aside, let’s briefly touch upon CRUD actions, or the widely used methods standard to Ruby. CRUD, which stands for `create`, `read`, `update`, and `delete`, are a set of methods provided in Ruby that perform basic actions such as creating, reading, updating, or deleting an Object. These methods are important to recognize due to their wide use.

Now, with all of this new-found knowledge of methods, you may be wondering how to actually make use of this behaviour. If so, then this paragraph is for you! We refer to making use of a method’s behaviour as “invoking” or “calling” a method in Ruby. This can be accomplished using dot notation, which when used, invokes a method, passing along a message to an Object, which in our case, is our `Cat`, Zeus. 

![Describing Objects and Methods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqmh9qi4djblhbf0vitm.png)

_Is that really all there is to methods?! Seems pretty straightforward to me…_

The above is just the beginning and while there is so much more to learn about methods, knowing the above should get you to a good baseline understanding of how Objects are given behaviour and receive messages through methods. If you’re keen to dive a bit deeper though, we’ll be releasing a brief follow up post that will touch upon some of the topics that didn’t make it into this post.

_And that’s it folks…_

![Meow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gd1luxmkoamm29z4e5r.png)

Credits to @juliannatetreault for the adorable cat drawings.