Roll Your Own respond_to

While refactoring some ruby code the other day I found myself wanting respond_to-like functionality. Specifically, I was calling a function that would have one of several possible outcomes, and I wanted to handle each in a clean way. Specifically, I wanted syntax like this:

1
2
3
4
5
6
7
8
zebra array do |row|
  row.even do |element|
    puts "even #{element}"
  end
  row.odd do |element|
    puts "odd #{element}"
  end
end

I put together a bit of code for doing this, called Multiblock. You could write zebra, with a Multiblock, like so:

1
2
3
4
5
6
def zebra(list)
  list.each_with_index do |element, index|
    odd_or_even = [:even, &odd][index % 2]
    yield Multiblock[odd_or_even, element]
  end
end

Here’s the source code for Multiblock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Multiblock
  def self.[](*args)
    new *args
  end

  def initialize(*args)
    _expect *args unless args.empty?
  end

  def [](*args)
     _expect *args
     self
  end

  def method_missing(name, *rest)
    if !@matched
      @matched = [@waiting_for, :else].include? name
       if @waiting_for == name
          @result = yield *@args
       elsif :else == name
         @result = yield @waiting_for, *@args
       end
     end

      @result
  end

  private

  def _expect(waiting_for, *args)
    @waiting_for = waiting_for.to_sym
    @args = args
    @result = nil
    @matched = false
  end
end

Filed in: Languages, Tips, Tools


Leave a Reply