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.