Developer

Understand how to use and implement Ruby blocks

The ubiquity, power, and elegance of the Ruby block makes it an important feature of the language that any Rubyist should know how to use and implement.

Ruby has the concept of a special type of code "block." These Ruby blocks are essentially syntactic sugar for passing a lambda (an anonymous function) to a method as an argument. Possibly the most common example of a Ruby block is the each block iterator:

foo = [0,1,2,3,4,5,6,7,8,9]

vals = [0,1]

foo.each do |n|

print "#{n}: "

puts vals[n] ||= vals[n-1] + vals[n-2]

end

In this example block, which is used to iteratively generate a Fibonacci sequence from an array of integers, each number in the array foo is being passed to the block in turn as parameter n. The code in the block is then used to operate on that number. This syntax makes for a very clear, clean appearance that makes the operation look much more direct than it actually is behind the scenes. To give you an example of how the minimum functionality of each is used to accomplish the task set forth in that example, let us use this naive implementation:

class Array

def my_each

for element in self

yield element

end

end

end

foo = [0,1,2,3,4,5,6,7,8,9]

vals = [0,1]

foo.my_each do |n|

print "#{n}: "

puts vals[n] ||= vals[n-1] + vals[n-2]

end

Not all blocks in Ruby are iterators, however. For instance, the File class comes with an open method that can take a block as an argument. This is used to neatly group file operations with the code that opens the file and provides several benefits, including automatically closing the open file when the block is finished executing. An example of this method in action follows:

File.open('/home/username/foo.txt').each_line do |line|

if line.match(/foo/)

puts line

else

puts 'fooless line'

end

end

The alternative approach, which is more common in other languages, is to do something like this Ruby example:

foo_file = File.open('/home/username/foo.txt')

for line in foo_file

if line.match(/foo/)

puts line

else

puts 'fooless line'

end

end

foo_file.close

While this is a fairly trivial example, much more complex series of operations that must be performed on an opened file may eventually result in the programmer forgetting to close the opened file when finished, using the second approach. This can be especially problematic where a looping construct of some kind is used to perform the same operation over and over again with a new file each time, potentially resulting in thousands of files being opened and never closed if the file closing operation never executes — whether because of some error in the code or because it was never written by the programmer.

This File.open example shows a method that optionally takes a block, which the my_each method above does not. Luckily for someone who wants to create a method that optionally takes a block, the method block_given? offers a simple solution:

class Array

def my_each

for element in self

if block_given?

yield element

else

puts element

end

end

end

end

The yield method offers succinct clarity of code, but there may be times you want more from your Ruby blocks — the ability to pass them around in your code like a variable. In such circumstances, blocks can be handled as method arguments:

class Array

def my_each(&block)

if self.size < 10

for element in self

yield element

end

else

self.even_each(&block)

end

end

def even_each

for element in self

if element.modulo(2) == 0

yield element

end

end

end

end

If you want to use idiomatic Ruby code, you might want to use some blocks within your block-using method implementations:

class Array

def my_each(&block)

if self.size < 10

self.each {|element| yield element }

else

self.even_each(&block)

end

end

def even_each

self.reject do |element|

element.modulo(2) != 0

end.each {|element| yield element }

end

end

In this example, the even_each method operates only on every even number in the array. The my_each method will operate on every element, but only if the entire array has fewer than 10 elements in it; otherwise, it passes the block off to even_each. Thus, you might get the following with these methods when executing them in irb:

>> foo = [0,1,2,3,4,5,6,7,8,9]

=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>> foo.my_each {|n| print "#{n} " }; puts '!'

0 2 4 6 8 !

=> nil

>> bar = [0,1,2,3]

=> [0, 1, 2, 3]

>> bar.my_each {|n| print "#{n} " }; puts '!'

0 1 2 3 !

=> nil

Coming up with more sophisticated uses of the Ruby block is left as an exercise for the reader.

About

Chad Perrin is an IT consultant, developer, and freelance professional writer. He holds both Microsoft and CompTIA certifications and is a graduate of two IT industry trade schools.

Editor's Picks