ReturnBang is allows you to perform non-local exits from your methods. One potential use of this is in a web framework so that a framework-provided utility methods can jump directly back to the request loop.
Since providing just non-local exits is insufficient for modern Ruby
development, full exception handling support is also provided via raise!, rescue! and ensure!. This exception
handling support completely bypasses Ruby’s strict begin; rescue;
ensure; return
handling.
require ‘return_bang’ gives you a module you may include only in your application or library code. require ‘return_bang/everywhere’ includes ReturnBang in Object, so it is only recommended for application code use.
#return_here is used to designate where execution should be resumed. Return points may be arbitrarily nested. return! resumes at the previous resume point, return_to returns to a named return point.
raise! is used to indicate an exceptional situation has occurred and you would like to skip the rest of the execution.
rescue! is used to rescue exceptions if you have a way to handle them.
ensure! is used when you need to perform cleanup where an exceptional situation may occur.
include ReturnBang def framework_loop loop do return_here do # setup this request ensure! do # clean up this request end rescue! FrameworkError do # display framework error end rescue! do # display application error end user_code end end end def user_code user_utility_method other_condition = some_more code return! if other_condition # rest of user method end def user_utility_method raise! "there was an error" if some_condition # rest of utility method end
Adds an ensure block that will be run when exiting this #return_here block.
ensure! blocks run in the order defined and can be added at any time. If an exception is raised before an ensure! block is encountered, that block will not be executed.
Example:
return_here do ensure! do # this ensure! will be executed end raise! "uh-oh!" ensure! do # this ensure! will not be executed end end
# File lib/return_bang.rb, line 217 def ensure! &block _return_bang_stack.push [:ensure, block] end
Raises an exception like Kernel#raise.
ensure! blocks and rescue! exception handlers will be run as the exception is propagated up the stack.
# File lib/return_bang.rb, line 227 def raise! *args Thread.current[:current_exception] = _make_exception args type, = _return_bang_stack.first _, final = _return_bang_stack.shift if type == :return frames = _return_bang_stack.dup _return_bang_stack.clear _return_bang_cleanup frames final.call if final end
Rescues exceptions
raised by raise! and yields the exception
caught to the block given.
If no exceptions are given, StandardError is rescued (like the rescue keyword).
Example:
return_here do rescue! do |e| puts "handled exception #{e.class}: #{e}" end raise! "raising an exception" end
# File lib/return_bang.rb, line 260 def rescue! *exceptions, &block exceptions = [StandardError] if exceptions.empty? _return_bang_stack.push [:rescue, block, exceptions] end
Returns to the last return point in the stack. If no return points have
been registered a NonLocalJumpError is raised.
value
is returned at the registered return point.
# File lib/return_bang.rb, line 271 def return! value = nil raise NonLocalJumpError, 'nowhere to return to' if _return_bang_stack.empty? _, continuation, = _return_bang_stack.reverse.find do |type,| type == :return end _return_bang_unwind_to continuation continuation.call value end
Registers a return point to jump back to. If a name
is given
#return_to can jump here.
# File lib/return_bang.rb, line 288 def return_here name = nil raise ArgumentError, "#{name} is already registered as a return point" if _return_bang_names.include? name value = callcc do |cc| _return_bang_names[name] = _return_bang_stack.length if name _return_bang_stack.push [:return, cc] begin yield ensure _return_bang_unwind_to cc end end if exception = Thread.current[:current_exception] then Thread.current[:current_exception] = nil raise exception end # here is where the magic happens unwind_to = Thread.current[:unwind_to] return! value if unwind_to and _return_bang_stack.length > unwind_to return value end
Returns to the return point name
. value
is
returned at the registered return point.
# File lib/return_bang.rb, line 321 def return_to name, value = nil unwind_to = _return_bang_names.delete name raise NonLocalJumpError, "return point :nonexistent was not set" unless unwind_to Thread.current[:unwind_to] = unwind_to return! value end