Exercise 40: Modules, Classes, and Objects

Ruby is called an "object-oriented programming language." This means there is a construct in Ruby called a class that lets you structure your software in a particular way. Using classes, you can add consistency to your programs so that they can be used in a cleaner way. At least that's the theory.

I am now going to teach you the beginnings of object-oriented programming, classes, and objects using what you already know about hashes and modules. My problem is that object-oriented programming (OOP) is just plain weird. You have to struggle with this, try to understand what I say here, type in the code, and in the next exercise I'll hammer it in.

Here we go.

Modules Are Like Hashes

You know how a hash is created and used and that it is a way to map one thing to another. That means if you have a hash with a key "apple" and you want to get it then you do this:

1
2
mystuff = {'apple' => "I AM APPLES!"}
puts mystuff['apple']

Keep this idea of "get X from Y" in your head, and now think about modules. You've made a few so far, and you should know they are:

  1. A Ruby file with some functions or variables in it inside a module .. end block..
  2. You import that file.
  3. And you can access the functions or variables in that module with the . (dot) operator.

Imagine I have a module that I decide to name mystuff.rb and I put a function in it called apple. Here's the module mystuff.rb:

1
None

Once I have this code, I can use the module MyStuff with require and then access the apple function:

1
2
require "./mystuff.rb"
MyStuff.apple()

I could also put a variable in it named tangerine:

1
2
3
4
5
6
7
8
module MyStuff
    def MyStuff.apple()
        puts "I AM APPLES!"
    end

    # this is just a variable
    TANGERINE = "Living reflection of a dream"
end

I can access that the same way:

1
2
3
4
require "./mystuff.rb"

MyStuff.apple()
puts MyStuff::TANGERINE

Refer back to the hash, and you should start to see how this is similar to using a hash, but the syntax is different. Let's compare:

1
2
3
mystuff['apple'] # get apple from dict
MyStuff.apple() # get apple from the module
MyStuff::TANGERINE # same thing, it's just a variable

This means we have a very common pattern in Ruby:

  1. Take a key=value style container.
  2. Get something out of it by the key's name.

In the case of the hash, the key is a string and the syntax is [key]. In the case of the module, the key is an identifier, and the syntax is .key. Other than that they are nearly the same thing.

Classes Are Like Modules

You can think about a module as a specialized hash that can store Ruby code so you can access it with the . operator. Ruby also has another construct that serves a similar purpose called a class. A class is a way to take a grouping of functions and data and place them inside a container so you can access them with the . (dot) operator.

If I were to create a class just like the mystuff module, I'd do something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyStuff

    def initialize()
        @tangerine = "And now a thousand years between"
    end

    attr_reader :tangerine

    def apple()
        puts "I AM CLASSY APPLES!"
    end

end

That looks complicated compared to modules, and there is definitely a lot going on by comparison, but you should be able to make out how this is like a "mini-module" with MyStuff having an apple() function in it. What is probably confusing is the initialize() function and use of @tangerine for setting the tangerine instance variable.

Here's why classes are used instead of modules: You can take this MyStuff class and use it to craft many of them, millions at a time if you want, and each one won't interfere with each other. When you import a module there is only one for the entire program unless you do some monster hacks.

Before you can understand this though, you need to know what an "object" is and how to work with MyStuff just like you do with the mystuff.rb module.

Objects Are Like Require

If a class is like a "mini-module," then there has to be a concept similar to require but for classes. That concept is called "instantiate", which is just a fancy, obnoxious, overly smart way to say "create." When you instantiate a class what you get is called an object.

You instantiate (create) a class by calling the class's new function, like this:

1
2
3
thing = MyStuff.new()
thing.apple()
puts thing.tangerine

The first line is the "instantiate" operation, and it's a lot like calling a function. However, Ruby coordinates a sequence of events for you behind the scenes. I'll go through these steps using the preceding code for MyStuff:

  1. Ruby looks for MyStuff and sees that it is a class you've defined.
  2. Ruby crafts an empty object with all the functions you've specified in the class using def because you did MyStuff.new().
  3. Ruby then looks to see if you made a "magic" initialize function, and if you have it calls that function to initialize your newly created empty object.
  4. In the MyStuff function initialize I then use @tangerine which is telling Ruby, "I want the tangerine variable that is part of this object." Ruby uses operators like @ and $ to say where a variable is located. When you did $stdin you were saying, "the global stdin," because $ means global. When you do @tangerine you are saying, "the object's tangerine", because @ means "this object."
  5. In this case, I set @tangerine to a song lyric and then I've initialized this object.
  6. Now Ruby can take this newly minted object and assign it to the thing variable for me to work with.

That's the basics of how Ruby does this "mini-import" when you call a class like a function. Remember that this is not giving you the class but instead is using the class as a blueprint for building a copy of that type of thing.

Keep in mind that I'm giving you a slightly inaccurate idea of how these work so that you can start to build up an understanding of classes based on what you know about modules. The truth is, classes and objects suddenly diverge from modules at this point. If I were being totally honest, I'd say something more like this:

  • Classes are like blueprints or definitions for creating new mini-modules.
  • Instantiation is how you make one of these mini-modules and require it at the same time. "Instantiate" just means to create an object from the class.
  • The resulting created mini-module is called an object, and you then assign it to a variable to work with it.

At this point objects behave differently from modules, and this should only serve as a way for you to bridge over to understanding classes and objects.

Getting Things from Things

I now have three ways to get things from things:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# dict style
mystuff['apples']

# module style
MyStuff.apples()
puts MyStuff::TANGERINE

# class style
thing = MyStuff.new()
thing.apples()
puts thing.tangerine

A First Class Example

You should start seeing the similarities in these three key=value container types and probably have a bunch of questions. Hang on with the questions, as the next exercise will hammer home your "object-oriented vocabulary." In this exercise, I just want you to type in this code and get it working so that you have some experience before moving on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Song

  def initialize(lyrics)
    @lyrics = lyrics
  end

  def sing_me_a_song()
    @lyrics.each {|line| puts line }
  end
end

happy_bday = Song.new(["Happy birthday to you",
           "I don't want to get sued",
           "So I'll stop right there"])

bulls_on_parade = Song.new(["They rally around tha family",
            "With pockets full of shells"])

happy_bday.sing_me_a_song()

bulls_on_parade.sing_me_a_song()

What You Should See

$ ruby ex40.rb
Happy birthday to you
I don't want to get sued
So I'll stop right there
They rally around tha family
With pockets full of shells

Study Drills

  1. Write some more songs using this and make sure you understand that you're passing an array of strings as the lyrics.
  2. Put the lyrics in a separate variable, then pass that variable to the class to use instead.
  3. See if you can hack on this and make it do more things. Don't worry if you have no idea how, just give it a try, see what happens. Break it, trash it, thrash it, you can't hurt it.
  4. Search online for "object-oriented programming" and try to overflow your brain with what you read. Don't worry if it makes absolutely no sense to you. Half of that stuff makes no sense to me too.

Common Student Questions

Why do I need @ on @tangerine when I make initialize or other functions?
If you don't have the @ on a variable then Ruby doesn't know which variable you are referring to. Do you mean a tangerine in your function, in the script, or in the object your setting up? The @ on @tangerine makes your usage specific so Ruby knows where to look.

Buy DRM-Free

When you buy directly from the author, Zed A. Shaw, you'll get a professional quality PDF and hours of HD Video, all DRM-free and yours to download.

$29.99

Buy Directly From The Author

Or, you can read Learn Ruby the Hard Way for free right here, video lectures not included.