ensure: Execute Code Whether or Not an Error Occurs

There may be some circumstances in which you want to take some particular action whether or not an exception occurs. For example, whenever you are dealing with some kind of unpredictable input/output—say, when working with files and directories on disk—there is always the possibility that the location (the disk or directory) or the data source (the file) either may not be there at all or may provide some other kinds of problems—such as the disk being full when you attempt to write to it or the file containing the wrong kind of data when you attempt to read from it.

You may need to perform some final “cleanup” procedures whether or not you have encountered any problems, such as logging onto a specific working directory or closing a file that was previously opened. You can do this by following a begin..rescue block of code with another block starting with the ensure keyword. The code in the ensure block will always execute, whether or not an exception has arisen beforehand.

Let’s look at two simple examples. In the first one, I try to log onto a disk and display the directory listing. At the end of this, I want to be sure that my working directory (given by Dir.getwd) is always restored to its original location. I do this by saving the original directory in the startdir variable and once again making this the working directory in the ensure block:

ensure.rb

startdir = Dir.getwd

begin
   Dir.chdir( "X:\" )
   puts( `dir` )
rescue Exception => e
   puts e.class
   puts e
ensure
   Dir.chdir( startdir )
end

When I run this, the following is displayed:

We start out here: C:/Huw/programming/bookofruby/ch9
Errno::ENOENT
No such file or directory - X:
We end up here: C:/Huw/programming/bookofruby/ch9

Let’s now see how to deal with the problem of reading the incorrect data from a file. This might happen if the data is corrupt, if you accidentally open the wrong file, or—quite simply—if your program code contains a bug.

Here I have a file, test.txt, containing six lines. The first five lines are numbers; the sixth line is a string, “six.” My code opens this file and reads in all six lines:

ensure2.rb

f = File.new( "test.txt" )
begin
    for i in (1..6) do
        puts("line number: #{f.lineno}")
        line = f.gets.chomp
        num = line.to_i
        puts( "Line '#{line}' is converted to #{num}" )
        puts( 100 / num )
    end
rescue Exception => e
    puts( e.class )
    puts( e )
ensure
    f.close
    puts( "File closed" )
end

The lines are read in as strings (using gets), and the code attempts to convert them to integers (using to_i). No error is produced when the conversion fails; instead, Ruby returns the value 0. The problem arises in the next line of code, which attempts a division by the converted number.

Having opened the data file at the outset, I want to ensure that the file is closed whether or not an error occurs. If, for example, I read in only the first five lines by editing the range in the for loop to (1..5), then there would be no exception. I would still want to close the file. But it would be no good putting the file-closing code (f.close) in the rescue clause because it would not, in this case, be executed. By putting it in the ensure clause, however, I can be certain that the file will be closed whether or not an exception occurs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.77.153