cult3

Writing Ruby Classes

May 27, 2015

Table of contents:

  1. Why do you need to write classes?
  2. Defining a Class
  3. The initialize method
  4. Setting instance variables
  5. Creating getter methods
  6. Creating setter methods
  7. Using attribute_accessor
  8. Using attribute_reader
  9. Using attribute_writer
  10. Conclusion

An important part of object-oriented programming is writing classes. Classes are the blueprints of objects and allow us to define the properties and behaviour that the objects of our applications should posses.

Last week we looked at working with Ruby methods. Writing reusable methods is a big step towards becoming a programmer in the early days of learning to code.

In today’s tutorial we’re going to be looking at writing classes in Ruby.

As Ruby is a typical object-oriented language, many of the characteristics of classes and objects are similar to other languages.

However, we’ll also look at some of the unique aspects that make Ruby a fantastic choice of programming language to use for your next project.

Why do you need to write classes?

Before we get into writing classes in Ruby, first it is important to understand why we need classes in the first place.

A class represents an important concept in the application we are building.

By writing classes, we can encapsulate the behaviour and properties of these important concepts.

Object-oriented programming is all about modelling important concepts as objects and then using those objects to send messages to other objects.

Defining a Class

Hopefully you are familiar with why we need to write classes. I know that the concept can seem foreign at first, but if you just keep persisting things will fall in to place.

To define a new class you use the class keyword, give the class a name, and then finish with an end:

class Book
end

The name of the class is a constant and by convention should begin with a capital letter. If your class name is two words it should use Camel Case, (e.g MyClass).

I’ve saved the class definition in a file called book.rb.

If you fire up irb we can load the class file so we can play around with it:

load './book.rb'

Every time you make a change to the book.rb file you will need to reload the class.

An interesting thing to notice about ruby objects is that all objects inherit from something.

In our definition of our Book class you will notice that we didn’t inherit from any class. But have a look what you get returned when you check the superclass:

Book.superclass
# => Object

If you don’t specify a class to inherit from, your class will automatically inherit from Object. This is really useful because it means your class will already have some of the basic object methods of the ruby language.

If you are new to object inheritance, don’t worry about it. We’ll be looking at inheritance in a future tutorial.

The initialize method

Now that we have our Book class defined we can instantiate it like you would with any other typical Ruby class:

book = Book.new
book.class
# => Book

Whenever we create a new instance of a Ruby object, the initialize method will be automatically called. We can see this in action by modifying the Book class:

class Book
  def initialize
    puts 'You just initialized the Book class!'
  end
end

Now when you create a new instance of the Book class you should see string of text printed to the screen:

Book.new
# => "You just initialized the Book class!"

The initialize is used for setting the object up when it is first created. This is typically setting instance variables, but it could be whatever you need to do when the new object is created.

Setting instance variables

One of the most common things to do in the initialize method is to set the instance variables of the object. For example, when we create a book, we also need to set it’s title:

class Book
  def initialize(title)
    @title = title
  end
end

Notice how we write @title? The @ means that this is an instance variable, rather than a local variable that would be scoped to the initialise method.

To set the title, you pass in the value when creating the new object:

book = Book.new('The Wolf of Wall Street')

Creating getter methods

Now that we have a book object to work with, we are probably going to want to be able to access the title property, right?

Well, normally to access an object’s property you would call the getter method, like this:

book.title
# => NoMethodError: undefined method 'title' for #<Book:0x007fe9c32f2ab0 @title="hello">

Uh oh, looks like our Book object doesn’t have a getter method and so we will need to implement that ourselves:

class Book
  def initialize(title)
    @title = title
  end

  def title
    @title
  end
end

In the title method we simply need to return the @title instance variable. Remember as we saw last week, you don’t need to write the return keyword to return a value from a ruby method.

Creating setter methods

Another common thing you will want to do with an object is change it’s properties. For example, we might want to change the title of the book:

book.title = "Zen and the Art of Motorcycle Maintenance"
# => book.title = "Zen and the Art of Motorcycle Maintenance"
# NoMethodError: undefined method 'title=' for #<Book:0x007fe9c32cc130 @title="The Wolf of Wall Street">

Once again, in order to set a property, we first need to create the setter method:

class Book
  def initialize(title)
    @title = title
  end

  def title
    @title
  end

  def title=(title)
    @title = title
  end
end

Now if you try to reset the title of a book object it should work correctly.

Using attribute_accessor

Getting and setting properties on an object is such a common thing Ruby has a shortcut in the attr_accessor method.

class Book
  attr_accessor :title

  def initialize(title)
    @title = title
  end
end

If you reload the class in IRB you will see that it works in exactly the same way, but we didn’t have to define the getter and setter methods.

attr_accessor accepts a Symbol and will automatically create the getter and setter methods for you. This might not seem like a big deal, but it does save you from repeating the same boilerplate code every time you want to create a new class!

Using attribute_reader

Imagine we modify the Book class to automatically set a created_at timestamp when the object was instantiated:

class Book
  attr_accessor :title, :created_at

  def initialize(title)
    @title = title
    @created_at = Time.new
  end
end

We can, once again, use attr_accessor to automatically create the getter and setter methods:

book.created_at
# => 2015-04-29 18:21:51 +0100

However, once the object is created, we shouldn’t be able to set a new timestamp:

book.created_at = Time.now
# => 2015-04-29 18:24:03 +0100

To get around this problem we can use attr_reader instead of attr_accessor:

class Book
  attr_accessor :title
  attr_reader :created_at

  def initialize(title)
    @title = title
    @created_at = Time.new
  end
end

Now when we try to use the setter method, we should get an Exception:

book = Book.new('The 4-Hour Work Week')
# => #<Book:0x007ff609827150 @title="The 4-Hour Work Week", @created_at=2015-04-29 18:26:36 +0100>

book.created_at
# => 2015-04-29 18:26:36 +0100

book.created_at = Time.now
# NoMethodError: undefined method 'created_at=' for #<Book:0x007ff609827150>

So if you only want to create the getter methods you can use attr_reader instead of attr_accessor.

Using attribute_writer

So you can create both the setter and getter methods, or just the getter methods, how would you create just the setter methods?

Well, Ruby also has the attr_writer method for doing just that:

class Book
  attr_accessor :title
  attr_reader :created_at
  attr_writer :owner

  def initialize(title)
    @title = title
    @created_at = Time.new
  end
end

In the class above you can set the owner property but you can’t read it:

book = Book.new('Fooled By Randomness')
# => #<Book:0x007fbb4a026ec8 @title="Fooled By Randomness", @created_at=2015-04-29 18:30:36 +0100>

book.owner = 'Jane'
# => "Jane"

book.owner
# NoMethodError: undefined method 'owner' for #<Book:0x007fbb4a026ec8>

Conclusion

There’s a lot to cover when looking a Ruby classes so I’m going to break this out into a few different tutorials.

Today we looked at defining a class and creating the getter and setter methods.

I really love some of the unique characteristics of Ruby. For example, the fact that all objects inherit from Object. Or the fact we can use attr_accessor instead of having to write out getter and setter methods.

Its stuff like this that makes Ruby a lovely language to work with.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.