Peter Marklund

Peter Marklund's Home


RubyGems: LoadError in nested require no longer causes silent failure with Ruby 1.8.5

I had a Rails application where I was trying to reference a helper that didn't exist. What happens in Rails when you try to use a class that you haven't required is that the Ruby callback method const_missing is invoked. Behind the scenes Rails will then try to require the class for you. What was happening was that Rails was trying to require the helper file, but the helper file didn't exist, and so that failed. The problem wasn't so much that it failed, but that it failed silently - there was no error message to be seen anywhere.

After having painstakingly tracked down the problem it turned out that it had to do with the Ruby require method and how RubyGems (I was using Rails through RubyGems) overrides the require method. RubyGems redefines the require method so that it first invokes the original Ruby require method, and if a LoadError is thrown, it will attempt to find the appropriate Gem, add it to the load path, and then invoke the Ruby require method again.

I posted about this silent failure on the RubyGems mailinglist and Eric Hodel suggested I should file a bug if I could reproduce the problem without Rails. Now several weeks later, and after an upgrade to Ruby 1.8.5, it turns out I can no longer reproduce the bug. The reason is that the Ruby require method behaves slightly differently with respect to LoadErrors in Ruby 1.8.5.

This may never be relevant to you, but let me just briefly try to explain the details of what is going on. Suppose you have a file called test.rb with a single line that says:

  require 'this_file_does_not_exist'

Then with Ruby 1.8.4 you would get:

$ irb
irb(main):001:0> require 'test'
LoadError: no such file to load -- this_file_does_not_exist
        from ./test.rb:1:in `require'
        from ./test.rb:1
        from (irb):1
irb(main):002:0> require 'test'
=> false

As you can see the second time you try to require the file the require method returns false, i.e. no LoadError is thrown. This indicates that the file has already been loaded. However it doesn't tell us that the loading failed. This absence of a second LoadError is what yields a silent failure if test.rb lives in an activated gem. The reason is that the first LoadError is caught by the RubyGems require method.

With Ruby 1.8.5 however a LoadError will be thrown everytime you do "require 'test'" and thus the LoadError will no longer be hidden by RubyGems. I don't know why they changed the contract of the Ruby require method, they might have had other reasons, but my suspicion is it was to avoid some kind of silent failures.