Olexiy (Alexey) Prokhorenko likes to live in different places and blog about different things. Ruby, JRuby, Java, Javascript, Scala, Rails, Spring, Hibernate... just to name a few. And different aspects of product management and agile software development. On the other note, he also loves speed in form of flashy cars and sportbikes, so those topics may find their place here, too.

Saturday, February 07, 2009

Ruby on Rails application "migrating" into JRuby on Tomcat

JRuby is something what made me like Ruby. Actually, it's the huge reason I am leaning into Ruby. By having strong background with Java, I cannot say I am disgusted with it (as it was a trend recently to say so). I like Java, but even with the evolvment of Spring, Struts, JBoss Seam (and many others) it cannot be too competitive with PHP, Ruby, etc. languages in terms of fast launching of prototype. Eventually, people are getting back to "roots" (i.e. Java, C) when they are getting their bigger, need more options with scalability, etc. What I love about JRuby is that it helps me to run everything in JVM and when I'll need "help" from Java, it will be right here, with the easiest integration possible.

Actually, I tried to get into JRuby few times, but truth to be told, I didn't see it at the level I needed it. However, recently, I gave it another try and it was the real deal - I am about to describe my experience below.

I've got a real Web application, which will be launched in production (soon), and it is using Ruby on Rails. So, I decided to port it into JRuby.
Here we go (I'll try to explain everything from the cleanest installation possible).

First step is, obviously, downloading JRuby. I am on Mac OS X, so everything will be according my experience on this platform. It should not be too different from any Unix-other one.

In my case I keep everything in /usr/local/, so let's install JRuby right there:

olexiy@airbus:~$ curl http://dist.codehaus.org/jruby/jruby-bin-1.1.6RC1.tar.gz -o ~/Downloads/jruby-bin-1.1.6RC1.tar.gz
olexiy@airbus:~$ cd /usr/local
olexiy@airbus:/usr/local$ sudo tar -zxvf ~/Downloads/jruby-bin-1.1.6RC1.tar.gz
olexiy@airbus:/usr/local$ sudo ln -sf jruby-1.1.6RC1/ jruby


Update paths:

olexiy@airbus:~$ cat .profile | grep JRUBY
JRUBY_HOME=/usr/local/jruby; export JRUBY_HOME;
PATH=$HTTPD_HOME/bin:$JAVA_HOME/bin:$JRUBY_HOME/bin:$SCALA_HOME/bin:$FLEX_HOME/bin:$MAVEN_HOME/bin:$MYSQL_HOME/bin:$MYSQL_HOME:/opt/local/bin:/usr/local/bin/bin:/usr/local/bin/lib:/usr/local/bin:/opt/bin:/usr/bin:/usr/sbin:/opt/local/bin:/usr/local/sbin:$PATH;


(and re-login to be sure your new paths picked up).

Let's install gem's now. I am specifying exact path where to use gem from, just because I want to be on the safe side as which gem will be launched. I need JRuby's one. Later down the text, I am specifying exact paths just to let reader understand easier which thing should have been running.

olexiy@airbus:~$ sudo /usr/local/jruby/bin/gem install rails mongrel jdbc-mysql activerecord activerecord-jdbcmysql-adapter rspec rack rake jruby-openssl activesupport


We'll need also ruby-debug, and need to install it with a tiny workaround:

olexiy@airbus:~$ cd /tmp
olexiy@airbus:/tmp$ curl http://files.rubyforge.vm.bytemark.co.uk/debug-commons/ruby-debug-base-0.10.3.1-java.gem -o ruby-debug-base-0.10.3.1-java.gem
olexiy@airbus:/tmp$ sudo /usr/local/jruby/bin/gem install ruby-debug-base-0.10.3.1-java.gem
olexiy@airbus:/tmp$ sudo /usr/local/jruby/bin/gem install --ignore-dependencies ruby-debug
olexiy@airbus:/tmp$ rm ruby-debug-base-0.10.3.1-java.gem


And we need to use MySQL Connector/J (we copy it into lib/ folder of JRuby):

olexiy@airbus:~$ cd /tmp
olexiy@airbus:/tmp$ curl http://mysql.easynet.be/Downloads/Connector-J/mysql-connector-java-5.1.7.tar.gz -o mysql-connector-java-5.1.7.tar.gz
olexiy@airbus:/tmp$ tar -zxvf mysql-connector-java-5.1.7.tar.gz
olexiy@airbus:/tmp$ sudo mv mysql-connector-java-5.1.7/mysql-connector-java-5.1.7-bin.jar /usr/local/jruby/lib/
olexiy@airbus:/tmp$ rm -R mysql-connector-java-5.1.7/ mysql-connector-java-5.1.7.tar.gz


Almost ready. Here is an example of what gems do I see installed for me:

olexiy@airbus:~$ sudo /usr/local/jruby/bin/gem list --local

*** LOCAL GEMS ***

actionmailer (2.2.2)
actionpack (2.2.2)
activerecord (2.2.2)
activerecord-jdbc-adapter (0.9)
activerecord-jdbcmysql-adapter (0.9)
activeresource (2.2.2)
activesupport (2.2.2)
columnize (0.3.0)
gem_plugin (0.2.3)
jdbc-mysql (5.0.4)
jruby-openssl (0.3)
mongrel (1.1.5)
rack (0.9.1)
rails (2.2.2)
rake (0.8.3)
rspec (1.1.12, 1.1.11)
ruby-debug (0.10.3)
ruby-debug-base (0.10.3.1)
sources (0.0.1)


Even if we are done with installation, we still need to prepare our Web application. Changes are required, but they are very easy. My application is called 'titi', and the changes we'll be making are not application-specific. You need to open config/environment.rb and after:

# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
...


need to add the following block:

# Load JDBC drivers if we run on JRuby
if RUBY_PLATFORM =~ /java/
require 'rubygems'
RAILS_CONNECTION_ADAPTERS = %w(jdbc)
end


Also open config/database.yml and below is an example of changed settings for "development" (and all others will require similar changes):

...
development:
<% if RUBY_PLATFORM =~ /java/ %>
adapter: jdbcmysql
driver: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/titi_development?user=root&password=
<% else %>
adapter: mysql
host: localhost
socket: /tmp/mysql.sock
<% end %>
encoding: utf8
database: titi_development
username: root
password:
...


It's all what's needed! Here we can launch us on JRuby (from you Web app folder):

olexiy@airbus:~/repo/titi/trunk/titi$ jruby -v
jruby 1.1.6RC1 (ruby 1.8.6 patchlevel 114) (2008-12-03 rev 8263) [i386-java]
olexiy@airbus:~/repo/titi/trunk/titi$ jruby script/server


Now, we can enjoy JVM. But important part is to be able to deploy for production on (say) Tomcat. And we need Warbler for that.

olexiy@airbus:~$ sudo /usr/local/jruby/bin/gem install warbler


Now, let's ask Warble to create us config file:

olexiy@airbus:~/repo/titi/trunk/titi$ sudo /usr/local/jruby/bin/warble config


It will be created in config/warble.rb and in my case I had to uncomment there the following lines:

...
config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl"]
...
config.gems["rails"] = "2.2.2"
...
config.webxml.rails.env = ENV['RAILS_ENV'] || 'production'
...


Now, let's build the web application into WAR:

olexiy@airbus:~/repo/titi/trunk/titi$ sudo /usr/local/jruby/bin/warble war


Once done, we will have titi.war in the Web app folder. So let' go and have it running in Tomcat. Here is a short intro how to install it (if you didn't yet):

