Como sair de um bloco de rubi?

420

Aqui está Bar#do_things:

class Bar   
  def do_things
    Foo.some_method(x) do |x|
      y = x.do_something
      return y_is_bad if y.bad? # how do i tell it to stop and return do_things? 
      y.do_something_else
    end
    keep_doing_more_things
  end
end

E aqui está Foo#some_method:

class Foo
  def self.some_method(targets, &block)
    targets.each do |target|
      begin
        r = yield(target)
      rescue 
        failed << target
      end
    end
  end
end

Pensei em usar raise, mas estou tentando torná-lo genérico, então não quero colocar nada específico Foo.

user169930
fonte

Respostas:

747

Utilizar a palavra-chave next. Se você não deseja continuar para o próximo item, use break.

Quando nexté usado dentro de um bloco, ele faz com que o bloco saia imediatamente, retornando o controle ao método do iterador, que pode então iniciar uma nova iteração invocando o bloco novamente:

f.each do |line|              # Iterate over the lines in file f
  next if line[0,1] == "#"    # If this line is a comment, go to the next
  puts eval(line)
end

Quando usado em um bloco, breaktransfere o controle para fora do bloco, para fora do iterador que invocou o bloco e para a primeira expressão após a invocação do iterador:

f.each do |line|             # Iterate over the lines in file f
  break if line == "quit\n"  # If this break statement is executed...
  puts eval(line)
end
puts "Good bye"              # ...then control is transferred here

E, finalmente, o uso de returnem um bloco:

return sempre faz com que o método envolvente retorne, independentemente de quão profundamente aninhado dentro de blocos seja (exceto no caso de lambdas):

def find(array, target)
  array.each_with_index do |element,index|
    return index if (element == target)  # return from find
  end
  nil  # If we didn't find the element, return nil
end
JRL
fonte
2
obrigado, mas o próximo apenas passa para o próximo item da matriz. é possível sair?
User169930
Você deve ligar em seguida com o valor de retorno. "def f; x = rendimento; coloca x; fim" "f faz os próximos 3; coloca" o "; fim" Isso imprime 3 (mas não "o") no console.
Marcel Jackwerth
5
next, break, return, Você não pode comparar
finiteloop
Adicionei uma resposta expandindo o comentário que @MarcelJackwerth adicionou sobre o uso nextou breakcom um argumento.
Tyler Holien
8
Há também uma palavra-chave chamada redo, que basicamente apenas move a execução de volta ao topo do bloco na iteração atual.
precisa saber é o seguinte
59

Eu queria apenas ser capaz de sair de um bloco - como se fosse um avanço para a frente, não muito relacionado a um loop. Na verdade, eu quero interromper um bloco que está em um loop sem terminar o loop. Para fazer isso, fiz do bloco um loop de uma iteração:

for b in 1..2 do
    puts b
    begin
        puts 'want this to run'
        break
        puts 'but not this'
    end while false
    puts 'also want this to run'
end

Espero que isso ajude o próximo pesquisador que chegar aqui com base na linha de assunto.

Don Law
fonte
4
Esta foi a única resposta que respondeu à pergunta como foi colocada. Merece mais pontos. Obrigado.
Alex Nye
Isso funciona da mesma forma para o intervalo e para o próximo. Se o false for alterado para true, o próximo permanecerá na aparência e a quebra ocorrerá.
G. Allen Morris III
39

Se você quiser que o seu bloco para retornar um valor útil (por exemplo, quando se usa #map, #injectetc.), nexte breaktambém aceitar um argumento.

Considere o seguinte:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    if x % 3 == 0
      count + 2
    elsif x.odd?
      count + 1
    else 
      count
    end
  end
end

O equivalente usando next:

def contrived_example(numbers)
  numbers.inject(0) do |count, x|
    next count if x.even?
    next (count + 2) if x % 3 == 0
    count + 1
  end
end

Obviamente, você sempre pode extrair a lógica necessária em um método e chamar isso de dentro do seu bloco:

def contrived_example(numbers)
  numbers.inject(0) { |count, x| count + extracted_logic(x) }
end

def extracted_logic(x)
  return 0 if x.even?
  return 2 if x % 3 == 0
  1
end
Tyler Holien
fonte
1
Obrigado pela dica com o argumento para break!
rkallensee
2
você poderia por favor atualize w / um exemplo específico usando break(provavelmente apenas substituir um de seus nextcom break..
Mike Graf
Uma coisa muito interessante. break somethingfunciona, funciona, break(something)mas true && break(somehting)gera um erro de sintaxe. Apenas para sua informação. Se a condição for necessária, então ifou unlessprecisa ser usada.
Akostadinov 27/10/16
21

use a palavra-chave em breakvez dereturn

AShelly
fonte
8

Talvez você possa usar os métodos internos para localizar itens específicos em uma matriz, em vez de each-ing targetse fazer tudo manualmente. Alguns exemplos:

class Array
  def first_frog
    detect {|i| i =~ /frog/ }
  end

  def last_frog
    select {|i| i =~ /frog/ }.last
  end
end

p ["dog", "cat", "godzilla", "dogfrog", "woot", "catfrog"].first_frog
# => "dogfrog"
p ["hats", "coats"].first_frog
# => nil
p ["houses", "frogcars", "bottles", "superfrogs"].last_frog
# => "superfrogs"

Um exemplo seria fazer algo assim:

class Bar
  def do_things
    Foo.some_method(x) do |i|
      # only valid `targets` here, yay.
    end
  end
end

class Foo
  def self.failed
    @failed ||= []
  end

  def self.some_method(targets, &block)
    targets.reject {|t| t.do_something.bad? }.each(&block)
  end
end
August Lilleaas
fonte
2
Não adicione métodos arbitrários como esse à classe Array! Isso é realmente uma prática ruim.
Mrbrdo 25/10/12
3
Rails faz isso, então por que ele não pode?
Wberry
2
@wberry Isso não quer dizer que necessariamente deva . ;) Em geral, porém, é melhor evitar as classes principais dos patches de macaco, a menos que você tenha uma boa razão (por exemplo, adicionar algumas funcionalidades generalizáveis muito úteis que muitos outros códigos acharão úteis). Mesmo assim, ande com leviandade porque, uma vez que uma classe é fortemente consertada por macacos, é fácil para as bibliotecas começarem a andar umas sobre as outras e causar um comportamento extremamente estranho.
blm768
2

nexte breakparece fazer a coisa correta neste exemplo simplificado!

class Bar
  def self.do_things
      Foo.some_method(1..10) do |x|
            next if x == 2
            break if x == 9
            print "#{x} "
      end
  end
end

class Foo
    def self.some_method(targets, &block)
      targets.each do |target|
        begin
          r = yield(target)
        rescue  => x
          puts "rescue #{x}"
        end
     end
   end
end

Bar.do_things

saída: 1 3 4 5 6 7 8

G. Allen Morris III
fonte
2
break termina imediatamente - next continua para a próxima iteração.
Ben Aubin
-3

Para sair de um bloco de rubi, basta usar a returnpalavra-chave return if value.nil?

Kiry Meas
fonte
2
Não returnsai da função?
ragerdl