rubyruby-block

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

  • 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