1

I would like to return a value from a yield in a Ruby code block. In other words, I would like to pass a value from inside the blocking into the block itself.

def bar
    puts "in block"
    foo = 1
    # The next line is wrong, 
    # but this is just to show that I want to assign a value to foo
    foo = yield(foo)
    puts "done block #{foo}"
end

puts "start"
bar do |shoop|
    puts "doing other stuff"
    shoop = 2
    puts "doing more stuff"
    # The following commented-out line would work, 
    # but I would rather not force the programmer 
    # to always put a variable at the end
    #shoop
end
puts "done"

What I would like to see for output is:

start
in block
doing other stuff
doing more stuff
done block 2
done

Is there any way to accomplish this without relying on the implicit return value at the end of a Ruby block? I believe this is possible, because I've seen this type of behavior in Sinatra code blocks before.

Seanny123
  • 8,776
  • 13
  • 68
  • 124

3 Answers3

3

You can implicitly return values using instance variables, like it is done in Rails:

def bar
  yield
  puts "The block has assigned @value to #{@value}"
end

bar do
  @value = 42
  nil # Do not return value intentionally
end

This code outputs:

The block has assigned @value to 42

UPDATE

Another nice option is to use helper methods, many frameworks do it too:

def bar
  def render(value)
    @value = value
  end

  yield
  puts "The block has rendered #{@value}"
end

bar do
  render 42
  nil # Do not return value intentionally
end

This code outputs:

The block has rendered 42

UPDATE 2

Please see an important addition from @Marek Lipka https://stackoverflow.com/a/19542149/203174

Community
  • 1
  • 1
Daniel Vartanov
  • 3,243
  • 1
  • 19
  • 26
1

I must warn you that @Daniel Vartanov answer applies only if self in the method and in the method call scope is the same. Otherwise, the block should be called in the context of self in method, that is:

def bar(&block)
  instance_eval(&block)
  puts "The block has assigned @value to #{@value}"
end
Marek Lipka
  • 50,622
  • 7
  • 87
  • 91
1

One can abuse Object#tap:

bar do |shoop|
  puts "doing other stuff"
  2.tap do
    puts "doing more stuff"
  end
end

Tap yield to the block, but results in the object that was tapped (in this case, 2).

It's a fun trick, and occasionally helpful, but a simple temporary variable usually is plainer and easier to read.

Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191