How to use Ruby blocks

Asked

Viewed 647 times

9

I’m having a hard time understanding blocks in Ruby. Could someone explain to me why we use it?

2 answers

9


You know closures (in English)? It’s the same thing. You can see more on another question done here on the site. It is not the same language but the idea is the same. The Java 8 has the same feature - although with little details a little different.

The block is a code snippet that, roughly speaking, can be linked to a variable. And obviously can be passed as a parameter. Then you define a code at a certain time but your invocation will only happen at another time, at another place.

This is often used to personalize behavior. So you may have some method that needs to perform a task, but it doesn’t know how to perform all the parts. So it can be more flexible. These parts will be defined by the other part of the application code. In general, this method takes what it should perform by parameter or takes it through a class member or even by calling another method that provides the desired chunk.

Of course, during the execution, we’re not talking about the source code being transported back and forth. Once compiled this code snippet will be available in a memory address and it is actually this address that will be used to indicate the snippet. But this is implementation detail, you don’t need to know this to use.

Even more. The block has this address and a data structure that stores possible variables that can have their values stored there. Since these blocks are closures, they can contain the values of variables declared in the scope where the block was defined. Thus these values will be available when the block is executed effectively, even if the variable that gave rise to the value is no longer available.

So deep down what’s called block code is an object containing enclosed variable values and the address of a compiled code snippet.

These blocks can be considered nameless methods. Since the method has no name, its execution is done through the variable where it is stored.

So:

codigo = {|x| x + 1 }

This is a method you receive has a parameter called x´ e dentro dele ele pega o valor passado por x` and sum 1, this is the result of the code block.

Another syntax:

do |texto|
    puts texto
end

A practical example:

[1,2,3].each {|x| puts "(#{x})" }

This will print the 2 numbers in parentheses. Note that scanning the data collection is the method each that you don’t need to know how it works, is an existing method that you just need to know that you can call to execute something in all elements of any collection and that the parameter that this method expects is a block of code. This block will tell you what the method should perform on each element.

Of course it is possible to get the same result in another way, but you may need more code, you may forget to handle all the necessary aspects by doing it manually, especially when you have to do more complicated things. Then you encapsulate a logic in a method and let it do something specific that will be defined by the block.

Another example:

def metodo(exec)
    exec.call "Vinicius"
end
codigo = proc do |nome|
    puts "Ola, #{nome}"
end
#ainda não executou nada aqui
metodo(codigo)
#agora executou a impressão do nome

Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.

Realize that the method metodo You don’t know what he’s going to do, you just know he’s going to do something to be set by the received parameter. The definition was made out of it, independently.

Now look with a closed variable:

def metodo(exec)
    exec.call "Vinicius"
end
idade = 20
codigo = proc do |nome|
    puts "Ola, #{nome}, voce tem #{idade} anos"
end
metodo(codigo)

Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.

The code is just an example. In this case the name will be set inside the metodo but age is already going along with the codigo. So nome is block parameter, idade is closed variable.

A general explanation is this. Depending on your programming learning baggage, it may not be as clear yet but read more about the same subject that you are consolidating and clarifying. Of course there may be specific aspects that you don’t understand and you may open up new questions.

  • Thank you very much for your answer. It was very enlightening. I managed to understand the concept of closures, sometimes I get confused with some uses of Blocks in Rails. For example the respond_to method, which waits for a block. In this case, the method definition, already has the programming defined for the answer, but, it does not know the answer format you want, so it waits for a block of code, which informs?

  • I don’t know Ror but this is it, the respond_to is just for you to "set up" an action to be executed when something happens. That is, the Ror knows that he has to do something there but he does not know the exact procedure to perform, you the creator of the application will tell which code should be executed. It can be from taking a simple field of the model or doing a complex processing, probably using various model information.

  • otima explicação @bigown

2

You can create blocks using the keyword lambda.

diga_ola = lambda {|nome| puts "Olá, #{nome}!" }
diga_ola.call 'Fulano' # "Olá, Fulano!"

This allows you to pass a block to a method:

def executar(bloco)
  # faça algo
  bloco.call
  # faça mais algo
end

bloco = lambda { puts 'foo' }
executar bloco

But please don’t use it that way in Ruby, because there is a syntax sugar which makes everything more elegant, using the keyword yield.

def executar
  puts 'Antes do bloco'
  yield 'Fulano'
  puts 'Depois do bloco'
end

executar do |nome|
  puts "Olá, #{nome}!" # "Olá, Fulano!"
end

# ou
# executar {|nome| puts "Olá, #{nome}!" }

yield executes the block, and you can pass parameters to it. If you want to access the block as a variable, you can also add &block as the last method parameter:

def cumprimente(cumprimento, &block)
  nome = block.call
  puts "#{cumprimento}, #{nome}"
end

cumprimente 'Olá' do
  return 'Fulano'
end

block_given? will return if a block has been passed as parameter:

def do_something
  puts 'Hello'
  if block_given?
    yield
  end
  puts 'Bye
end

There’s actually a difference between a lambda, one proc and a block, but this is complex and I will not try to explain here.

Ruby blocks are powerful, and multiple libraries and frameworks use it to create Domain Specific Languages (DSL). (Ref1, Ref2).

An example of this is Rails Migrations:

create_table :pessoas do |t|
  t.string :nome, null: false
  t.integer :idade
  t.belongs_to :organizacao, foreign_key: true
end

The above execution is a normal method (create_tableis a method). But the syntax was so well thought out that it doesn’t look like Ruby, it looks like a "language" to create Migrations, almost a poetry.

Blocks are also used to functionally program in Ruby:

numeros = [1, 2, 3, 4, 5]
dobro = numeros.map {|n| n*2 } # [2, 4, 6, 8, 10]
  • For the case of the create_table method of Rails, block is used because the table fields are dynamic, correct?

  • @Sorry Vinicius, I didn’t understand your question. You can make it clear?

  • In the create_table method, it is necessary to use block as parameter to indicate the fields of the database table, correct? and variable that is passed as parameter to the block would be an instance?

  • Exactly. t, which is received as parameter in the block, is an instance representing the database table. string, integer, etc. are methods of this instance. At the end of the block execution, Rails uses the data you entered to generate SQL (CREATE TABLE...)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.