GSwR VI: Stay Classy with Ruby

This entry is part 6 of 6 in the series Getting Started with Ruby

Getting Started with Ruby

Welcome to the last post in the Getting Started with Ruby series.

In the previous post, we covered writing our own methods. In this post, we’ll learn all about how classes work: How to create them, how to create methods for them, and how to use inheritence to save recreating lots of behaviour.

Classes are quite literally the building blocks of Ruby – everything in Ruby is an object and classes are basically a list of method definitions that acts like a blueprint for objects. Classes are used to create objects, known as instances of the class. These objects have all of the methods defined in the class.

We’re going to start by looking at how to add our own methods to the built in classes such as Strings, Integers and Arrays.

Changing Built In Classes

We’ve already seen many of the built-in methods in classes like String and Integer, such as reverse and odd?. Ruby let’s us add extra methods to these classes by opening up the class definition. This is done by typing the keyword class followed by the name of the class.

Here’s an example of opening up the Integer class to add a new method called double:

class Integer
  def double
    self * 2
  end
end

Notice that the word ‘Integer’ starts with a capital letter, which is true for all class names.

This new method returns the value of the integer that calls the method multiplied by 2. The self keyword refers to the object itself, in this case the integer that calls the method. Now all integers will have this method.

We can test this code out in IRB. First of all, save the code above in a file called ‘extensions.rb’ and then load it into IRB so that the extra methods become available to us. To do this, navigate to the folder where the ‘extensions.rb’ file is saved and then launch IRB by typing irb into a terminal prompt. Once IRB starts, we just need to type the following:

load ‘./extensions.rb’
=> true

Now we can have a go at testing out our new method:

2.double
=> 4
5.double
=> 10
12345.double
=> 24690

Great, it works!

You can add new methods for all of the built in classes such as Strings and Arrays. In fact you can even redefine the methods that already exist. Here’s an example that changes the behaviour of the reverse method on the String class. Add the following code to the bottom of the ‘extensions.rb’ file:

class String
  def reverse
    "no reversing"
  end
end

As you can see, by opening up the String class we redefine the reverse method to return a string that says ‘no reversing’. Have a go at running this in IRB – you will need to save ‘extensions.rb’ and then load it again for the changes to take effect.

Changing the way built-in methods behave is very much frowned upon since people expect methods to work in a certain way. Adding new methods that add more functionality is (usually) thought of as good thing though. This is as monkey patching. Ruby on Rails has a module called Active Support which adds lots of extra methods to the String class. One of the new methods is called pluralize, which returns the plural version of a string.

Here’s an example of a similar pluralize method. Add it to the bottom of the ‘extensions.rb’ file:

class String

  def pluralize
    case self
      when "woman" then "women"
      when "person" then "people"
      when "octopus" then "octopi"
      when "sheep" then "sheep"
      else self + "s"
    end
  end

end

This uses a case statement to return the plural of some irregular words and then just puts an ‘s’ on the end for all the rest. Let’s have a look at this in action in IRB (don’t forget that you’ll need to load extensions.rb again):

octupus.pluralize
=> octupi

book.pluralize
=> books

bus.pluralize
=> buss

It’s by no means complete, as shown in the last example. It’s readily apparent, though, how this could be used to add some very useful functionality to all strings.

Creating Your Own Classes

We don’t just have to make do with playing around with the built in classes though – we can create our own!

In the last few posts, we’ve imitated a die being rolled. Let’s create a Die class that can be used to create lots of die objects. We can then “roll” these dice produce random numbers.

Create a file caled dice.rb and add the following code:

class Die
  def roll
    rand(1..6)
  end
end

Any class starts with the class keyword and is followed by the name of the class (always capitialized). It ends with the end keyword. We then add any methods inside this class definition.

To create a new Die object, we use the new method of the Die class (don’t forget to load the ‘die.rb’ file):

die = Die.new
 => #<Die:0x8e3459c>

