Wrapped exceptions in Ruby

Sometimes you want to raise an exception when a method fails but without losing information about an inner exception. Let me explain it with an example.

At Watu we have a method that causes a user to be indexed in our search engine. This method is called many times in different situations. One of those situations is when indexing most or all of our users. Recently, something failed and I got this exception:

undefined method `each' for #<String:0x10dab8fc>

Something that should be an array is actually a string. In this case the indexing method is correct, it’s just getting broken data. I want to fix the bug in the data generation, but to locate it, I need to know which user has broken data. I added a rescue clause to my indexing method to show me that data:

def index
  # ...
rescue
  raise "Error when indexing user #{self}"
end

Now I get something like:

Error when indexing user #<User:1234>

which allows me know that user 1234 has something wrong in its data. The problem is that now I have no idea what the issue is. I lost the information about trying to call each on a string.

The solution to this problem is exception wrapping (or nesting). You want the custom exception to wrap the other one so that you have both pieces of information. This, for example, exists in Java and if you search the web you’ll find ways on how to implement it in Ruby. Implementing this manually is not needed anymore since Ruby 2.1. Unfortunately, it’s a bit hidden and the tools haven’t caught up yet.

The secret lies in a new method in the class Exception called, cause. At the time of this writing it doesn’t even have documentation:

No documentation for the method Exception#cause

No documentation for the method Exception#cause

Using it is very straightforward. Just raise an exception in the rescue clause and the new exception will have the previous one as its cause. For example, in this case:

begin
  a = 1 / 0
rescue
  raise "Something went wrong"
end

you get two exceptions: the divided-by-zero wrapped inside one with the “Something went wrong” message.

The problem arrises that nobody seems to be using the causes of exceptions yet. If you run that in IRB, this is what you get:

RuntimeError: Something went wrong
        from (irb):4:in `rescue in irb_binding'
        from (irb):1
        from /Users/pupeno/.rvm/rubies/ruby-2.1.0/bin/irb:11:in `&lt;main&gt;'

But the exception’s cause is in there… hidden. If you catch the outer exception you can access its cause. For example:

begin
  begin
    a = 1 / 0
  rescue
    raise "Something went wrong"
  end
rescue => e
  puts e
  puts e.cause
end

would produce:

Something went wrong
divided by 0

The reason why it doesn’t produce something like that by default is because whatever IRB is using to print exceptions is ignoring the exception’s cause. Now we’ll have to wait until all the tools catch up with this new feature.

Well, we don’t actually have to wait. Aside from the fact that most of them are open source and you can fix them yourself, Ruby allows you to monkey patch so you can fix your own copy of these tools.

In my case I needed rake to print inner exceptions, so I wrote this monkey patch (which works for rake 10.1.1):

module Rake
  class Application
    def display_error_message(ex)
      trace "#{name} aborted!"
      display_exception(ex)
      trace "Tasks: #{ex.chain}" if has_chain?(ex)
      trace "(See full trace by running task with --trace)" unless options.backtrace
    end

    private

    def display_exception(ex, margin="")
      trace "#{margin}#{ex.message}"
      if options.backtrace
        trace "#{margin}#{ex.backtrace.join("\n#{margin}")}"
      else
        trace "#{margin}#{Backtrace.collapse(ex.backtrace).join("\n#{margin}")}"
      end
      if ex.respond_to?(:cause) && !ex.cause.nil? # Ruby < 2.1.0 doesn't have *cause*
        trace "#{margin}which was caused by:"
        display_exception(ex.cause, "#{margin} ")
      end
    end
  end
end

This is something that I would like to see in rake itself so I created an issue request (#253). Take a look at it to follow the development of this feature and hopefully, all tools will start displaying causes in one way or another.

Rake tasks for production

When I need to run something periodically on production, I always implement it as a rake tasks and install it as a cron job. Nevertheless there’s some setup to do in the task to have proper logging and error reporting.

This is the template I use for creating those tasks:

namespace :projectx do
  desc "Do something"
  task :something => :environment do
    if Rails.env.development?
      # Log to stdout.
      logger = Logger.new(STDOUT)
      logger.level = Logger::INFO # DEBUG to see queries
      ActiveRecord::Base.logger = logger
      ActionMailer::Base.logger = logger
      ActionController::Base.logger = logger
    else
      logger = ActiveRecord::Base.logger
    end

    begin
      logger.info "Doing something"
    rescue Exception => e
      HoptoadNotifier.notify(e)
      raise e
    end
  end
end

While in development mode, it outputs to the console for convenience.

Really resetting the database

When I start coding a Ruby on Rails project, I find myself modifying the migration files over and over. I know this is not the way they were intended to use, but to avoid upfront design, I only ad fields when I need them. If I respected the way migrations were intended I would end up with hundred of migrations the first day and I would waste half my day just creating migrations.

After a project is deployed or a second developer is working on it, I revert to the way migrations are intended and I create a new one every time there’s a change I need in the database.

As migrations are intended to run only once, if you modify them, they won’t get run; and if you force them to run them, they’ll fail, because the database already contains such a table. So I’ve found myself doing this quite often:

rake db:drop && rake db:create && rake db:migrate && rake db:seed && rake db:data

db:data is a task I created to generate some sample data. Good known data that I can use to test the site locally. I’m using Factory Girl to create it, which I also use for the tests so I can re-use as much data creating logic as possible. It’s very good to get to a known state of the project you are developing and to get other developers started right away. I really recommend everyone doing it.

The problem is that I also need to reset my test data, so I end up having this other command and it gets horrible:

RAILS_ENV=test rake db:drop && RAILS_ENV=test rake db:create && RAILS_ENV=test rake db:migrate && RAILS_ENV=test rake db:seed

Note: no db:data this time.

I’ve got tired of re-writing these commands or trying to find them in my bash history, so I decided to write a Ruby task that will do it for me and here it is in case you want to use it too:

namespace :db do
  desc "Crush and burn the database"
  task :hard_reset => :environment do
    File.delete("db/schema.rb")
    Rake::Task["db:drop"].execute
    Rake::Task["db:create"].execute
    Rake::Task["db:migrate"].execute
    Rake::Task["db:seed"].execute
    if !Rails.env.test?
      Rake::Task["db:data"].execute
    end
  end

  desc "Generate sample data for developing"
  task :data => :environment do
    # Create the sample data in here
  end
end

Enjoy!

Update: To delete all records without resetting the whole database, check my post Deleting all records in a Rails project.