Bits for hackers.

Why We Create Objects in Ruby

When I first started learning how to code in Ruby, I kept hearing about how it was a purely object oriented language; however, I never REALLY understood what this meant on a practical level. So, this post is my attempt to try to clarify objects and object orientation for others who might be looking for a less abstract explanation.

What is an Object?

Before we discuss why we build objects, we should first understand and align on what objects are in the first place. It is easiest to understand this concept if you think of object in code as a model or representation of an object(any people, place or thing) in the real world.

For example, let’s say that you’re building an application that requires you to model a person (a tangible example of this if you want to create something like Siri for iPhones). In your code, you would create a new object called Person. The way to create such a Person object is to define a new Person class:

1
2
3
class Person

end

However, this would make a really boring person because we haven’t really defined any behaviors or things that this Person class can do! For simplicity’s sake, lets say that some of the key behaviors that all people can have are to say hello or to sneeze. We define these behaviors as methods within the body of the class:

1
2
3
4
5
6
7
8
9
class Person
  def say_hello
      puts "Hello, how are you doing today?"
  end

  def sneeze
      puts "Achooooo!!!"
  end
end

Now that we’ve done this, you can see that the Person object allows us to give people as a whole the ability to say hello and sneeze.

So, Why do we Create Objects?

To make our code better imitate reality so that it makes logical sense to us.

As you saw, the Person class we created makes sense to us (not as coders, but as humans, in general) - when you want to make the person (let’s use a person, Bob, as an example) say hello or sneeze, you just tell them to do so by passing the appropriate method: bob.say_hello or bob.sneeze. As you can imagine, we can then continue to build out our Person to infinite complexity by adding more such behaviors/methods.

Additionally, in the real world, objects are made up of other objects (e.g., people are made up of cells, countries are made up of people, sandwiches are made up of bread and meat) and share some characteristics with them.

Similarly, in Ruby, objects are also made of other objects and share many of the methods/behaviors with those objects.

Let’s take a look at an example in the illustration below:

As you can see, Ruby’s objects are designed in the same way as objects are in reality. You have BasicObject which serves as a “cell” or building block for all other objects that come out of it.

To see this more practically, you can see that if you pass class and superclass methods (these are methods that are shared across all objects in ruby) on any object in Ruby, at some point, you will bump into BasicObject! Try it out! You can do this on anything - whether it is an array, string, a number, a class, or hash, they will all lead up to BasicObject.

Illustration of how all Objects derive from BasicObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person

end

bob = Person.new

# bob.class
#=> Person

# Person.class
# => Class 

# Person.class.superclass
# => Module 

# Person.class.superclass.superclass
# => Object 

# Person.class.superclass.superclass.superclass
# => BasicObject 

This is why everything in Ruby is an object! Everything is derived from the same building block of BasicObject. I never fully grasped this concept until I understood the Ruby object chain.

If you still don’t believe me - take a look at this somewhat more complex drawing.

This proves that EVERYTHING in Ruby is an object - even “nil” and all of the errors you get when coding!

Great, So Ruby Objects Model Real Life, How Does This Help Me?

Using objects simplifies our lives as coders. Again, let’s take a look at a real life example of a school. If we weren’t able to create objects such as a Students class where we can add the behavior that we want, we would have to constantly deal with more primitive data types, such as hashes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
school = {
:name => "High School",

:address => {:city => "New York, :state => NY"},

:teachers => [{:name => "Mr. Jones", :subject => "Math"},
      {:name => "Mrs. Brown", :subject => "English"},
      {:name => "Mr. King", :subject => "Gym"},
      {:name => "Mrs. Smith", :subject => "Science"}],
:students => [{:name => "Bobby", :grade => "9"},
      {:name => "Jimmy", :grade => "10"},
      {:name => "Steve", :grade => "11"},
      {:name => "Timmy", :grade => "11"},
      {:name => "Jenny", :grade => "12"}]
}

As you can imagine, this hash can get huge and insanely complicated. Imagine if we did really put all of the data for all of the teachers and all of the students in here. Now say we wanted to store 100 pieces of more pieces of data per student (e.g., address, class grades, classes taken). This mess of nested arrays and hashes would soon become very unwieldy if you had to pick out a certain student or compile data from this information. If you need to do this over and over again, it becomes even more cumbersome.

Instead, we can create a Student class/object or a Teachers class/object that responds to similar to how we created the person class.

Example of how we could create a Teacher object
1
2
3
4
5
6
7
8
9
10
class Teacher
  
  attr_reader :name, :subject

  def initialize (name, subject)
      @name = name
      @subject = subject
  end

end

As you can see in the example above, rather than navigating the complexities of the primitive hash every time we want to get information about a teacher, all we have to do is something like the following to get information about a teacher:

Much simpler than navigating through the nested hash and arrays, right?
1
2
3
4
5
6
7
8
9
10
11
mrjones = Teacher.new("Mr. Jones", "Math")

# Now that we have intitialized Mr. Jones as an object in 
# the line above, we can just pass the methods "subject" 
# and "name" to Mr. Jones

mrjones.subject
# => "Math"

mrjones.name
# => "Mr. Jones"

Creating objects such as these allow us to just “set it and forget it.” Rather than having to comb through a primitive objects such as hashes over and over again, we can just use a quicker, and more natural way of getting the same result. Our object now becomes a black box - once you create it and it behaves as you like, you never have to worry about the internal workings of that object again unless you want to change functionality.

I hope that this was a good primer on objects - all of the topics discussed here really helped me to understand what object orientation really means in Ruby.