Monday, September 24, 2007

JRuby 1.0.1 on Edge Rails 1.2.3.7605

There are a few good tutorials out there on installing stable Rails on top of JRuby 0.92, but I haven't seen any yet describing how to do Edge Rails (1.2.3.7605 at the moment) on JRuby 1.0.1. The newest JRuby fixes a bunch of issues and adds some new features, and Edge Rails has all sorts of improvements to RESTful web services. I had gone through the older JRuby on Rails tutorials to get rolling, then upgraded to Edge Rails and found that I could no longer WAR up my application, nor even run Rails script commands. I'm running JRuby on Windows XP, and the problem arose from some Edge Rails code that determines the application root directory conditionally depending on the Ruby Platform, with a special case for Windows - not accounting for JRuby on Windows, which it sees as "java." So, here I'll describe the process to get rolling and fix some of these issues.

First, visit http://jruby.codehaus.org and download the JRuby 1.0.1 binaries. I unpacked them into C:\jruby, so C:\jruby\jruby-1.0.1\ is my JRuby "home" directory, and C:\jruby\jruby-1.0.1\bin is the path to jruby.bat, jirb.bat, and gem.bat. Following Dominic Da Silva's instructions on developer.com, I set my path to include c:\jruby\jruby-1.0.1\bin and ran:
jruby -version
and got: ruby 1.8.5 (2007-08-23 rev 4201) [x86-jruby1.0.1]

So far, so good.

Please note: If you get an error like this, just close your command prompt and open another one, and remember to reset your PATH to point to jruby. (Thanks to Arun Gupta for pointing that out.) It's due to a JRuby bug:
The input line is too long.
:gotCP
was unexpected at this time.


Now let's get rails installed, using jruby -S to make sure that we're running JRuby and not MRI:
jruby -S gem install rails -y --no-rdoc --no-ri
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed rails-1.2.3
Successfully installed activesupport-1.4.2
Successfully installed activerecord-1.15.3
Successfully installed actionpack-1.13.3
Successfully installed actionmailer-1.3.3
Successfully installed actionwebservice-1.2.3


Note the lack of activeresource in the installed gems - that's because we've installed stable Rails from RubyForge, as opposed to Edge Rails from rubyonrails.org. Now let's make an application to test:

jruby -S rails railstest

