Starting with Ruby - part 4

Hurrying on from part 3, let’s wrap up the basics of a Ruby class and get to testing!

You will recall that in defining the BlogPost class, we used a funky accessor generator, so let’s look at accessors in a little more detail. First, here’s how we define getters and setters the old-fashioned way:

# A getter
def title

# A setter
def title= new_title  # Note spacing, it's important
  @title = new_title

… all well and good, but we like shortcuts. Instead of defining a getter for all your attributes, simply add the attr_reader shortcut to your class, with pointers to the attributes you want to be able to read. You can either type out an accessor shortcut for each attribute on separate lines (as we do in the post.rb file provided in part 3), or you can combine them all into one directive (separated with commas). For example, here’s how to make title, status and creation date all readable in your BlogPost class—no more boilerplate methods required!

attr_reader :title, :status, :created

OK, but what about writing to one or more of these attributes? Well, in that case you could use the attr_writer shortcut. However, in all likelihood, if you’re writing, you’re probably reading too, so go the whole hog with attr_accessor, which as one might expect, does both.

Accessing data

The class is good to go; so how do we actually use these accessors from other bits of Ruby code? Well, let’s take BlogPost as our starting point and create a Ruby program that works with it. First, ignore any changes you may have made to the class per the accessor code above, and revert to the “vanilla” code provided in part 3 (also attached to this post for convenience). Now, in the same directory as your post.rb file, create a new Ruby file in your text editor of choice. The first thing this new program needs to know is what classes it has to be aware of, and we do this with something akin to an import statement in Java, like so:

require_relative 'post'

require_relative assumes you’re in the same directory as the class you’re referencing, and the value passed-in is the Ruby file name in which that class resides (less its .rb suffix). There are other types of require statements that you can use, but we’ll stick to the basics for now. Now that’s done, let’s start our new bit of code by instantiating a blog post:

post1 ="Test post number 1", "<p>Glad to be here.</p>")

Here we’ve created a BlogPost object and populated it with a title and some content. Good stuff. Let’s try to access one of its attributes:

puts "My post object's status is #{post1.status}"

If you cast your mind back to part 1 of this series you will remember that the keyword puts will simply shunt some content out to your console. Move on to part 2, where we learned about accessing object attributes using the hash (pound) / curly brace combo. If you take a look at the vanilla BlogPost class you will see that we set a default value for the status field in its initialize method. So far, so good, so let’s move on to setting an attribute value. Here’s the syntax for you to try out:

post1.status = "Published"

Urgh, that was upsetting eh! You should have seen something like this in your console window:

`<main>': undefined method `status=' for #<BlogPost:0x0000010086bea0> (NoMethodError)

… and quite right too. You attempted to set an attribute which does not (yet) have an accessor. In the original definition of BlogPost we only used the attr_reader shortcut, so status is read-only. Let’s make it writable. To do this, open post.rb in your editor, and we shall change status to be writable as well as readable by changing this bit:

attr_reader	:status
attr_reader	:created

… to this (I think we’ll leave the created attribute read-only):

attr_accessor	:status, :title
attr_reader	:created

Now try your status-setting line again, and see where that gets you.

On to testing!

Proper Ruby coders tend to be fanatical about testing, which makes sense given the dynamic nature of the language. Those familiar with unit testing in Java and .NET, using the de facto xUnit standards (e.g. Junit for Java) know that it’s easy enough at first: you stub your method, write a couple of asserts in a TestCase, flesh out the method, keep testing, and so on. But of course, things get more involved. You need to test within the scope of a web application, or a transaction, so you start injecting stuff with Spring TestRunner instances, or using so-called “mock objects” to set up the environment accordingly—all so that you can test one wee method. It takes considerable effort to simulate a network connection or a file system operation, and many Rubyists are simply not up for all that faff in their unit tests.

(All that said, mock object frameworks do, of course, exist for Ruby).

Anyway, rather than build a massive setUp() method with lots of objects ready to test our doesWinkleExist() method in Java, as a conscientious Rubyist, we take a different view, and defer to “duck typing” instead—we don’t care about the context of the method to be tested, we just want to know whether the expected result is returned (in short, duck typing means only caring about the object being passed-in in terms of its capabilities rather than its type). So rather than try to do a networked database read, or a file system write (which may be used in “real life”), our test just makes use of something else which supports the same method(s) that the real life objects use. To my mind, and your mileage may vary, this is what unit testing should be about. If you concern yourself with file systems, databases, other periphery, you lose sight of what it is your unit testing is about. Save file manipulation and the like for integration testing.

Run that duck thing by me again?

I will, I will—another time :-) For now, let’s look again at our BlogPost class. There’s a second one-line method that hitherto I have not mentioned (append_to_file). As its name implies, all it does is append the title of the current post instance to a given file. We could instantiate a real file on the host operating system, write to it, test that the right stuff has gone in, and then clean up after ourselves (remove the file handle, delete the file, etc.…)

Or we could simply use something else in place of a File. No code changes required! After all, we just want to test that the post title goes to the right place—at this stage we don’t care whether our file read / write stuff works, that’s for another test. It’s this sort of dynamism in your code that forms the basis for duck typing, but it is a very different approach if you come from a traditional Java / C# background. So let’s do it, let’s write and run a valid test case, just like that:

def test_append
  b ="Test post number 2", "<p>This post title should end up in a file</p>")
  b.append_to_file(myfile)	#See BlogPost class

  assert_equal(["Test post number 2"], myfile)

Super. By all means review the attached test_post.rb file to see the source code in its entirety, as I’ve removed the dependency definition at the top of the code here, for clarity. By the way, here’s a nifty tip for testing: instead of routing results to the console as normal, you can route them to a text file as simply as this:

ruby test_post.rb > test_results.txt

(Nice). And so, let us conclude

Good fun isn’t it? I find Ruby refreshingly terse after years of Java, and the language’s dynamism makes for some good old brain-twisting fun. So, we’ve covered basic class and method creation, with a teeny bit of of unit testing mixed in for good measure. I’ve left a lot out of course—these are really just teaser posts—but I hope to have whetted a few appetites. There are stacks of excellent resources out there for rounding out your knowledge. Up next? Perhaps some focus on putting all these bits together into a basic Ruby app—complete with conditionals, loops, exception handlng and wot-not? Or maybe moving on to complementary stuff such as RubyGems (packaging), Sinatra (framework for quick web apps—ideal for prototyping) or the full-stack web application framework, Ruby on Rails, who knows!

(Needless to say, if there’s something else you would like a bit of focus on, do pipe up).

Further reading


Comments on this post are now closed.


I’m a software architect / developer / general IT wrangler specialising in web, mobile web and middleware using things like node.js, Java, C#, PHP, HTML5 and more.

Best described as a simpleton, but kindly. You can read more here.