olexiy@airbus:~$ curl http://www.signal42.com/mirrors/apache/tomcat/tomcat-5/v5.5.27/bin/apache-tomcat-5.5.27.tar.gz -o ~/Downloads/apache-tomcat-5.5.27.tar.gz
olexiy@airbus:~$ curl http://apache.deathculture.net/tomcat/tomcat-6/v6.0.18/bin/apache-tomcat-6.0.18.tar.gz -o ~/Downloads/apache-tomcat-6.0.18.tar.gz
olexiy@airbus:~$ cd /usr/local
olexiy@airbus:/usr/local$ sudo tar -zxvf ~/Downloads/apache-tomcat-6.0.18.tar.gz
olexiy@airbus:/usr/local$ sudo ln -sf apache-tomcat-6.0.18/ tomcat


Let's not forget to update paths again to have Tomcat and Java there:

olexiy@airbus:~$ cat .profile | grep CATALINA
CATALINA_HOME=/usr/local/tomcat; export CATALINA_HOME;
PATH=$HTTPD_HOME/bin:$CATALINA_HOME/bin:$JAVA_HOME/bin:$JRUBY_HOME/bin:$SCALA_HOME/bin:$FLEX_HOME/bin:$MAVEN_HOME/bin:$MYSQL_HOME/bin:$MYSQL_HOME:/opt/local/bin:/usr/local/bin/bin:/usr/local/bin/lib:/usr/local/bin:/opt/bin:/usr/bin:/usr/sbin:/opt/local/bin:/usr/local/sbin:$PATH;
olexiy@airbus:~$ cat .profile | grep JAVA
JAVA_HOME=/usr; export JAVA_HOME;
PATH=$HTTPD_HOME/bin:$CATALINA_HOME/bin:$JAVA_HOME/bin:$JRUBY_HOME/bin:$SCALA_HOME/bin:$FLEX_HOME/bin:$MAVEN_HOME/bin:$MYSQL_HOME/bin:$MYSQL_HOME:/opt/local/bin:/usr/local/bin/bin:/usr/local/bin/lib:/usr/local/bin:/opt/bin:/usr/bin:/usr/sbin:/opt/local/bin:/usr/local/sbin:$PATH;


