49 Days of Ruby: Day 11 - Objects

Ben Greenberg - Apr 7 '21 - - Dev Community

Welcome to day 11 of the 49 Days of Ruby! ๐ŸŽ‰

After our journey through Strings, Integers, Booleans, Arrays, Hashes, and more, we're ready to dive into Objects!

Imagine I had the perfect model for a cup of coffee. I could make unique versions based on that model. Sometimes I may wish to add some sugar to the coffee. Other times, I may wish to add a dash of milk.

Each cup of coffee based on that perfect coffee model is a coffee object. The perfect model of coffee is a coffee class. We're going to get into classes more soon. It's enough for today to be familiar with the word.

Create Your First Object

Let's extend our coffee example and use it to create our first object in Ruby.

I'm going to give you a Coffee class from which our first coffee object will come from:

class Coffee
  def initialize(sugar:, milk:, size:)
    @sugar = sugar
    @milk = milk
    @size = size
  end
end
Enter fullscreen mode Exit fullscreen mode

The new Coffee class we created has an #initialize method. This method is on the class itself, meaning that it can be accessed on Coffee and not on instances we will create.

The #initialize method instructs our class we created what kind of things it is composed of and what those values are. In this case, we created a Coffee class that has sugar, milk, and size. We did not specify any default values for those, but rather accept any argument during the instantiation of objects from it.

Now that we have a class for Coffee, we can create some coffee! I'll create one for you:

irb(main)> breakfast_coffee = Coffee.new(sugar: 1, milk: 1, size: 'venti')

=> #<Coffee:0x000000013a241090 @sugar=1, @milk=1, @size="venti">
Enter fullscreen mode Exit fullscreen mode

Did you notice that what was outputted after we created breakfast_coffee looked a lot different than things we've seen up until this point? That's how a new object looks like in Ruby.

The #<> syntax tells us we're talking about an object. The first word is the class the object comes from. After the number and letter combo, which is not important for us right now, we see the variables assigned to our new object: sugar, milk, and size with their respective values.

What happens if we try to access one of them, perhaps the milk?

irb(main)> breakfast_coffee.milk

NoMethodError (undefined method `milk' for #<Coffee:0x000000013a241090 @sugar=1, @milk=1, @size="venti">)
Enter fullscreen mode Exit fullscreen mode

What? I can see the milk right in that object! Why can't I access them? You can't access them because by default they are private. The data held inside the object is called the state of the object, and unless you explicitly say otherwise, it remains locked away.

We can make a new instance method for Coffee that exposes the milk in each instance:

class Coffee
  def initialize(sugar:, milk:, size:)
    @sugar = sugar
    @milk = milk
    @size = size
  end

  def milk
   @milk
  end
end
Enter fullscreen mode Exit fullscreen mode

Now, when we ask for milk from our breakfast_coffee, we'll see the value we assigned to it.

This need to expose the data is so common that Ruby has some built-in helpers for you to make it even easier!

Let's open up that Coffee class again and add the following right at the top:

class Coffee
  attr_reader :sugar, :milk, :size

  def initialize(sugar:, milk:, size:)
    @sugar = sugar
    @milk = milk
    @size = size
  end
end
Enter fullscreen mode Exit fullscreen mode

We were able to remove an entire method with just one line called attr_reader, which stands for attributes reader. In other words, you can tell your code these are the attributes that I want to be able to be read from my objects. I can specify all of them, some of them or none of them.

What if you want to be able to change a value after instantiating it? There is another helper called attr_writer, which as you can guess, is the complementary helper to attr_reader, but defining those things that can be changed (or written).

How about if I want to be able to read and write the values after instantiation? We have attr_accessor, which is the catch-all and allows you to perform both operations on anything defined in the helper.

Go ahead and experiment more with objects in your IRB session! What else can you discover? Share your learnings and see you tomorrow!

Come back tomorrow for the next installment of 49 Days of Ruby! You can join the conversation on Twitter with the hashtag #49daysofruby.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .