Skipping rescue blocks in Ruby

2012-07-02

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 rescues 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.

blog comments powered by Disqus Prev: Git: Compare two branches Next: Postgres string_agg Function