Learn Ruby on Rails: the Ultimate Beginner’s Tutorial

Object Oriented Programming in Ruby

Let’s build on the theory that we covered at the start of this chapter as we take a look at Ruby’s implementation of OOP.

As we already know, the structure of an application based on OOP principles is focused on interaction with objects. These objects are often representations of real-world objects, like a Car. Interaction with an object occurs when we send it a message or ask it a question. If we really did have a Car object called kitt (we don’t — yet), starting the car might be as simple as:

irb> kitt.start

This short line of Ruby code sends the message start to the object kitt. Using OOP terminology, we would say that this code statement calls the start method of the kitt object.

As I mentioned before, in contrast to other object oriented programming languages such as Python and PHP, in Ruby, everything is an object. Especially when compared with PHP, Ruby’s OOP doesn’t feel like a “tacked-on” afterthought — it was clearly intended to be a core feature of the language from the beginning, which makes using the OOP features in Ruby a real pleasure.

As we saw in the previous section, even the simplest of elements in Ruby (like literal strings and numbers) are objects to which you can send messages.

Classes and Objects

As in any other OOP language, in Ruby, each object belongs to a certain class (for example, PontiacFirebird might be an object of class Car). As we saw in the discussion at the beginning of this chapter, a class can group objects of a certain kind, and equip those objects with common functionality. This functionality comes in the form of methods, and in the object’s ability to store information. For example, a PontiacFirebird object might need to store its mileage, as might any other object of the class Car.

In Ruby, the instantiation of a new object that’s based on an existing class is accomplished by sending that class the new message. The result is a new object of that class. The following few lines of code show an extremely basic class definition into Ruby — the third line is where we create an instance of the class that we just defined.

irb> class Car
irb> end
=> nil
irb> kitt = Car.new
=> #<Car:0x75e54>

Another basic principle in OOP is encapsulation. According to this principle, objects should be treated as independent entities, each taking care of its own internal data and functionality. If we need to access an object’s information — for instance, its internal variables — we make use of the object’s interface, which is the subset of the object’s methods that are made available for other objects to call.

Ruby provides objects with functionality at two levels — the object level, and class level — and it adheres to the principle of encapsulation while it’s at it! Let’s dig deeper.

Object-level Functionality

At the object level, data storage is handled by instance variables (a name that’s derived from the instantiation process mentioned above). Think of instance variables as storage containers that are attached to the object, but to which other objects do not have direct access.

To store or retrieve data from these variables, another object must call an accessor method on the object. An accessor method has the ability to set (and get) the value of the object’s instance variables.

Let’s look at how instance variables and accessor methods relate to each other, and how they’re implemented in Ruby.

Instance Variables

Instance variables are bound to an object, and contain values for that object only.

Revisiting our cars example, the mileage values for a number of different Car objects are likely to differ, as each car will have a different mileage. Therefore, mileage is held in an instance variable.

An instance variable can be recognized by its prefix: a single “at” sign (@). And what’s more, instance variables don’t even need to be declared! There’s only one problem: we don’t have any way to retrieve or change them once they do exist. This is where instance methods come into play.

Instance Methods

Data storage and retrieval is not the only capability that can be bound to a specific object — functionality, too, can be bound to objects. We achieve this binding through the use of instance methods, which are specific to an object. Invoking an instance method (in other words, sending a message that contains the method name to an object) will invoke that functionality on the receiving object only.

Instance methods are defined using the def keyword, and end with the end keyword. Enter the following example into a new Ruby shell:

$ irb
irb> class Car
irb>   def open_trunk
irb>     # code to open trunk goes here
irb>   end
irb> end
=> nil
irb> kitt = Car.new
=> #<Car:0x75e54>

What you’ve done is define a class called Car, which has an instance method with the name open_trunk. A Car object instantiated from this class will (possibly using some fancy robotics connected to our Ruby program) open its trunk when its open_trunk method is called. (Ignore that nil return value for the moment; we’ll look at nil values in the next section.)

Indenting your Code

While the indentation of code is a key element of the syntax of languages such as Python, in Ruby, indentation is purely cosmetic — it aids readability, but does not affect the code in any way. In fact, while we’re experimenting with the Ruby shell, you needn’t be too worried about indenting any of the code. However, when we’re saving files that will be edited later, you’ll want the readability benefits that come from indenting nested lines.

The Ruby community has agreed upon two spaces as being optimum for indenting blocks of code such as class or method definitions. We’ll adhere to this indentation scheme throughout this book.