Ready to launch:

olexiy@airbus:~$ sudo /usr/local/tomcat/bin/catalina.sh start
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
olexiy@airbus:~$ ps -ax | grep tomcat
71464 ttys000 0:02.22 /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home/bin/java -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath :/usr/local/tomcat/bin/bootstrap.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
71466 ttys000 0:00.00 grep tomcat


It's up an running. You can check it out on http://localhost:8080/. In other terminal window we will monitor logs while we'll deploy our application.

olexiy@airbus:~$ tail -f /usr/local/tomcat/logs/catalina.out


keep this window open while the deployment will be in process. To deploy our application we just need to copy titi.war into Tomcat's deployment directory.

olexiy@airbus:~/repo/titi/trunk/titi$ sudo cp titi.war /usr/local/tomcat/webapps/


Now, breath deeply for a minute or two, point browser to http://localhost:8080/titi/ and it gives me perfect production application running on Tomcat.

That's it! Very impressive how two different technologies were easy to mix together. They do require quite few steps, but it's all well worth it.

13 comments:

Abditus@mac.com said...
This post has been removed by the author.
pravin said...

Thanks Alex, I followed your procedure. However I got error during tomcat6 start up

SEVERE: unable to create shared application instance
org.jruby.rack.RackInitializationException: Please install the jdbcmysql adapter: `gem install activerecord-jdbcmysql-adapter` (no such file to load -- active_record/connection_adapters/jdbcmysql_adapter)

Could you correct me if I did anything wrong to get this error.

Oliver Meyn said...

Well done - worked for me just as advertised. Thanks alot!

Abditus@mac.com said...

Pravin,

I ran into that problem, but ended up getting it to work by completely removing jruby and ~/.gem then following Alex's steps exactly. I haven't isolated the problem, but it seemed to be caused by intermingling my local ~/.gem libs with the ones in /usr/local/jruby. Also be sure to manually clear out the unpacked war in tomcat (it can be slow doing it for you and you might unknowingly be running an old war), and delete everything in tmp in your rails project before running warble again.

Olexiy Prokhorenko said...

Abditus@ -- you are correct with your note about war:clean - it helpful and lets you be on the safe side knowing it gets everything new.

pravin -- this may be the problem that warble didn't include this gem, and may be this gem was installed into your "other" gem tree - it is very easy to mess up when running couple Ruby's (and Java's and anything :-) on one machine. be sure you install all gems in one place, and that you update warble config to actually include this gem.

You also can do in some separate folder:

jar -xvf yourwebapp.war

and it will extract everything and you'll be able to see what is really coming in gems folder.

I hope this helps.

pravin said...

Thanks Abditus, and Olexiy.

I have problem with warble. Warble did not include the gems activerecord-jdbc-adapter-0.9, activerecord-jdbcmysql-adapter-0.9, jdbc-mysql-5.0.4, jruby-openssl-0.3, etc. while generating the WAR.

I had to copy these gems and gemspec manually to the tomcat's WEB-INF\gems folder.

I am not sure why warble missed these gems. However, Thanks for your help.

Olexiy Prokhorenko said...

Did you try to change warble's config the way I mentioned in my blog post?

pravin said...

Yes Olexiy. I have warble.rb at \jruby-1.1.6RC1\bin\config\. My Project is \jruby-1.1.6RC1\samples\hello

I uncommented config.gems += ["activerecord-jdbcmysql-adapter", "jruby-openssl"], config.webxml.rails.env = ENV['RAILS_ENV'] || 'development', and config.gems["rails"] = "2.2.2"

Olexiy Prokhorenko said...

Pravin --

I never got into this problem. It looks like still it's related to not having the gem in the right place. Try to install it again with jruby's gem and be sure it's installated into jruby's gem "folder". It should be included then.
What you also can try to do is to change jdbcmysql to just jdbc and see if it works for you. It seems that you are on Windows machine, and I really have no idea what exactly happens there. I didn't have any problems on Mac OS X and Debian.

pravin said...

I am working with Windows XP machine. I have warble at "C:\jruby-1.1.6RC1\bin". I have set PATH in command prompt for this bin folder. I have warble.rb at "C:\jruby-1.1.6RC1\bin\config\".

When I am running "warble" outside of jruby's bin folder, warble could not read warble.rb for the config.

Jorge.Rodriguez.Suarez said...

Hey alex,

When u did sudo /usr/local/jruby/bin/gem install rails mongrel jdbc-mysql...

u install the mysql-connector-java-5.0.4-bin.jar in the gem path, so you dont need to download the driver :P

Great post !

Olexiy Prokhorenko said...

That's right, though when I tried to deploy on two different Debians it didn't work there until I installed JAR manually the way I described. Didn't have time to dig into details, so it may be system configuration, but anyhow, I thought it would be better to mention it. :-)

fractalontology said...

Thanks for this!