Uso do yield

Boa noite caros, estou começando a programar em Ruby agora e logo de início dei de cara com o comando yield, li a respeito, fiz alguns teste e entendi bem a sua funcionalidade, mas a minha grande dúvida está relacionada a sua verdadeira utilidade, vocês conseguiriam me dar exemplos mais complexos dos que existem por aí em qualquer artigo sobre yield? Até agora, todos os exemplos que eu vi poderiam simplesmente ser trocados para um método ou um bloco diretamente dentro do método.

Alguns métodos interessantes que usam blocos (yields) sem ser os clássicos das collections

ActiveRecord::Base do @post.save @post.user.increment(:posts_count, 1) end
Tudo que tiver dentro do bloco acima vai rodar em uma única transação, que no início é aberta e no final commitada, se ocorrer exceção dentro do bloco ele faz rollback

File.open("arquivo.txt", 'w') do |file| file.puts "1ª linha" file.puts "2ª linha" end
O arquivo é aberto e fechado automaticamente, evitando os try / catch / finally horríveis de Java que sempre acaba alguém esquecendo de fechar

GUJ::Application.routes.draw do resources :forums do resources :topics do resources :posts do end end end end
Ex de DSL do Rails para configurar rotas REST aninhadas

Como vai DiegoCC? Que legal que está estudando Ruby cara! Manda ver.

Bom, realmente o yield não é tecnologia alienígena e certamente será sempre possível substituí-lo por cosias “normais” (por assim dizer, não me entenda mal).
Mas o que acho legal nele é a legibilidade e facilidade que dá para implementar certas coisas. Veja por exemplo a seguinte funcionalidade implementada com o uso do yield tirada do projeto standalone_migrations.

require 'tasks/standalone_migrations'

StandaloneMigrations::Configurator.environments_config do |env|

  env.on "my_custom_config" do |current_custom_config|
    p current_custom_config
    # => the values on your current "my_custom_config" environment
    nil
  end

end

Esse código permite a alteração das configurações internas de um projeto (não importa pro exemplo saber exatamente o que está acontencendo) em tempo de execução. Quem implementou a api não sabe quais configurações serão utilizadas em tempo de execução, nem sequer quem está usando a api. Mas a forma como isso foi implementado permite ao usuário da api deixar para descobrir essas configurações somente em tempo de execução e ainda assim realizar alguma alteração. Veja um exemplo um pouco mais concreto, que foi na verdade a principal motivação dessa implementação, que é alterar as configurações de banco de dados de projetos hosteados pelo heorku:

require 'tasks/standalone_migrations'

StandaloneMigrations::Configurator.environments_config do |env|

  env.on "production" do

    if (ENV['DATABASE_URL'])
      db = URI.parse(ENV['DATABASE_URL'])
      return {
        :adapter  => db.scheme == 'postgres' ? 'postgresql' : db.scheme,
        :host     => db.host,
        :username => db.user,
        :password => db.password,
        :database => db.path[1..-1],
        :encoding => 'utf8'
      }
    end

    nil
  end

end

E finalmente o trecho de implementação onde o yield é realmente invocado:

    def on(config_key)
      if @configurations[config_key] && block_given?
        @configurations[config_key] = yield(@configurations[config_key]) || @configurations[config_key]
      end
      @configurations[config_key]
    end

fonte: https://github.com/thuss/standalone-migrations/blob/master/lib/standalone_migrations/configurator.rb

No fim das contas o grande valor adicionado aqui pelo yield foi a forma como ficou limpo/fácil adiar para o último momento (execução em host específico) a decisão sobre qual configuração será usada para conexão com o banco de dados.

Que achou desse uso DiegoCC, espero ter ajudado e vamos discutir a respeito! =)

Fala ricardo.valeriano, vamos ver se eu entendi bem. O exemplo do código que você passou serve para passar a configuração do banco para o módulo/library/whatever sem que seja preciso em algum momento invocar um arquivo de configuração para obter esses dados, certo?

Se for isso mesmo, eu consegui captar bem a utilidade dele e digo mais, achei genial essa implementação! Eu venho do PHP e por mais modular que façamos os códigos sempre temos o hábito de dar requires em arquivos de configuração (mesmo que seja indiretamente por um ponto padrão de algum framework).

Obrigadão pela respostas ricardo.valeriano e o victorcosta.

com esse exemplo que o Valeriano deu, você ainda pode adicionar um outro ambiente de config sem se preocupar com a implementação da config:

require 'tasks/standalone_migrations'  
  
StandaloneMigrations::Configurator.environments_config do |env|  
  
  env.on "production" do  
  
    if (ENV['DATABASE_URL'])  
      db = URI.parse(ENV['DATABASE_URL'])  
      return {  
        :adapter  => db.scheme == 'postgres' ? 'postgresql' : db.scheme,  
        :host     => db.host,  
        :username => db.user,  
        :password => db.password,  
        :database => db.path[1..-1],  
        :encoding => 'utf8'  
      }  
    end  
  
    nil  
  end

  env.on "development" do  
  
    if (ENV['DATABASE_URL'])  
      db = URI.parse(ENV['DATABASE_URL'])  
      return {  
        :adapter  => db.scheme == 'sqlite' ? 'sqlite' : db.scheme,  
        :host     => db.host,  
        :username => db.user,  
        :password => db.password,  
        :database => db.path[1..-1],  
        :encoding => 'iso-8859-1'  
      }  
    end  
  
    nil  
  end    
  
end

E em tempo de execução, os blocos iriam ser executados dependendo do environment.
Talvez estaria aí a maior importância do yield Tou certo?

Fala @leandronsp, belezinha man? :smiley:

Então, faz sentido sim a sua conclusão. É que nesse caso específicamente (pelo menos até onde sei) só há o postgre disponível lá no Heroku, mas como eu disse, faz sentido.

@DiegoCC, o que eu realmente queria destacar com esse exemplo é a possibilidade que o uso de um bloco oferece de delegar parte do comportamento da aplicação para mais tarde. Nesse caso, quem escreveu o standalone_migrations não precisa saber todas as possibilidades possíveis de configuração relacionadas com a aplicação em tempo de execução, mas apenas se preocupar em criar um “ponto de extensão” por assim dizer. Quem quiser aumentar/adaptar/melhorar o funcionamento, precisa plugar um bloco a esse ponto de extensão, e através dele adicionar a funcionalidade desejada.