With our class in place, we can make use of this method:

irb> kitt.open_trunk
=> nil

Since we don’t want the trunks of all cars to open at once, we’ve made this functionality available as an instance method.

I know, I know: we still haven’t modified any data. We use accessor methods for this task.

Accessor Methods

An accessor method is a special type of instance method, and is used to read or write to an instance variable. There are two types: readers (sometimes called “getters”) and writers (or “setters”).

A reader method will look inside the object, fetch the value of an instance variable, and hand this value back to us. A writer method, on the other hand, will look inside the object, find an instance variable, and assign the variable the value that it was passed.

Let’s add some methods for getting and setting the @mileage attribute of our Car objects. Once again, exit from the Ruby shell so that we can create an entirely new Car class definition. Our class definition is getting a bit longer now, so enter each line carefully. If you make a typing mistake, exit the shell and start over.

$ irb
irb> class Car
irb>   def set_mileage(x)
irb>     @mileage = x
irb>   end
irb>   def get_mileage
irb>     @mileage
irb>   end
irb> end
=> nil
irb> kitt = Car.new
=> #<Car:0x75e54>

Now, we can finally modify and retrieve the mileage of our Car objects!

irb> kitt.set_mileage(5667)
=> 5667
irb> kitt.get_mileage
=> 5667

This is still a bit awkward. Wouldn’t it be nice if we could give our accessor methods exactly the same names as the attributes that they read from or write to? Luckily, Ruby contains shorthand notation for this very task. We can rewrite our class definition as follows:

$ irb
irb> class Car
irb>   def mileage=(x)
irb>     @mileage = x
irb>   end
irb>   def mileage
irb>     @mileage
irb>   end
irb> end
=> nil
irb> kitt = Car.new
=> #<Car:0x75e54>

With these accessor methods in place, we can read to and write from our instance variable as if it were available from outside the object.

irb> kitt.mileage = 6032
=> 6032
irb> kitt.mileage
=> 6032

These accessor methods form part of the object’s interface.

Class-level Functionality

At the class level, class variables handle data storage. They’re commonly used to store state information, or as a means of configuring default values for new objects. Class variables are typically set in the body of a class, and can be recognized by their prefix: a double “at” sign (@@).

First, enter the following class definition into a new Ruby shell.

$ irb
irb> class Car
irb>   @@number_of_cars = 0
irb>   def initialize
irb>     @@number_of_cars = @@number_of_cars + 1
irb>   end
irb> end
=> nil

The class definition for the class Car above has an internal counter for the total number of Car objects that have been created. Using the special instance method initialize, which is invoked automatically every time an object is instantiated, this counter is incremented for each new Car object.

By the way, we have actually already used a class method. Do you like how I snuck it in there? The new method is an example of a class method that ships with Ruby and is available to all classes — whether they’re defined by you, or form part of the Ruby Standard Library. (The Ruby Standard Library is a large collection of classes that’s included with every Ruby installation. The classes facilitate a wide range of common functionality, such as accessing web sites, date calculations, file operations, and more.)

Custom class methods are commonly used to create objects with special properties (such as a default color for our Car objects), or to gather statistics about the class’s usage.

Extending the earlier example, we could use a class method called count to return the value of the @@number_of_cars class variable. Remember that this is a variable that’s incremented for every new Car object that’s created. Class methods are defined identically to instance methods: using the def and end keywords. The only difference is that class method names are prefixed with self. Enter this code into a new Ruby shell:

$ irb
irb> class Car
irb>   @@number_of_cars = 0
irb>   def self.count
irb>     @@number_of_cars
irb>   end
irb>   def initialize
irb>     @@number_of_cars+=1
irb>   end
irb> end
=> nil

The following code instantiates some new Car objects, then makes use of our new class method:

irb> kitt = Car.new         # Michael Knight's talking car
=> #<0xba8c>
irb> herbie = Car.new       # The famous VolksWagen love bug!
=> #<0x8cd20>
irb> batmobile = Car.new    # Batman's sleek automobile
=> #<0x872e4>
irb> Car.count
=> 3

The method tells us that three instances of the Car class have been created. Note that we can’t call a class method on an object (Ruby actually does provide a way to invoke some class methods on an object, using the :: operator, but we won’t worry about that for now. We’ll see the :: operator in use in Chapter 4, Rails Revealed.):

