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.
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
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.
#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
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">
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.
#<> 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">)
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
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
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.