Ruby: Yield Block from a Block

Blocks and yields in Ruby

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

Ruby: the yield inside of a block

The block is passed similarly to the argument of that function. This can be specified explicitly, like so:

class Test
def my_each(&block)
"abcdeabcabc".scan("a") do |x|
puts "!!! block"
yield x
# Could be replaced with: block.call(x)
end
end
end

Technically, it's exactly the same (puts put in there for clarification), its presence is not checked the way it is usually done for arguments. Should you forget to give it a block, the function will halt on the first yield it has to execute with exactly the same LocalJumpError (at least, that's what I get on Rubinius). However, notice the "!!! block" in the console before it happens.

It works like that for a reason. You could check whether your function is given a block, if it is specified explicitly as above, using if block, and then skip the yields. A good example of that is a content_tag helper for Rails. Calls of this helper can be block-nested. A simplistic example:

content_tag :div do
content_tag :div
end

...to produce output like:

<div>
<div></div>
</div>

So, the block is executed "on top" (in terms of call stack) of your method. It is called each time a yield happens as some sort of function call on a block. It's not accumulated anywhere to execute the block afterwards.

UPD:

The Enumerator returned by many eaches is explicitly constructed by many iterators to save context of what should happen.

It could be implemented like this on my_each:

class Test
def my_each(&block)
if block
"abcdeabcabc".scan("a") { |x| yield x }
else
Enumerator.new(self, :my_each)
end
end
end

Can you yield inside a code block in Ruby?

The code you have there does in fact not work, because there is no block to yield to.

You will get a LocalJumpError, which gets swallowed by the catch-all rescue, and thus it will look like there is a problem with the file, when in fact, there is actually a programming error. (Teachable moment: never ever do a blanket catch-all rescue, always rescue only exactly those exceptions you want to handle.)

Yielding inside a block

Proper functioning of the included Enumerable interface's methods requires that the class implement an each method that yields successive values. The implementation in your sample code effectively delegates this logic to that of Array's each method (since @players is an Array).

Replacing yield with return would result in no values being provided, and bypassing any code block that was passed in.

You could experiment with the each implementation to verify this.

Ruby Blocks (Yield)

What you're failing to realise is that yield can return a value. The last executed statement in a block is the returned value.

So you can take the results from each yield call and add it to your resulting array.

Then, have the resulting array as your return value from your new_map method.

def new_map(a)
new_array = []
a.each do |item|
# invoke the block, and add its return value to the new array.
new_array << yield(item)
end
new_array
end

Why is yield not passing the result to block (Rails)?

In your controller you need to have a variable for the result.

def create
Command.run(params) do |result|
if result
render json: { message: 'Successfully processed request' }, status: :success
else
render json: { message: 'Encountered an error' }, status: :bad_request
end
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end

(EDIT)

Also, you are calling the class method which call the instance method. You have to pass the block from the calling code to the instance method you are calling.

def self.run(params, &block)
new.run(params, &block)
end

Ruby blocks - yield {puts} vs. puts yield

In the first snippet:

  • the method yields to the block
  • the block prints the string "anything"
  • the method returns the return value of the blocks, which is the return value of puts, which is nil

In the second snippet:

  • the method yields to the block
  • the block returns the string "anything"
  • the method prints the return value of the block
  • the method returns the value of puts, which is nil

Having said that, it depends on what you want to achieve with the block the methods yields to.

In the first snippet, it is more about yielding the control to the block and the block does whatever it needs to do. The return value might not be relevant, or it might be relevant as the return value of the method.

In the second snippet, the block serves as a way to calculate a value that will then be used by the method.

To illustrate the two cases, here's two examples:

def on_create
yield created_user
end

on_create do |user|
send_welcome_email(user)
end
def send_email(address)
body = yield
EmailService.deliver(address, body)
end

send_email('alice@example.com') do
"Hello Alice, ..."
end

Ruby return in yield block called from a method with ensure

It's a Ruby's feature of "unwinding the stack" from blocks. How your return works step by step:

  1. You return 3 from bar block. return_value = 3 and Ruby marks that it is a return value from block, so it should unwind the stack and return 3 from parent function. It would not return to foo at all if there was no ensure section. It is very important difference between returning from functions and from blocks.
  2. But Ruby always executes ensure, and there is one more return in ensure section of foo.
  3. After return 1 in ensure section of foo, return_value is 1. It is not a value from block, so Ruby "forgets" about previous return 3 and forgets to return it from bar.
  4. It returns 1 from foo and 4 from bar.

Moreover, if you write next 3 instead of return 3 in the block - it will return to foo after yield and execute puts "s = #{s}"; return 2 even without the ensure block. This is a magical Ruby feature for iterators and enumerators.



Related Topics



Leave a reply



Submit