So I had this code in a Rails project using Devise for authentication:
before_filter :require_app_login
def require_app_login
logger.debug("---- AAA ----")
authenticate_user!
logger.debug("---- BBB ----")
rescue Exception => e
logger.debug("---- CCC ----")
raise e
end
In my log file, I was seeing non-authenticated users getting “AAA” but not “BBB” or “CCC”. I couldn’t figure out how Ruby could unwind the stack without printing one of those or the other. It turns out that Warden (which Devise uses) employs a little-used Ruby feature called throw
/catch
. It is not a synonym for raise
/rescue
! In fact it’s a lot like GOTO
. You use catch
like this:
catch :my_label do
# run some code
end
Anywhere in that block of code, including further down the stack, you can say throw :my_label
to cause the catch
to immediately return. The stack unwinds with no exception handling. Amazing!
Note that although your rescue
s get skipped, if you add an ensure
block, that will still get called. I couldn’t find this documented anywhere, but that’s the behavior I observe with Ruby 1.9.3, and it makes sense. I guess the moral is that unless you use ensure
, you shouldn’t assume that you’ve covered all the ways for your code to unwind.