Comment sortir d'un bloc de rubis?


420

Voici 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

Et voici 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

J'ai pensé à utiliser raise, mais j'essaie de le rendre générique, donc je ne veux rien ajouter de spécifique Foo.

Réponses:


747

Utilisez le mot-clé next. Si vous ne souhaitez pas passer à l'élément suivant, utilisez break.

Lorsqu'il nextest utilisé dans un bloc, il provoque la fermeture immédiate du bloc, renvoyant le contrôle à la méthode itérateur, qui peut alors commencer une nouvelle itération en invoquant à nouveau le bloc:

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

Lorsqu'il est utilisé dans un bloc, breaktransfère le contrôle hors du bloc, hors de l'itérateur qui a appelé le bloc et vers la première expression suivant l'invocation de l'itérateur:

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

Et enfin, l'utilisation de returndans un bloc:

return provoque toujours le retour de la méthode englobante, quelle que soit sa profondeur d'imbrication dans les blocs (sauf dans le cas des 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

2
merci, mais next ne passe qu'à l'élément suivant dans le tableau. est-il possible de sortir?
user169930

Vous devez appeler ensuite avec la valeur de retour. "def f; x = yield; met x; end" "f fait les 3 suivants; met" o "; end" Ceci imprime 3 (mais pas de "o") sur la console.
Marcel Jackwerth

5
next, break, return, Vous ne pouvez pas comparer
finiteloop

J'ai ajouté une réponse développant le commentaire ajouté par @MarcelJackwerth sur l'utilisation nextou breakavec un argument.
Tyler Holien

8
Il existe également un mot clé appelé redo, qui ramène simplement l'exécution en haut du bloc dans l'itération actuelle.
Ajedi32

59

Je voulais juste pouvoir sortir d'un bloc - un peu comme un goto avant, pas vraiment lié à une boucle. En fait, je veux rompre un bloc qui est dans une boucle sans terminer la boucle. Pour ce faire, j'ai fait du bloc une boucle d'une itération:

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

J'espère que cela aidera le prochain googleur qui atterrit ici en fonction de la ligne d'objet.


4
C'était la seule réponse qui répondait à la question telle qu'elle était posée. Mérite plus de points. Merci.
Alex Nye

Cela fonctionne de la même manière pour la pause et la suivante. Si le faux est changé en vrai, le prochain reste dans l'apparence et la rupture éclate.
G. Allen Morris III

39

Si vous voulez que votre bloc pour retourner une valeur utile (par exemple lors de l' utilisation #map, #injectetc.), nextet breakégalement accepter un argument.

Considérer ce qui suit:

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

L'équivalent en utilisant 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

Bien sûr, vous pouvez toujours extraire la logique nécessaire dans une méthode et l'appeler de l'intérieur de votre bloc:

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

1
Merci pour l'astuce avec l'argument pour break!
rkallensee

2
pourriez-vous s'il vous plaît mettre à jour avec un exemple spécifique en utilisant break(remplacez probablement l'un des vôtres nextpar break..
Mike Graf

Une chose très intéressante. break somethingfonctionne, break(something)fonctionne mais true && break(somehting)donne une erreur de syntaxe. Juste FYI. Si la condition est nécessaire, alors ifou unlessdoit être utilisée.
akostadinov


8

Vous pouvez peut-être utiliser les méthodes intégrées pour rechercher des éléments particuliers dans un tableau, au lieu de each-ing targetset de tout faire à la main. Quelques exemples:

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"

Un exemple serait de faire quelque chose comme ceci:

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

2
N'ajoutez pas de méthodes arbitraires comme celle-ci à la classe Array! C'est vraiment une mauvaise pratique.
mrbrdo

3
Rails le fait, alors pourquoi pas lui?
wberry

2
@wberry Cela ne veut pas dire que cela devrait nécessairement . ;) En général, cependant, il est préférable d'éviter de corriger les classes principales de singe à moins que vous n'ayez une bonne raison (c'est-à-dire l'ajout de fonctionnalités généralisables très utiles que beaucoup d'autres codes trouveront utiles). Même alors, marchez légèrement car une fois qu'une classe est fortement corrigée par des singes, il est facile pour les bibliothèques de commencer à marcher les unes sur les autres et à provoquer un comportement extrêmement étrange.
blm768

2

nextet breaksemblent faire la bonne chose dans cet exemple simplifié!

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

sortie: 1 3 4 5 6 7 8


2
la pause se termine immédiatement - la prochaine passe à la prochaine itération.
Ben Aubin

-3

Pour sortir d'un bloc rubis, utilisez simplement un returnmot-clé return if value.nil?


2
Ne quitte pas returnla fonction?
ragerdl
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.