This instantiates a new Die object represented by #, which shows a unique ID for the object. The object is stored in the variable die and will have access to all the methods in the class definition – the only method we have at the moment is the roll method that should return a random integer between 1 and 6:

die.roll
=> 5
die.roll
=> 2

But what if we want to have a die with a different number of sides, do we have to create a new class for every type of die? Not at all! We can use instance variables to store information about an object – these can be set when an object is instantiated using a special method called intialize. Open up the ‘die.rb’ file and change it to the following:

class Die
  def initialize(sides=6)
    @sides = sides
  end

  def roll
    rand(1..@sides)
  end
end

The initialize method gets called automatically when a new Die object is created. As you can see, it has a parameter called sides that has a default value of 6. This is used to set an instance variable called @sides.

Instance variables always have an “at” @ symbol at the beginning and are available to all the methods in a class (a normal variable is only available inside the method in which it is defined.) We have seen these before when using Sinatra, where instance variables are available in all route handlers and all views (this is actually because these are actually all just methods of a class that Sinatra uses.)

This means that the number of sides can be set when a Die object is created and then stored in an instance variable. @sides is used in the roll method as the upper limit of the call to the rand method.

Let’s have a go at testing our new Die class in IRB (don’t forget to save ‘die.rb’ and load it again):

twenty_sided_die = Die.new(20)
 => #<Die:0x96c5c38 @sides=20>

When the new method is called, it creates a new Die object and invokes the initialize method. As you can see, along with the object’s unique ID, the instance variable @sides is also stored in the object. This is then used in the roll method:

twenty_sided_die.roll
=> 13
twenty_sided_die.roll
=> 7

Object Properties

We just saw in the Die example that a property of an object can be stored as an instance variable. There are different ways of creating, reading, and updating these properties. Let’s create a new Animal class to see how it works.

Add the following code to a file called ‘animals.rb’:

class Animal
  def initialize(name="Animal")
    @name = name
    puts "An animal has been born"
  end

  def say_hello
    "Hello! My name is #{ @name }"
  end
end

This class uses the initialize method to create an instance variable called @name that stores the name of the animal object provided as an argument to the new method. We also use puts to output a string to say “An animal has been born”.

The class has another method called say_hello. This outputs a string that uses the @name instance variable. Let’s go into IRB and test these methods out. First of all, we’ll need to require the ‘animal.rb’ file:

load './animal.rb'
 => true
freddy = Animal.new("Freddy")
An animal has been born
 => #<Animal:0x891f1d8 @name="Freddy">

Here we can see that an Animal object has been created and stored in a variable called freddy. It has a unique ID (0x891f1d8) and a name property (“Freddy”). This freddy variable should respond to the Animal class’ methods:

freddy.say_hello
 => "Hello! My name is Freddy"

We can create a getter method that simply tells us the value of property stored as an instance variable. Getter methods simply have the same name as the property which we want to query. For example, if we want to know the name of an object, we can add this getter method:

def name
  @name
end

This simply returns the value of the instance variable @name:

freddy.name
=> "Freddy"

We can also create a setter method that allows us to create or update the value of a property stored in an instance variable. This has the same name as the property, but is followed by an = symbol. It also takes an argument – the new value for the instance variable.

Here is an example of a setter method for the name property:

def name=(value)
  @name = value
end

This has a parameter called value and all that is done inside the method is to set the instance variable @name to be equal to value:

freddy.name = "Frederick"
=> "Frederick"
freddy.name
=> "Frederick"

Because getter and setter methods are very common in class defnitions, Ruby has a shortcut. Instead of writing a getter method, we can simply use the attr_reader method. This takes the property, in symbol form, as an argument.

For example, if the Animal class also had an age property, we could add a getter method like so:

attr_reader :age

There is a similar shortcut for setter methods – attr_writer. So if we wanted the age property have a setter method we would write:

attr_writer :age

It’s very common to want both getter and setter methods, so Ruby has another method that combines the two:

attr_accessor :age

