May 27, 2015
Table of contents:
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.
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.
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.
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.
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')
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.
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.
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!
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
.
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>
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.