Monday, January 12, 2009

Rails Cygwin Gems Issue

I have an existing web app that I used successfully in linux over a year ago. I go to launch it in a Cygwin gem installation, with Cygwin's installation of Ruby.

Symptom: The web app doesn't launch. Instead I get:

~/Temp/cookbook
Mike, HIT that shit: ls
README  Rakefile  app  components  config  db  doc  lib  log  public  script  test  tmp  vendor
~/Temp/cookbook
Mike, HIT that shit: ./script/server
./script/../config/boot.rb:28: undefined method `require_gem' for main:Object (NoMethodError)
    from ./script/server:2:in `require'
    from ./script/server:2

I don't have any immediate ideas for how to fix this. I don't understand what the error message is saying. My guess is that some library load path is wrong; I've seen issues like this before when I look it up.

More Information: I read online for more information about this error, specifically in the context of rails. People wrote that sometimes this can be a symptom of an old rails application having an incorrect config file.

As a test, I used my current version of rails to create a new rails application, and did a recursive diff on them. There were many differences that had nothing to do with the core of my application.

I then figured I should update my application to work with a more modern version of rails. I tried googling how to do this, and I found a common solution for this using "rake", which is an app I've heard a lot about.

Rake made no sense to me at first because it was called a "build" system, and Ruby is a dynamic language so code shouldn't need to be compiled. But then I finally realized you could use rake to build projects in Java, or C++, and replace conventional makefiles. Whoops =\

Anyway, the file that contains a lot of "useful info" is config/environment.rb. In my case, at least the gems version was mismatched. The file contained version 1.1.6, while gem -v returned 1.1.1. I tried to use rake to update my application, as above, but the command failed; "undefined method 'require_gem'". Conjecture: If I make a new application, and run that one, and it works, then what does that mean? It means I've conjectured that something is wrong with my old application code.

I had deleted the new application, but when I re-made a new one, and run its ./script/server, it worked! Well, the WebBrick server ran and I was able to load the front ruby page in a web browser on another machine by connecting to http://192.168.3.103:3000/.

The first link on it failed with an sqlite error, which I can fix later.

So now this raises the question, what's wrong with my existing application so that it won't even launch the front page? It must be something in my old application code...

Information:

I figured out what the error means. If you write a trivial program and try to call a function foo that isn't defined, you get a "undefined method 'foo' for main:Object (NoMethodError)" where foo is the name of the function that you tried to call.

So, require_gem isn't defined, even though it should be. Who provides this function? Why isn't that person's work available in the runtime?

Information: I walked up to the offending line using print statements to verify that boot.rb was the file that was offending, as the error suggested.

boot.rb uses require_gem, but this isn't defined. This method has been deprecated [See Jim's comment].

In a current version of a ruby autogenerated application, "gem" is used instead.

This was a known issue with Rails that has been fixed.

Conjectured Solution 1: If I manually replace boot.rb file to use "gem" instead of "require_gem", then things will work.

A reproduction of the problem is this tiny script:

#!/usr/bin/ruby

require 'rubygems'
require_gem "rails"

Which can be fixed with:

#!/usr/bin/ruby

require 'rubygems'
gem "rails"

By replacing require_gem with gem in boot.rb, the issue is fixed temporarily.

The script/server script worked correctly, and served up the main webpage.

But it isn't clear how to update boot.rb in a permanent fashion. Indeed, my old cookbook application worked well because it defaulted to using a mysql database driver.

The rake rails:update also runs without a crash, but this corrupted the boot.rb file to revert to its old state where it uses require_gem, so this is actually a harmful step to take. I looked up "rake rails:update" on google, and found a handy reference for all the rake "tasks". This may be handy later on.

Conjectured Solution 2: Replace the boot.rb file with a file from a newly created rails project. But this file is totally incompatible with the given rails configuration, and the rake command crashes.

Conjectured Solution 3: Maybe environment.rb also has issues. I compared my app's environment.rb to a new app's environment.rb, and found a RAILS_GEM_VERSION difference. My app uses a 1.x version, while the new app uses a 2.x version. It is possible that when I run rake rails:update, it "updates" my rails to an old version denoted in this file. I edited my app's environment.rb to use my 2.x version of rails.

If I do this and also use Conjectured Solution 1 of replacing require_gem calls, and then do a rake rails:update, then perhaps my app will be properly updated.

I tried this, and the first thing I noticed is that the update took a much longer time (seconds instead of miliseconds). I inspected boot.rb, and it had not been clobbered, as with Solution 1!

I ran rake rails:update again, and it still worked!

I ran script/server, and it worked!

This resolved the require_gem issue.

After this, I did have a new problem, where I was getting 500 server errors, but that was separate from this problem, which was now solved.

Reviewing the solution

  • Leaving a rails application stale and dead for years at a time is a bad idea. This is what happened here.
  • Rake appears to have a bunch of "tasks" associated with it.
  • There is a difference between activating a gem, and loading the gem. "require" loads a file. "activating" a gem just points the ruby load path to the appropriate paths denoted by the gem.
  • Run "ruby -r debug" to go into ruby debug mode, instead of having to print statments. ( Python also supports "python -m pdb" ).