This creates both getter and setter methods for the age property.

freddy.age = 4
=> 4
freddy.age
=> 4

Inheritance

Inheritance is a powerful aspect of object-oriented programming that allows you to create generic classes that can then be used to as a base for more specific classes. The “subclasses” inherit all the functionality from the generic “super” classes.

To demonstrate this, we’re going to create a Duck class that inherits from the Animal class. This is because a duck is an animal and should be able to do everything an animal can do. But a duck will also have some more specific things it can do that not all animals can. Inheritance allows us to take all the methods from the Animal class and then add any extra methods that are specific to the Duck class.

To see this, add the following code to the bottom of the ‘animals.rb’ file.

class Duck < Animal
  def swim
    puts "I'm quackers about swimming!"
  end

  def say_hello
    "Quack! I'm a duck called #{ @name }"
  end
end

We use the angled bracket to signify inheritence. In this example the class Duck inherits from the Animal class. This means that the Duck class gets all of the methods in the Animal class. We can add any additional methods that we want the Duck class to have. In this case, we added a method called swim. This is something that we don’t want all animals to do, only Ducks.

If some of the methods in the Animal class are not quite right, we can overwrite them in the Duck class by simply redefining them. In this case we redefine the say_hello method so that the string it produces is more like what a duck might say.

We can see the results of this in IRB (don’t forget to reload the ‘animals.rb’ file):

donald = Duck.new('Donald')
An animal has been born
 => #<Duck:0x9e26ba8 @name="Donald"> 

donald.say_hello
 => "Quack! I'm a duck called Donald"

donald.swim
 => "I'm quackers about swimming!"

Inheritance can save a lot of time when creating classes as it saves you from reinventing the wheel. You can simply take all the methods from an existing class and build upon it.

Sinatra

We couldn’t finish this series off without a look at Sinatra! Now that we’ve covered classes, it’s about time we looked at how to create modular-style Sinatra applications.

You see, all of the web apps we’ve created so far have been what are know as “classic-style” applications. In a “modular-style” application, we create a class that inherits from Sinatra::Base. This ensures that our class will have all methods defined on the Sinatra::Base class.

Here is an example of the Hello World application that we started with written in the modular style:

require 'sinatra/base'

class MyApp < Sinatra::Base
  get '/hello'
    "Hello Sinatra!"
  end

  run! if app_file == $0
end

The first difference to notice is that instead of require 'sinatra', we use require 'sinatra/base'. This is because we only need part of the Sinatra library and it is also the trigger to say that we are using the modular-style.

The next difference is that all of the route handlers are wrapped in a class definition. This class can be called anything we like. It’s MyApp in this case, but it must inherit from Sinatra::Base in order to gain all of Sinatra’s methods.

Another big difference is that we have to explicitly start the server using Sinatra’s run! method. The code after this is an if statement to start the server if the file is the main file being run (denoted by the $0 symbol) rather than required or loaded by another file. This is in case we want to use the code in tests or as part of a bigger application and don’t want the server to start.

Although the classic-style that we have used throughout this series is fine in most cases, using the modular-style can have some benefits. This is especially true when you want to build bigger applications or frameworks, or release your application as a Ruby Gem.

That’s All Folks!

This brings us to the end of theGetting Started With Ruby series. I hope that you have found it useful and see the joy of Ruby. I also hope that you’ve seen how easy it is to use Sinatra to turn your Ruby programs into web apps.

This is only the beginning, though. We’ve only just scratched the surface of Ruby. There is so much more to discover: modules, tests, meta-programming … there’s always something more to learn in Ruby!

Keep improving your knowledge by reading books and all the wonderful posts on this website, but most of all practice! Programming is just like any other discipline: The more you do it, the better you’ll get at it. And don’t forget to ask any questions in the comments if you need help. Most of all, have fun learning to program in Ruby!

Getting Started with Ruby

<< GSwR V: Methods to the Madness

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.

No Reader comments

Comments on this post are closed.