Как вырваться из рубинового блока?

Вот 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

И вот 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

Я думал об использовании raise, но я пытаюсь сделать его общим, поэтому я не хочу помещать что-либо конкретное в Foo.

Ответ 1

Используйте ключевое слово next. Если вы не хотите перейти к следующему элементу, используйте break.

Когда в блоке используется next, это приводит к немедленному выходу блока, возвращению элемента управления методу итератора, который может начать новую итерацию, снова вызвав блок:

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

При использовании в блоке break передает управление из блока, из итератора, который вызывается блоком, и первого выражения, следующего за вызовом итератора:

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

И, наконец, использование return в блоке:

return всегда возвращает возвращающий метод, независимо от того, насколько глубоко он вложен в блоки (за исключением случая 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

Я хотел просто быть в состоянии вырваться из блока - вроде как прямое переключение, не связанное с циклом. На самом деле, я хочу разбить блок, который находится в цикле, не прерывая цикл. Для этого я сделал блок одноэлементным циклом:

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

Надеюсь, что это поможет следующему гуглеру, который приземляется здесь на основе темы.

Ответ 3

Если вы хотите, чтобы ваш блок возвращал полезное значение (например, при использовании #map, #inject и т.д.), next и break также принимают аргумент.

Рассмотрим следующее:

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

Эквивалент с помощью 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

Конечно, вы всегда можете извлечь логику, необходимую в метод, и вызвать это изнутри вашего блока:

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

Ответ 4

используйте ключевое слово break вместо return

Ответ 5

Возможно, вы можете использовать встроенные методы для поиска определенных элементов в массиве вместо each -ing targets и делать все вручную. Несколько примеров:

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"

В одном примере можно было бы сделать что-то вроде этого:

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

Ответ 6

next и break кажутся правильными в этом упрощенном примере!

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

вывод: 1 3 4 5 6 7 8

Ответ 7

Для того, чтобы выйти из рубинового блока просто использовать return ключевое слово return if value.nil?, return if value.nil?