The Rubyist Historian: Methods and Classes

In our last section I introduced some Ruby programming basics. Now we’re moving in to methods and classes.

Methods

Notations like gets() and chomp() are called methods. In our above example, gets() accepts a single line of data from the user and assigns the string to name. So how can we know what methods are available to us as programmers? The all-knowing, all-powerful Ruby Docs. gets and chomp barely scratch the surface. We can do things like count the number of characters or lines in a string or file, reverse the lines of a string or file, cut a string apart and join it in alphabetical order, or operate all methods on it at once. For example:

puts "I am a Rubyist Historian".length() #=> 24
puts "Learning some Ruby-fu".reverse() #=> uf-ybuR emos gninraeL
puts "Ruby is fracking awesomesauce.".split("").sort().join() #=>    .Raaabcceeefgiikmnorsssuuwy

Run this in the terminal and you should get the commented results. For the first example, we would technically say that we are invoking the length method on object “I am a Rubyist Historian." (Or, even more abstractly, “I am a Rubyist Historian” is an object of type string.) Everything before the period is the receiver while everything after the period indicates the method(s) you wish to invoke upon the object.

We can also create methods. We’ll return to our original “hello, name” program. But this time we’re going to write our own method and invoke it. Methods are defined with the keyword def followed by the method name and the method’s parameters between parentheses (parentheses here are optional, but I use them for readability’s sake. Remember, if you do not use parentheses you need to have a single space in its place, e.g., name hello is the same as name(hello)). So to build a new “hello, name” program using a defined method, we could write:

# defining function 'hello' to ask
# for parameter 'name'
def hello(name)
    'Hello ' + name
end

puts "Please enter your name: "
name = gets.chomp

puts hello(name)

NB: Indentations does not matter to Ruby, but for readability’s sake, we include them.

Become very familiar with defining functions. This allows us to define functions for later use and set code apart to keep the program organized (as Prof. Steve Ramsay persistently reminded us in his course, programs are designed for people to read, not just computers). You also want to avoid redundancy. As a final note, what happens inside of functions is not visible outside the function. We can make things visible by using the global variable (adding $ to the beginning of the variable you want visible, e.g., $result) but, as Prof. Ramsay warned us: global variables are evil. No, really, they are. The last thing you’ll want is a global variable to plague parts of your code without your knowledge.

Classes

Similarly to methods, we can define classes in Ruby. Recall that Ruby is an object oriented programming language. By using classes, we’re fully entering the realm of OOP and learning how to create our own objects. We know that objects are closely allied with a type (object of type string, for example) and that certain behaviors go with certain objects. Objects are a data structure and a state, and also have behaviors that we call methods.

Ruby classes are templates for creating new kinds of objects. Classes are created by using the class keyword, and take note that classes are capitalized and methods are lowercase. By using OOP we are making data central through what’s called procedural programming where we’re defining relationships between and among objects.

Let’s say you wanted a program that allowed you to input author names and ISBNs. First we define the class starting off the definition with class followed by the class name, capitalized:

class Books
    # . . .
end

We’ll use the initialize method here, which allows programmers to set the state of constructed objects. We store these as instance variables inside the object, which we incidate through the use of the @ symbol. This makes variables visible within a class – this is not a global variable. But the instance variables means we can allow each object to have its own unique state. initialize is a special method in Ruby. Ruby allocates memory to hold uninitialized objects and then calls the object’s initialize method. The method passes any parameters that were passed to new.

Enough talk, let’s write the code and explain things further:

class Books

    attr_accessor :fname, :lname, :isbn

    def initialize( fname, lname, isbn )
        @fname = fname
        @lname = lname
        @isbn = isbn
    end

    def to_s
        @lname + ", " + @fname + ", ISBN: " + @isbn
    end

end

author = Books.new("Walt", "Whitman", "1234567890")
puts author

What we’ve done here is passed the instance variables @fname, @lname, and @isbn a string by calling the class constructor Books (Books.new(“Walt”, “Whitman”, “1234567890”)). We could just as easily said Books.new(“William”, “Shakespeare”, “1234567890”). Note that attr_accessor is not declaring an instance variable, it’s only creating the accessor methods. Ruby decouples instance variables and accessor methods.

The class Books takes three variables, fname, lname, and isbn. These parameters act like local variables within the method and follow the same lowercase naming convention. Yet, if we kept them as local variables they would vanish once initialize returned. So, we use an accessor to keep the variables usable throughout the class.

Note also that we redefined the to string (.to_s) type cast as well. By default, when Ruby uses puts it calls on the .to_s type cast to convert data into a string. But we want .to_s to be more useful. We can override the default implementation to display whatever we’d like it to display.

Additional Resources

Visit the Rubyist Historian Table of Contents for more sections, and check out the Github repository for an archive of all the code examples.

See something that’s wrong? Examples that don’t work? Explanations that are unclear or confusing? Embarrassing typographic errors? Drop me an email at jason.heppler+feedback at gmail and I’ll fix things right up!

Topic structure, examples, and explanations for the Rubyist Historian are inspired by, credited to, and drawn from Stephen Ramsay and his course Electronic Text.