Getting to Know Classes and Objects in Ruby using… Pokemon?

Photo by Don H on Unsplash

2021 marks the 25th anniversary of Pokemon’s existence. I have been a big fan of the franchise ever since I was 4 years old. My first exposure came when my savvier Pokemon friend pulled a number of one sided trades that left me with a fistful of element cards and common Pokemon (This rash of trades was a prelude for his skills in making deals as he recently finished law school at Georgetown).

From the trading cards and toys to the handheld and now console games, Pokemon has been an interest of mine that has taken up many hours. While I can’t offer an anniversary song like Post Malone, Katy Perry or J Balvin can (https://www.youtube.com/watch?v=gBGoDmLMe3U), I did want to tie Pokemon into one of my articles. I felt the best tie in would be to look at objects and classes in depth by creating Trainers and Pokemon and investigating their many types.

What are Classes and Objects?

In programming, a class is a template used for creating an object. An object is an item that has attributes that describe it as well as methods that it can perform. The often used, and honestly overused, examples for these are a Car class and car object.

There are many different types of cars, but most cars share certain attributes and abilities. Cars have a number of wheels, doors, drive systems and colors. Along with these physical properties, they also have the ability to perform actions like parking, shifting gears, reversing and operating turn signals. Because these are commonly shared items, we can create a Car class which will serve as a template to speed up our ability to create car objects. A car object can be seen below:

Figure 1. Car class

Here we start by creating a class called Car which will store the information for our car object template. If you come from JavaScript, the first line inside of our class object may seem foreign. The attr_accessor is a special helper method that allows us to interact with the object attributes. The method attr_accessor is a shorthand method that combines the ability to access these values for use (attr_reader) with the ability to access these values to modify (attr_writer).

Ruby requires these helper methods to be used to declare what can be accessed and how it can be accessed inside of class objects. If you do not define and give certain access abilities to each attribute value, you will get an undefined error where you reference that value.

After we get past the line that allows for reading and writing we get to the initialize method. This is a class specific method that all classes have. This method allows us to accept values when we create a new instances of the object and bind these values to that instance.

In our car example we accept 4 arguments: model, manufacturer, wheels and max_speed. Because we define these in our initialize method, not including them when you create a new instance of the class will cause an error.

You can see that we set each of these vales equal to its name plus a “@.” This @ sign signifies an instance variable, which we can access off any instance due to our attr_accessor method defined earlier. These instance attributes are very similar to our class attributes which are prefaced with a “@@”, the difference between the two is that you can only access instance variables off of an instance while you can only access a class value off the class itself. We will dive deeper into these ideas once we get to the Pokemon. For now we can run through the other two common Ruby variable types:

Figure 2. Ruby Variable Scope Types

What makes the variables different is their scope, or their availability for use in different parts of a program. We already mentioned how instance and class variables can be accessed. Local variables are available only within the most specific class, method or module they are defined within. Global variables can be defined anywhere and accessed from any place in your program. This sounds great, but global variables are not recommended as they can cause naming collisions and make maintain code more difficult.

The last items I want to look at with our car class example are instance methods. Just as classes have instance and class attributes, so do classes which have instance and class methods. Instance methods can only be called on an instance and can take arguments if arguments are defined in the method definition. For our example we have activate_turn_signal, which takes a string and prints a sting based on the value of the argument.

Figure 3. Turn signal method

Now that we have walked through classes with our rudimentary car, let’s move on to Pokemon, Trainer and Type classes!

Creating Our Trainer Class

If you are a fan of the series, or even just grew up in the 90’s and early 2000’s, you know that the point of Pokemon was to capture all 150 of the original Pokemon. This could be accomplished by searching for and catching Pokemon, training your Pokemon to evolve, or trading with trainers in the game or your real life friends.

In the games you could have 6 Pokemon with you at any time, but had a large amount of storage available to you through the PC system. In our Trainer class, we will not limit ourselves to 6 Pokemon, but will instead allow as many as we are able to find and catch.

Before we create the Trainer class, let’s first think about the different things that we will allow our Trainer to do and the different pieces of information they should have. There are certain methods that all Trainers should share, these methods are known as class methods. Class methods have a different syntax from regular methods:

Figure 4. Regular and Class Methods

When it comes to classes and objects, the difference between regular and class methods is in how they are called and used. If you were to define a method inside a class, like you do for regular methods, you will have to create an instance of the class to access it. This is okay for methods that may be used for instances, but for actions that are shared between all instances of a class it would be nice to be able to use them on the class itself. This is where class methods come in.

Class methods are instance independent methods that are called directly on the class object. They can be defined inside the class on either the class object’s name, or on the self object. The self object is an interesting concept of programming. In this instance, the self object is a pointer that points to whatever object it is being called within. In our case the self object points to the Trainer class that wraps the method.

It is more common, and recommended to use self instead of the Class name when creating class methods. This makes code more easily maintainable as changing the class name does not require you to update every class method within your class.

Now that we’ve gone over regular methods, class methods and self, let’s look at our Trainer class itself:

Figure 5. Trainer class

The first thing we see in our Trainer class is the attr_reader helper method. In our previous examples we used attr_accessor which allows you to read and change instance variables. Our two instance variables for this class are a string name and an array pokemon, which we don’t want our users to manually manipulate. Because of this we will use attr_reader to allow them to read, but not manually modify the Trainer class instances.

Next we see our initialize method. Here we pass in a string called name when we create a Trainer instance. We also create an array called pokemon. While it starts out empty, and we are not able to change it from the Trainer class, we will see later how we fill it by creating instances of our Pokemon class.

After the initialize method we come to our first regular method. Because this method is not a class method, it cannot be used without creating an instance of Trainer. If we create an object called ash and call greeting, this is what we get:

Figure 6. Trainer greeting instance method

Here we call our method on our instance, and access the instance attribute before printing it in our string interpolation. This prints “Hello my name is Ash!” to the terminal. A later method I want to highlight is the throw_pokeball method that accepts an action argument. Though you have to call these methods defined within the class on an instance, this does not limit you from adding parameters to your methods.

The last interesting part of this class is the class method we create with catchphrase. This generic string can and should be used by all aspiring Pokemon trainers. Class methods and variables should be used whenever you want to perform a function off of the class object, or access shared information from all instances. Here we have:

Figure 7. Trainer class method

When we run it on the Trainer class object we can see that it prints “Gotta catch em all!” to the terminal. This is our only class method for this class and displays the generic mission all trainers have. It can’t be accessed from the instances currently, but we could change this by creating an instance method that and “self.class.catchphrase” inside.

Figure 8. Class method accessed through instance method

Now that we’ve looked at items such as different attr helper methods, the initialize method and class methods, let’s next create our Type class.

Creating Our Types

Every Pokemon has one or more types. These types help identify which types of attacks can be used by which Pokemon. The type also dictates which types your Pokemon is strong and weak against. In our Type class we will identify the name, strength and weakness and then keep track of how many different types exist.

Here is the code for our Type class:

Figure 9. Type Class

Again we use “attr_reader” because we do not want users to be able to change the information once it is set. We only want users to read it. On the next line we see something new with @@number_of_types. This is a class variable. Class variables are great if we want to keep track of the number of instances of our class we create, or different attributes of the different instance variables.

Here we are simply keeping a count of the number of types that have been created. In our initialize method we can see that every time we create a new type, we increment our @@number_of_types variable. You cannot access class variables directly, so in order to be able to access the number of types, we create a class method “self.show_number_of_types” which prints out the current value of @@number_of_types.

With this Type class we are able to create instances of different types, including their strengths and weaknesses, keep track of the total number of types and read these values. Now that we have the ability to define types, let’s create some Pokemon!

Catching some Pokemon

Now for the moment you’ve been waiting for, creating a Pokemon class. This class is the culmination of what we have learned building our Trainer and Type classes. Compared to our other classes it is quite a bit shorter:

Figure 10. Pokemon Class

At the top of our class we make use of both the attr_accessor and attr_reader helper methods. We use attr_reader with our name and type because after the initialize method is run we should not be able change either of these. We do however allow read and write access to the level and trainer so that we can increment the level as the Pokemon gets stronger and change the trainer if the Pokemon is traded or released.

Below these methods we have our initialize method. Here we set the name and type of the Pokemon as well as the trainer. What’s new in this initialize method is that when create this Pokemon, it will be added to the trainer’s Pokemon. This is done by using the array push operator “<<” with self which in this case refers to the instance of the Pokemon being created.

A quick note on trading and releasing: using attr_accessor to change the trainer will not be sufficient with our current example. If we change the trainer to none, or another trainer, the Pokemon will not be removed or show up in the trainer’s list. To do this we would have to write more instance methods to act on specific trainers, but this is outside the scope of this article.

Putting it all Together

Now that you have gotten through this article you are a Pokemon and Class Champion… well sort of. As any trainer knows, there is always more to learn and explore. The journey to master Ruby and Object Oriented Programming never really stops, but hopefully you feel more confident in your understanding of classes and their instances, different variable scopes and the self object. If you like the article, leave a like and comment and keep a look out for more content!

Former Big Beer Engineer turned Full Stack Software Engineer