I am currently working on a project using JRuby for a server-side component. One of the team’s deployment goals has been to avoid relying on the production environment having JRuby installed in it (either globally or with RVM). Instead we have been using the jruby-complete.jar to bundle the entire JRuby runtime with our application. This post that argues for using jruby-complete still does a good job of explaining why this is a good thing (even if it is a few years old).
When starting this project, I already had a pretty good blueprint on how to run a Ruby application with jruby-complete. However, in the time since that post was written Bundler has become the defacto way of managing gem dependencies. In this post, I want to share how we are using jruby-complete with Bundler in our development and production environments.
In order to use Bundler to manage your project’s gems, you first need to install it. We chose to put it in
java -jar vendor/jruby-complete-1.7.0.jar -S gem install -i vendor/gem_home --no-rdoc --no-ri bundler
In order for JRuby to know where to find the installed Bundler gem, you need to set the
GEM_PATH environment variables to point to
vendor/gem_home. You should be able to see that Bundler has been installed by doing a
> GEM_HOME=vendor/gem_home GEM_PATH=vendor/gem_home java -jar vendor/jruby-complete-1.7.0.jar -S gem list *** LOCAL GEMS *** bundler (1.3.5)
Bundler with Binstubs
My preference is to install all of the gems specified in the Gemfile into a separate directory. For this you can use the
--path command line option. In addition you should use the
--binstubs option to get a
bin directory containing scripts that can be used to run gems without having to use “bundle exec.” We ran into all sorts of trouble trying to
bundle exec using jruby-complete, but no problems at all with the binstubs scripts.
For example, using the following Gemfile:
source 'https://rubygems.org' gem "rake" gem "rspec" gem "hamsterdam"
I can install the specified Gems like so:
> GEM_HOME=vendor/gem_home GEM_PATH=vendor/gem_home java -jar vendor/jruby-complete-1.7.0.jar -S bundle install --path=vendor/bundle --binstubs Fetching gem metadata from https://rubygems.org/......... Fetching gem metadata from https://rubygems.org/.. Installing rake (10.0.4) Installing diff-lcs (1.2.4) Installing hamster (0.4.3) Installing hamsterdam (1.0.6) Installing rspec-core (2.13.1) Installing rspec-expectations (2.13.0) Installing rspec-mocks (2.13.1) Installing rspec (2.13.0) Using bundler (1.3.5) Your bundle is complete! It was installed into ./vendor/bundle
Rake in the Gemfile
In a normal MRI environment, you will almost certainly have Rake installed as a system gem. But in this environment, we are listing rake in the Gemfile and using the binstub to run it:
GEM_HOME=vendor/gem_home GEM_PATH=vendor/gem_home java -jar vendor/jruby-complete-1.7.0.jar -S bin/rake -T
Package and Ignore
I think it is a good idea to package the bundled gems and add
vendor/bundle to your
.gitignore (or equivalent). Packaging the gems puts them into
vendor/cache and means that you can bundle install without having to access rubygems.org.
GEM_HOME=vendor/gem_home GEM_PATH=vendor/gem_home java -jar vendor/jruby-complete-1.7.0.jar -S bundle package
At this point you have a functioning environment that can run your Ruby code using jruby-complete with its bundled gems. The biggest pain now is the long commands that need to be run. I have been using bash aliases to make this less painful. For example:
alias jruby="PATH=bin:$PATH GEM_HOME=vendor/gem_home GEM_PATH=vendor/gem_home java -jar vendor/jruby-complete-1.7.0.jar" alias rspec="jruby -S rspec" alias rake="jruby -S rake" alias bundle="jruby -S bundle"
Put this in an
environment file and source it when working on this project. If you have RVM installed, you can have RVM source the environment for you when you cd into the project directory automatically. Just put
source environment in the
If I am going to be using the jruby-complete jar in production, then I want to also be using it in development if at all possible. It turns out that it is very possible, and in fact quite convenient once you get the environment configured as I have described above.