This runs the rails generator, and all of its usual output about what directories and files it created for us. Now, cd into railstest, edit config/database.yml to point to your usual MySQL database (remember to set both development and production passwords, as you'll see later), then run jruby script/server to get running. Visit http://127.0.0.1:3000 to make sure that WEBrick is working, and for me it is. So now let's try something more complex. Terminate WEBrick, and then create a simple Hello controller with an index method that outputs the time:
jruby script/generate controller Hello
app/controllers/hello_controller.rb:
class HelloController < ApplicationController
def index
render :inline => "Hello, it is #{Time.now}"
end
end


Fire that up, go to http://127.0.0.1:3000/hello and we now get greeted with the time.
Hello, it is Mon Sep 24 09:00:36 EDT 2007

So far, we're doing alright with stable Rails 1.2.3. Now let's kick it up a notch and upgrade to Edge Rails:
jruby -S gem install rails -y --no-rdoc --no-ri --source http://gems.rubyonrails.org
Bulk updating Gem source index for: http://gems.rubyonrails.org
Successfully installed rails-1.2.3.7605
Successfully installed activesupport-1.4.2.7605
Successfully installed activerecord-1.15.3.7605
Successfully installed actionpack-1.13.3.7605
Successfully installed actionmailer-1.3.3.7605
Successfully installed activeresource-0.9.0.7605

jruby script/server
C:/jruby/jruby-1.0.1/lib/ruby/1.8/pathname.rb:420:in `realpath_rec': No such file or directory - C:/railstest/C: (Errno::ENOENT)
from C:/jruby/jruby-1.0.1/lib/ruby/1.8/pathname.rb:453:in `realpath'
from C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/gems/rails-1.2.3.7605/lib/initializer.rb:494:in `set_root_path!'
from C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/gems/rails-1.2.3.7605/lib/initializer.rb:459:in `initialize'
from ./script/../config/boot.rb:44:in `new'
from ./script/../config/boot.rb:44:in `run'
from ./script/../config/boot.rb:44
from :1:in `require'
from :1


Ouch! Now let's check out the stack trace. The problem seems to be that C:/railstest/C: doesn't exist, which is correct. But why was it expected to exist? Let's work our way up the stack. In C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/gems/rails-1.2.3.7605/lib/initializer.rb, around line 494 is the method set_root_path!, defined as:

# Set the root_path to RAILS_ROOT and canonicalize it.
def set_root_path!
raise 'RAILS_ROOT is not set' unless defined?(::RAILS_ROOT)
raise 'RAILS_ROOT is not a directory' unless File.directory?(::RAILS_ROOT)

@root_path =
# Pathname is incompatible with Windows, but Windows doesn't have
# real symlinks so File.expand_path is safe.
if RUBY_PLATFORM =~ /(:?mswin|mingw)/
File.expand_path(::RAILS_ROOT)

# Otherwise use Pathname#realpath which respects symlinks.
else
Pathname.new(::RAILS_ROOT).realpath.to_s
end

Object.const_set(:RELATIVE_RAILS_ROOT, ::RAILS_ROOT.dup) unless defined?(::RELATIVE_RAILS_ROOT)
::RAILS_ROOT.replace @root_path
end


See the problem? In line 489, if RUBY_PLATFORM =~ /(:?mswin|mingw)/ doesn't return true if the RUBY_PLATFORM is 'java' on windows, which will be true when ENV['OS'] =~ /windows/i. So we just modify that line to: if RUBY_PLATFORM =~ /(:?mswin|mingw)/ || (RUBY_PLATFORM == 'java' && ENV['OS'] =~ /windows/i)

Now try jruby script/server again, and you'll get this error:
C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/gems/activesupport-1.4.2.7605/lib/active_
support/dependencies.rb:500:in `require': Could not find RubyGem jruby-openssl (
>= 0.0.0) (Gem::LoadError)

That's easy to fix, just jruby -S gem install jruby-openssl --no-rdoc --no-ri and you're good to go. I would describe where openssl joined the Edge Rails dependencies, but I can't seem to find that in the stack trace. If you're really interested, I'd suggest grepping through the code. So now, the WEBrick server actually works, and we should get the proper greeting and timestamp on /hello, but we don't! Instead, we get a 404 error in the browser, and the following stack trace in the server:

500 Internal Server Error
#<NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.each>
["C:/jruby/jruby-1.0.1/lib/ruby/1.8/webrick/httputils.rb:129:in `parse_header'",
"C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/gems/rails-1.2.3.7605/lib/webrick_serve
r.rb:146:in `extract_header_and_body'", "C:/jruby/jruby-1.0.1/lib/ruby/gems/1.8/
gems/rails-1.2.3.7605/lib/webrick_server.rb:118:in `handle_dispatch'", "C:/jruby
/jruby-1.0.1/lib/ruby/gems/1.8/gems/rails-1.2.3.7605/lib/webrick_server.rb:78:in
`service'", "C:/jruby/jruby-1.0.1/lib/ruby/1.8/webrick/httpserver.rb:104:in `se
rvice'", "C:/jruby/jruby-1.0.1/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'",
"C:/jruby/jruby-1.0.1/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'", "C:
/jruby/jruby-1.0.1/lib/ruby/1.8/webrick/server.rb:95:in `start'"]
[2007-09-24 09:27:56] ERROR `/hello' not found.
127.0.0.1 - - [24/Sep/2007:09:27:56 EDT] "GET /hello HTTP/1.1" 404 275


Since the stack didn't tell us anything useful, check log/development.log:
Status: 500 Internal Server Error
A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret
=> "some secret phrase" } in config/environment.rb


Aha! This session_key thing is new in Edge Rails, and you'll get the same error in MRI. I did a google search for "edge rails webrick parse_header" and found a ticket about it. So just do what they say, paste that code into config/environment.rb and try again. Make sure you don't just refresh the page, otherwise you'll get a "tampered with cookie" error.

