Seeing with Ruby Classes

November 26, 2014

In Ruby, everything is an object. When you want something done in Ruby, you ask an object to do it. You ask 2 to add 2 to itself, rather than abstractly add 2 plus 2. As such, a programmer's challenge is largely about engineering objects to have a role and play that role effectively. For example, I might want to create a catalog of all my books. In doing so, I want to create an object book, which is able to return its title, author, and other information. But I have 100 books. Now I face the problem of defining 100 books and writing methods for all 100 to return the proper information. Instead, a general book class can be defined so that every instance of that class, a specific book object, will have the methods it needs to return the title and author. As such, defining a class represents grouping methods, so that objects created from that class behave similarly.

Let's dive deeper into the book example and the difference between classes and objects (though in Ruby, classes are themselves objects). In this case, the book object is a specific book with a title and an author, at the least. The book class is a generic book, one that doesn't have information associated with it until we create a specific, new book object. As mentioned, a class consists of a collection of method definitions. Methods that are meant to be shared among all instances of the class are called instance methods (as opposed to singleton methods which work on one particular object–we briefly proposed using these until we realized we had 100 books). Let's define the class Books and two methods for setting and retrieving the title.

              class Books
                def set_title(string)
                  @title = string
                end

                def get_title
                  @title
                end
              end

              book = Books.new
              book.set_title("The Name of the Wind")
              puts book.get_title
              => "The Name of the Wind"
            

The code returns the title of the book! A couple things to note: first, the variables in the class start with a single @. This is to denote they are instance variables. Instance varaibles allow individual objects of that class to remember a state or variable. As such, instance variables are only visible to the object to which they belong. Additionally, an instance variable created in one method can be used by any other method in that class, unlike local variables. This is how an object can remember its state. Notice that @title is created in set_title and retrieved in get_title–two separate methods. The second thing to note is that the book object is created using the new method on the Books class. new is a constructor method for the class, which creates and returns a new instance of the class.

Let's say we want to set the title of the book at the moment of the object's creation, rather than setting it later. We can do the following:

              class Books
                def initialize(string)
                  @title = string
                end

                def get_title
                  @title
                end
              end

              book = Books.new("The Name of the Wind")
              puts book.get_title
              => "The Name of the Wind"
            

Here we've used the special method initialize to execute the block of code every time a new instance of the class is created. In this case, we're requiring a string at the moment of creation, which is set as the title. If we wanted to change the title at a later time other than initialization, we would need a setter method, like we've saw above with set_title. In this case, set_title sets or writes the title attribute for the book. Attributes are properties of an object whose value can be read and/or written through the object. The get_title method similarly gets or reads the title and returns it. Because setting and getting (or writing and reading) attributes is so common, Ruby offers some syntactic sugar to streamline the process:

              class Books
                attr_reader :title
                attr_writer :title
              end

              book = Books.new
              book.title = "The Name of the Wind"
              puts book.title
              => "The Name of the Wind"
            

Here, attr_reader replaces the get_title method and attr_writer replaces the set_title method. The colon in :title denotes that it is a symbol, roughly equivalent to a string. Because we can both read and write the title, we could condense the code even further using attr_accessor, which is the equivalent of attr_writer plus attr_reader.

              class Books
                attr_accessor :title
              end

              book = Books.new
              book.title = "The Name of the Wind"
              puts book.title
              => "The Name of the Wind"
            

As a final example, let's add in the ability to read/write the author and set the title using the initialize method again, but only allow reading of the title:

              class Books
                attr_reader :title
                attr_accessor :author #note we could add more attributes by separating with a comma

                def initialize(string)
                  @title = string
                end
              end

              book = Books.new("The Name of the Wind")
              puts book.title
              => "The Name of the Wind"
              book.author = "Patrick Rothfuss"
              puts book.author
              => "Patrick Rothfuss"
            

In conclusion, classes are extrememly useful in creating organized and logical code. By developing a class and creating instances of that class, you can share behaviors among objects, lessening your work and making your code more efficient.