Blocks and yields in Ruby

I am trying to understand blocks and yield and how they work in Ruby.

How is yield used? Many of the Rails applications I've looked at use yield in a weird way.

Can someone explain to me or show me where to go to understand them?


Solution 1:

Yes, it is a bit puzzling at first.

In Ruby, methods can receive a code block in order to perform arbitrary segments of code.

When a method expects a block, you can invoke it by calling the yield function.

Example:

Take Person, a class with a name attribute and a do_with_name method. When the method is invoked it will pass the name attribute to the block.

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end

Now you can invoke this method and pass an arbitrary code block.

person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end

Would print:

Got: Oscar

Notice the block receives as a parameter a variable called value. When the code invokes yield it passes as argument the value of @name.

yield( @name )

The same method can be invoked with a different block.

For instance to reverse the name:

reversed_name = ""

# Invoke the method passing a different block
person.do_with_name do |value| 
    reversed_name = value.reverse
end

puts reversed_name

=> "racsO"

Other more interesting real life examples:

Filter elements in an array:

 days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]  

 # Select those which start with 'T' 
 days.select do | item |
     item.match /^T/
 end

=> ["Tuesday", "Thursday"]

Or sort by name length:

 days.sort do |x,y|
    x.size <=> y.size
 end

=> ["Monday", "Friday", "Tuesday", "Thursday", "Wednesday"]

If the block is optional you can use:

yield(value) if block_given?

If is not optional, just invoke it.

You can try these examples on your computer with irb (Interactive Ruby Shell)

Here are all the examples in a copy/paste ready form:

class Person 
    def initialize( name ) 
         @name = name
    end

    def do_with_name   # expects a block
        yield( @name ) # invoke the block and pass the `@name` attribute
    end
end


person = Person.new("Oscar")

# Invoking the method passing a block to print the value
person.do_with_name do |value|
    puts "Got: #{value}"
end


reversed_name = ""

# Invoke the method passing a different block
person.do_with_name do |value| 
    reversed_name = value.reverse
end

puts reversed_name



# Filter elements in an array:    
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]  

# Select those which start with 'T' 
days.select do | item |
    item.match /^T/
end



# Sort by name length:     
days.sort do |x,y|
   x.size <=> y.size
end

Solution 2:

In Ruby, methods can check to see if they were called in such a way that a block was provided in addition to the normal arguments. Typically this is done using the block_given? method but you can also refer to the block as an explicit Proc by prefixing an ampersand (&) before the final argument name.

If a method is invoked with a block then the method can yield control to the block (call the block) with some arguments, if needed. Consider this example method that demonstrates:

def foo(x)
  puts "OK: called as foo(#{x.inspect})"
  yield("A gift from foo!") if block_given?
end

foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)

Or, using the special block argument syntax:

def bar(x, &block)
  puts "OK: called as bar(#{x.inspect})"
  block.call("A gift from bar!") if block
end

bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)

Solution 3:

It's quite possible that someone will provide a truly detailed answer here, but I've always found this post from Robert Sosinski to be a great explanation of the subtleties between blocks, procs & lambdas.

I should add that I believe the post I'm linking to is specific to ruby 1.8. Some things have changed in ruby 1.9, such as block variables being local to the block. In 1.8, you'd get something like the following:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"

Whereas 1.9 would give you:

>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"

I don't have 1.9 on this machine so the above might have an error in it.

Solution 4:

I found this article to be very useful. In particular, the following example:

#!/usr/bin/ruby

def test
  yield 5
  puts "You are in the method test"
  yield 100
end

test {|i| puts "You are in the block #{i}"}

test do |i|
    puts "You are in the block #{i}"
end

which should give the following output:

You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100

So essentially each time a call is made to yield ruby will run the code in the do block or inside {}. If a parameter is provided to yield then this will be provided as a parameter to the do block.

For me, this was the first time that I understood really what the do blocks were doing. It is basically a way for the function to give access to internal data structures, be that for iteration or for configuration of the function.

So when in rails you write the following:

respond_to do |format|
  format.html { render template: "my/view", layout: 'my_layout' }
end

This will run the respond_to function which yields the do block with the (internal) format parameter. You then call the .html function on this internal variable which in turn yields the code block to run the render command. Note that .html will only yield if it is the file format requested. (technicality: these functions actually use block.call not yield as you can see from the source but the functionality is essentially the same, see this question for a discussion.) This provides a way for the function to perform some initialisation then take input from the calling code and then carry on processing if required.

Or put another way, it's similar to a function taking an anonymous function as an argument and then calling it in javascript.