irb> kitt.count
NoMethodError: undefined method 'count' for #<Car:0x89da0>

As implied by the name, the count class method is available only to the Car class, not to any objects instantiated from that class.

I sneakily introduced something else in there. Did you spot it? In many languages, including PHP and Java, the ++ and — operators are used to increment a variable by one. Ruby doesn’t support this notation; instead, when working with Ruby, we need to use the += operator. Therefore, the shorthand notation for incrementing our counter in the class definition is:

irb>     @@number_of_cars+=1

This code is identical to the following:

irb>     @@number_of_cars = @@number of cars + 1

Both of these lines can be read as “my_variable becomes equal to my_variable plus one.”

Inheritance

If your application deals with more than the flat hierarchy we’ve explored so far, you might want to construct a scenario whereby some classes inherit from other classes. Continuing with the car analogy, let’s suppose that we had a green limousine named Larry (this assigning of names to cars might feel a bit strange, but it’s important for this example, so bear with me). In Ruby, the larry object would probably descend from a StretchLimo class, which could in turn descend from the class Car. Let’s implement that, to see how it works:

$ irb
irb> class Car
irb>   @@wheels = 4
irb> end
=> nil
irb> class StretchLimo < Car
irb>   @@wheels = 6
irb>   def turn_on_television
irb>     # Invoke code for switching on on-board TV here
irb>   end
irb> end
=> nil

Now, if we were to instantiate an object of class StretchLimo, we’d end up with a different kind of car. Instead of the regular four wheels that standard Car objects have, this one would have six wheels (stored in the class variable @@wheels). It would also have extra functionality, made possible by an extra method — turn_on_television — which would be available to be called by other objects.

However, if we were to instantiate a regular Car object, the car would have only four wheels, and there would be no instance method for turning on an on-board television. Think of inheritance as a way for the functionality of a class to become more specialized the further we move down the inheritance path.

Don’t worry if you’re struggling to wrap your head around all the aspects of OOP — you’ll automatically become accustomed to them as you work through this book. You might find it useful to come back to this section, though, especially if you need a reminder about a certain term later on.

Return Values

It’s always great to receive feedback. Remember our talk about passing arguments to methods? Well, regardless of whether or not a method accepts arguments, invoking a method in Ruby always results in feedback — it comes in the form of a return value, which is returned either explicitly or implicitly.

To return a value explicitly, use the return statement in the body of a method:

irb> def toot_horn
irb>   return "toooot!"
irb> end
=> nil

Calling the toot_horn method in this case would produce the following:

irb> toot_horn
=> "toooot!"

However, if no return statement is used, the result of the last statement that was executed is used as the return value. This behavior is quite unique to Ruby:

irb> def toot_loud_horn
irb>   "toooot!".upcase
irb> end
=> nil

Calling the toot_loud_horn method in this case would produce:

irb> toot_loud_horn
=> "TOOOOT!"

Standard Output

When you need to show output to the users of your application, use the print and puts (“put string”) statements. Both methods will display the arguments passed to them as Strings; puts also inserts a carriage return at the end of its output. Therefore, in a Ruby program the following lines:

print "The quick "
print "brown fox"

would produce this output:

The quick brown fox

However, using puts like so:

puts "jumps over"
puts "the lazy dog"

would produce this output:

jumps over
the lazy dog

At this stage, you might be wondering why all of the trial-and-error code snippets that we’ve typed into the Ruby shell actually produced output, given that we haven’t been making use of the print or puts methods. The reason is that irb automatically writes the return value of the last statement it executes to the screen before displaying the irb prompt. This means that using a print or puts from within the Ruby shell might in fact produce two lines of output — the output that you specify should be displayed, and the return value of the last command that was executed, as in the following example:

irb> puts "The quick brown fox"
"The quick brown fox"
=> nil

Here, nil is actually the return value of the puts statement. Looking back at previous examples, you will have encountered nil as the return value for class and method definitions, and you’ll have received a hexadecimal address, such as <#Car:0x89da0>, as the return value for object definitions. This hexadecimal value showed the location in memory that the object we instantiated occupied, but luckily we won’t need to bother with such geeky details any further.

Having met the print and puts statements, you should be aware that a Rails application actually has a completely different approach to displaying output, called templates. We’ll look at templates in Chapter 4, Rails Revealed.

Go to page: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://onsman.com/ ronsman

    That should be fixed now.

  • Radha Krishna

    For beginners this will be very helpful…