The two keywords yield and super allow you to pass control back and forth between parent and child methods, to weave power between a more general method (in the parent class) and a more specific method (in the child class) with ease and logic. Using yield and super effectively can help you maintain the DRY (Don’t repeat yourself) principle, keeping your code easier to maintain.

Say you have three classes for image creation: a parent class, Image, and two children: Raster and Vector. Now for your raster images, you’re going to be using the GD2 library for Ruby, and for your vector images, you’re going to be creating SVG images. Some of the functionality used to create any given image will be different, naturally, since with the raster images, you’re creating an image object, and with the vector images, you’re concatenating strings of XML SVG code. However, a lot of the code will be the same, and should thus be shared between the two children to avoid duplication. For example, the logic used to determine what color some text should be, or what shape you should draw, can be shared between the two child classes. Any shared code should go in the one common class between the two children: the parent class, Image.

Accessing Shared Code

That is, accessing code in a parent class method from a child class method of the same name. The trick is with super.

write_text() in Raster

1
2
3
4
5
6
7
8
9
10
11
12
def write_text( text, color )
  super() do |width, height, left, top|
    image = Image.new( width, height )
    image.draw do |canvas|
      canvas.color = color
      canvas.font = Font::TrueType['/usr/share/fonts/times.ttf', 20]
      canvas.move_to( left, top )
      canvas.text( text )
    end
  end
  image
end

write_text() in Vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def write_text( text, color )
  image = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
  image << '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">'

  super() do |width, height, left, top|
    image << '<svg viewBox="0 0 ' + width.to_s + ' ' + height.to_s + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
    image << '<text font-family="Times" x="' + left.to_s + '" y="' + top.to_s + '" fill="' + color.to_s + '">'
  end

  image << text.to_s
  image << '</text>'
  image << '</svg>'

  image
end

While these two methods accomplish the same thing, they do it differently. However, each of them has a call to super. That will access a method of the same name in the parent class:

write_text() in Image

1
2
3
def write_text
  yield( 300, 100, 5, 5 )
end

All that Image’s write_text() is doing is passing values back to the child method that called it. Each child method’s super call defines variables in the block: width, height, left, and top in the line that says super() do |width, height, left, top|. The yield call in Image defines those values to be width = 300, height = 100, left = 5, and top = 5. To Image’s write_text(), however, it’s just throwing some numbers at the child method: it doesn’t know that the child method is going to use those numbers to define the dimensions of the image and the starting position of its text.

The definition of write_text() in Image is very simple, but it doesn’t have to be that way. There could be a great deal of calculation involved in determining what values the child methods should use for whichever variables are being passed to it. The parent class’s shared method is a place to store shared logic and shared data; let the child classes do the specific stuff with that general data.

Shared Variables Sans yield

Values can also be reached in the child class methods without using yield if instance variables are defined in the parent method:

write_text() in Image

1
2
3
4
5
def write_text
  @width = 300
  @height = 100
  @left = @top = 5
end

Then write_text() in both Raster and Vector would be able to access Image’s @width, @height, @left, and @top, as defined in its write_text().

Multiple Calls to super

Sometimes it may be useful to let the parent class’s method do some work, then the child class’s, then the parent’s again. In this case, you can call super and yield as many times as necessary:

Method in Child Class

1
2
3
4
5
6
7
8
9
def my_method
  return1 = super( 'a string' ) do |my_var|
    # Do some stuff with my_var
  end

  return2 = super( ['array', 'of', 'strings'] ) do |my_other_var|
    # Do some stuff with my_other_var
  end
end

Method in Parent Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def my_method( value )
  if value.class == String
    yield( 'this is my_var' )
  elsif value.class == Array
    yield( 'this is my_other_var' )
  end

  # Checks to see if value can work with the <<
  # method, which both Array and String objects
  # have, and if so, adds another string to it;
  # this value is returned since it's the last
  # line of the method
  value << ' appended to value' if value.respond_to?( :<< )
end

yield Returns Data to super

The child class method can also throw data back to the parent class method by returning a value from the super block:

Method in Child Class

1
2
3
4
5
def my_method
  value_from_parent = super do |my_var|
    'this gets thrown back to the parent class method'
  end
end

Method in Parent Class

1
2
3
4
def my_method
  value_from_child = yield( 'becomes my_var' )
  'this gets thrown back to the child class method'
end

More Information