Reputation: 4777
Here is 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
And here is 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
I thought about using raise, but I am trying to make it generic, so I don't want to put anything any specific in Foo
.
Upvotes: 468
Views: 383165
Reputation: 1242
To break out from a ruby block simply use return
keyword return if value.nil?
next
.
next
terminates lambda, block, or proc it is in.break
terminates the method that yielded to the block or invoked the proc or lambda it is in.Credit to: Ruby block return, break, next
Upvotes: -4
Reputation: 231
You have four ways to unwind the stack in 'non-exceptional' ways: next
, break
, return
, and throw
.
next
will cause the block to return.
break
will cause the method that yielded to the block to return.
return
will cause the method where the block is defined to return.
throw
will traverse up the stack until it finds a catch
with a matching symbol, and cause that to return. This is much like a 'lightweight' exception for non-exceptional situations.
All of them can take a return value that will be returned by whatever they caused to return instead of the value their last expression which they would return normally.
Here are some examples:
def doSomething
puts "> doSomething"
yield
puts "< doSomething"
end
def withNext
puts "> withNext"
doSomething do
puts "> block"
puts "* NEXT! causes the block to return to doSomething"
next
puts "< block"
end
puts "< withNext"
end
def withBreak
puts "> withBreak"
doSomething do
puts "> block"
puts "* BREAK! causes doSomething to return to withBreak"
break
puts "< block"
end
puts "< withBreak"
end
def withReturn
puts "> withReturn"
doSomething do
puts "> block"
puts "* RETURN! causes withReturn to return"
return
puts "< block"
end
puts "< withReturn"
end
def withThrow
puts "> withThrow"
catch :label do
puts "> catch :label"
doSomething do
puts "> block 1"
doSomething do
puts "> block 2"
puts "* THROW! causes catch :label to return to withThrow"
throw :label
puts "< block 2"
end
puts "< block 1"
end
puts "< catch :label"
end
puts "< withThrow"
end
withNext
puts "* Done"
puts
withBreak
puts "* Done"
puts
withReturn
puts "* Done"
puts
withThrow
puts "* Done"
output:
> withNext
> doSomething
> block
* NEXT! causes the block to return to doSomething
< doSomething
< withNext
* Done
> withBreak
> doSomething
> block
* BREAK! causes doSomething to return to withBreak
< withBreak
* Done
> withReturn
> doSomething
> block
* RETURN! causes withReturn to return
* Done
> withThrow
> catch :label
> doSomething
> block 1
> doSomething
> block 2
* THROW! causes catch :label to return to withThrow
< withThrow
* Done
Upvotes: 2
Reputation: 77993
Use the keyword next
. If you do not want to continue to the next item, use break
.
When next
is used within a block, it causes the block to exit immediately, returning control to the iterator method, which may then begin a new iteration by invoking the block again:
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
When used in a block, break
transfers control out of the block, out of the iterator that invoked the block, and to the first expression following the invocation of the iterator:
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
And finally, the usage of return
in a block:
return
always causes the enclosing method to return, regardless of how deeply nested within blocks it is (except in the case of 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
Upvotes: 811
Reputation: 1042
next
and break
seem to do the correct thing in this simplified example!
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
output: 1 3 4 5 6 7 8
Upvotes: 2
Reputation: 54593
Perhaps you can use the built-in methods for finding particular items in an Array, instead of each
-ing targets
and doing everything by hand. A few examples:
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"
One example would be doing something like this:
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
Upvotes: 8
Reputation: 4951
If you want your block to return a useful value (e.g. when using #map
, #inject
, etc.), next
and break
also accept an argument.
Consider the following:
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
The equivalent using 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
Of course, you could always extract the logic needed into a method and call that from inside your block:
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
Upvotes: 42
Reputation: 1329
I wanted to just be able to break out of a block - sort of like a forward goto, not really related to a loop. In fact, I want to break of of a block that is in a loop without terminating the loop. To do that, I made the block a one-iteration loop:
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
Hope this helps the next googler that lands here based on the subject line.
Upvotes: 70