Blocks can be used to define a chunk of code that must be run under
some kind of transactional control.
For example, you'll often open a
file, do something with its contents, and then want to ensure that the
file is closed when you finish. Although you can do this using
conventional code, there's an argument for making the file responsible
for closing itself. We can do this with blocks. A naive implementation
(ignoring error handling) might look something like the following.
class File
def File.openAndProcess(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.openAndProcess("testfile", "r") do |aFile|
print while aFile.gets
end
|
produces:
This is line one
This is line two
This is line three
And so on...
|
This small example illustrates a number of techniques. The
openAndProcess
method is a
class method---it may be
called independent of any particular
File
object. We want it to
take the same arguments as the conventional
File.open
method,
but we don't really care what those arguments are. Instead, we
specified the arguments as
*args
, meaning ``collect the actual
parameters passed to the method into an array.'' We then call
File.open
, passing it
*args
as a parameter. This expands the
array back into individual parameters. The net result is that
openAndProcess
transparently passes whatever parameters it
received to
File.open
.
Once the file has been opened,
openAndProcess
calls
yield
,
passing the open file object to the block. When the block returns, the
file is closed. In this way, the responsibility for closing an open
file has been passed from the user of file objects back to the files
themselves.
Finally, this example uses
do
...
end
to define a block. The only
difference between this notation and using braces to define blocks is
precedence:
do
...
end
binds lower than ``{...}''. We
discuss the impact of this on page 234.
The technique of having files manage their own lifecycle is so useful
that the class
File
supplied with Ruby supports it directly. If
File.open
has an associated block, then that block will be
invoked with a file object, and the file will be closed when the block
terminates. This is interesting, as it means that
File.open
has
two different behaviors: when called with a block, it executes the
block and closes the file. When called without a block, it returns the
file object. This is made possible by the method
Kernel::block_given?
, which returns
true
if a block is associated
with the current method. Using it, you could implement
File.open
(again, ignoring error handling) using something like the following.
class File
def File.myOpen(*args)
aFile = File.new(*args)
# If there's a block, pass in the file and close
# the file when it returns
if block_given?
yield aFile
aFile.close
aFile = nil
end
return aFile
end
end
|