1

I've never needed to do this in Ruby, but my boss, being a C programmer, has put to me a problem that I honestly can't provide an elegant solution for in Ruby, without basically doing it in the C way (tracking a variable an using two "break" statements).

We have a situation like this (parsing XML):

(1..1000).each do |page|
  fetch_page(page).results.each do |results|
    do_something_with_results!
    break if results[:some_value] > another_value # this needs to exit BOTH blocks
  end
end

The only way I could do this is in a way that I would not describe as being very Ruby-like, and more a C way of thinking. Something like:

(1..1000).each do |page|
  should_break = false
  fetch_page(page).results.each do |results|
    do_something_with_results!
    if results[:some_value] > another_value
      should_break = true
      break
    end
  end
  break if should_break
end

That to me feels completely wrong and un-Ruby-like, but what's the functional approach?

d11wtq
  • 34,788
  • 19
  • 120
  • 195
  • Possible duplicate: http://stackoverflow.com/questions/1352120/how-to-break-outer-cycle-in-ruby – Jordan Running Jun 24 '11 at 04:32
  • 3
    While `throw` & `catch` are the Ruby keywords designed for this situation, first check if you can wrap this code in its own method and use `return` to break out. _Usually_ it gives more readable/testable code. – captainpete Jun 24 '11 at 04:40
  • @Jordan: the possible duplicate discusses only procedural programming styles of fixing the problem. – Andrew Grimm Jun 24 '11 at 04:43

2 Answers2

4
catch (:break) do
  (1..1000).each do |page|
    fetch_page(page).results.each do |results|
      do_something_with_results!
      throw :break if results[:some_value] > another_value # this needs to exit BOTH blocks
    end
  end
end

EDIT: @CaptainPete's comment above is spot on. If you can make it into a function, it has significant side benefits (unit testing being the primary one).

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • Wow, I remember reading this when I read a ruby book front to back, but never have I actually seen it used :) Thanks. – d11wtq Jun 24 '11 at 04:41
-1

It depends on your circumstances.

If the data sets aren't too large, you could do

results = (1..1000).map{|page_number| fetch_page(page_number).results}.flatten(1)
results.each do
  do_something_with_results!
  break if results[:some_value] > another_value # this needs to exit BOTH blocks
end

otherwise you'd have to do something to make it more lazy, such as

def each_result(page_numbers)
  page_numbers.each do |page_number|
    fetch_page(page_number).results.each do |result|
      yield result
    end
  end
end

and I'm sure there are many other ways for making something lazy.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338