So now we've got a regular JRuby on Rails setup working, let's see if we can turn it into a J2EE servlet using the Warbler gem.
jruby -S gem install warbler --no-rdoc --no-ri
jruby -S warble


That unpacks all of the Rails gems along with the GoldSpike Rails-Integration gem, and bundles everything into a WAR file for deployment. Then deploy with asadmin deploy railstest.war, and http://127.0.0.1:8080/railstest/ now gives the standard Welcome Aboard page, and http://127.0.0.1:8080/railstest/hello ... takes 20 seconds to load an error message. Ugh... Checking the server log in my default Sun J2EE SDK installation in C:\Sun\SDK\domains\domain1\logs\server.log, I see:
WebModule[/railstest] ServletContext.log():Failed to load Rails: No such file or directory - C:/Sun/SDK/domains/domain1/config/C: ... gems/rails-1.2.3.7605/lib/initializer.rb:494:in `set_root_path!
Which is the same Rails initializer bug that we previously fixed. Warbler unpacked the cached gem file rather than copying our modified code, so we need to apply the same fix to the upacked version in tmp/war/WEB-INF/gems, rerun jruby -S wable, redeploy the war file, and... still nothing. Warbler needs to know about jruby-openssl, so run jruby -S warble config, edit config/warble.rb and add config.gems = ["jruby-openssl","rails"], rewarble, redeploy, and after about 10 seconds of JRuby initialization time... SUCCESS!!!!

Now to be perfectly honest, I had a problem at this point. The first time I loaded /hello, I got this error:
Mysql::Error in HelloController#index
#28000Access denied for user 'root'@'localhost' (using password: NO)

Which meant that my production environment was missing its password in config/database.yml. Simple enough to fix, but it meant that I had to shutdown the J2EE server and restart it before I could redeploy the war file, since the JRuby interpreter was already running.


Anyway... Remember how the whole point of this exercise was to get RESTful resources working? Well, lets try a little something out. We won't make an ActiveRecord model, since that would require setting up a JDBC adapter, so rather we'll turn a Hash into XML.

class HelloController < ApplicationController
def myrecords
@myrecords = [{:a => 123, :b=> 234}]
render :xml => @myrecords
end
end


Rewarble, redeploy, and check out /hello/myrecords.xml . The fact that it returns type="array" in the root node means that ActiveResource's find :all method, as well as any methods that return multiple resource instances, will work correctly. Older versions of the to_xml method left that attribute out, causing the load method to fail.

It is now time to do a happy dance!

3 Comments:

At 1:14 AM, Blogger Unknown said...

Wow, you endured a lot of pain to get to that point. Thanks for sticking with it!

Also, if you have the inkling, please try either the latest on the JRuby trunk or 1.0 branch, as I fixed an edge-Rails related pathing bug (possibly the same as you had to work around).

 
At 12:11 PM, Blogger Adam Payne said...

Thanks for the suggestion, Nick. I got the latest trunk (4471). Here's the new stack trace, slightly different than what i was getting before:

C:/jruby/trunk/jruby/lib/ruby/1.8/pathname.rb:420:in `realpath_rec': No such fil
e or directory - C: (Errno::ENOENT)
from C:/jruby/trunk/jruby/lib/ruby/1.8/pathname.rb:453:in `realpath'
from C:/jruby/trunk/jruby/lib/ruby/gems/1.8/gems/rails-1.2.3.7707/lib/in
itializer.rb:495:in `set_root_path!'
from C:/jruby/trunk/jruby/lib/ruby/gems/1.8/gems/rails-1.2.3.7707/lib/in
itializer.rb:460:in `initialize'
from ./script/../config/boot.rb:35:in `new'
from ./script/../config/boot.rb:35:in `run'
from ./script/../config/boot.rb:35:in `run'
from script/server:2:in `require'
from script/server:2:in `require'

 
At 8:47 PM, Blogger Unknown said...

I downloaded the latest Netbeans build and came across the same error. Was able to fix using your post - Thanks.

If they know this is a bug, why haven't they fixed it yet!

 

Post a Comment

<< Home