Thursday, December 4, 2014

Using Puma, Rails 4 and JRuby on Heroku

The state of JRuby deployment has improved quite a bit since I wrote my book. But information on setting up a new environment still seems hard to come by. I've encountered a number of folks who have tried to set up a deployment environment using guides that are directed at MRI (C Ruby) users. Let's fix that, shall we...

In this post, I'll walk you through the basic steps for setting up Rails 4 to run with Puma under JRuby. Then you'll learn how to deploy that app on Heroku.

Creating a New App


First, let's create a brand new Rails 4 application. Make sure you have JRuby installed and are using it, like this:

$ rvm use jruby-1.7.16.1
Using /Users/jkutner/.rvm/gems/jruby-1.7.16
$ ruby -v
jruby 1.7.16 (1.9.3p392) 2014-09-25 575b395 on Java HotSpot(TM) 64-Bit Server VM 1.7.0_51-b13 +jit [darwin-x86_64]
Now install the Rails 4 Gem and create a new Rails application:

$ gem install rails -v 4.1.8
...
Successfully installed rails-4.1.8
1 gem installed
$ rails new my_app --database=postgresql
...
Use `bundle show [gemname]` to see where a bundled gem is installed.
Because this application was created under JRuby, a nice set of defaults have been configured for you. Gems like "therubyrhino" and "activerecord-jdbc" are already in your application's Gemfile.

But the new application does not have a production ready web server. That's were Puma comes in.

Using and Configuring Puma


Move into the root directory for your new application and open the Gemfile. You'll probably see a commented out line for the "unicorn" gem. Delete it. JRuby and Unicorn are a bad combination (if they even work together at all).

Now add the following line to the Gemfile to make Puma a dependency:

gem "puma", "2.10.2"
view raw Gemfile.rb hosted with ❤ by GitHub
Run bundle install at the command line to install the Gem.  Then configure Puma by creating a config/puma.rb file and putting the follow code in it:

port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
threads (ENV["MIN_PUMA_THREADS"] || 0), (ENV["MAX_PUMA_THREADS"] || 16)
preload_app!
view raw puma.rb hosted with ❤ by GitHub
This will set the port, environment, and thread pool size based on environment variables. The defaults are intended for development.

Because Puma is a multi-threaded server, and JRuby has real threads, you'll want to configure your database connection pool size appropriately. To do this, create a config/initializers/database_connection.rb file, and put the following code in it:

Rails.application.config.after_initialize do
ActiveRecord::Base.connection_pool.disconnect!
ActiveSupport.on_load(:active_record) do
config = ActiveRecord::Base.configurations[Rails.env] ||
Rails.application.config.database_configuration[Rails.env]
config['pool'] = ENV['MAX_PUMA_THREADS'] || 16
ActiveRecord::Base.establish_connection(config)
end
end
Setting your maximum pool size to the number of threads is a good default.

Now run the application with this command:

$ bundle exec puma -C config/puma.rb
view raw puma.sh-session hosted with ❤ by GitHub
Your application is running, and you can see it by browsing to http://localhost:3000. You'll probably get an error if you haven't connected the application to a database. You can do that locally, or you can use a free one from Heroku.

Deploying to Heroku


Heroku is a Platform-as-a-Service that supports both Ruby and Java applications. Naturally, it also supports JRuby. To start using Heroku, follow this guide for installing the Heroku toolbelt and creating an account. This will provide you with a heroku command that you can use to create and deploy apps for free!

To start, make sure your project is under version control with Git by running these commands:

$ git init
$ git add .
$ git commit -am "first commit"
view raw git.sh-session hosted with ❤ by GitHub
Now you need to tell Heroku how to run your app. Do this by placing the command you ran earlier in a Procfile in the root directory of your project. Create the Procfile and put this code in it:

web: bundle exec puma -C config/puma.rb
view raw Procfile hosted with ❤ by GitHub
You'll also need to configure the Ruby runtime in your Gemfile (in this case its JRuby) by adding this line:

ruby '1.9.3', :engine => 'jruby', :engine_version => '1.7.16'
view raw Gemfile2.rb hosted with ❤ by GitHub
Add the changes to Git by running these commands:

$ git add Procfile Gemfile
$ git commit -m "prepared app for heroku"
Next, create a Heroku app for the project and make your first deployment by running these command:

$ heroku create
Creating jkutner-test... done, stack is cedar-14
https://jkutner-test.herokuapp.com/ | https://git.heroku.com/jkutner-test.git
Git remote heroku added
$ git push heroku master
Counting objects: 34, done.
...
-----> Ruby app detected
-----> Compiling Ruby/Rails
-----> Using Ruby version: ruby-1.9.3-jruby-1.7.16
-----> Installing JVM: openjdk7-latest
Picked up JAVA_TOOL_OPTIONS: -Djava.rmi.server.useCodebaseOnly=true
-----> Installing dependencies using 1.6.3
Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4 --deployment
Picked up JAVA_TOOL_OPTIONS: -Djava.rmi.server.useCodebaseOnly=true
...
Your bundle is complete!
Gems in the groups development and test were not installed.
It was installed into ./vendor/bundle
Bundle completed (16.73s)
Cleaning up the bundler cache.
-----> Preparing app for Rails asset pipeline
Running: rake assets:precompile
Picked up JAVA_TOOL_OPTIONS: -Djava.rmi.server.useCodebaseOnly=true
I, [2014-12-04T20:09:48.558000 #970] INFO -- : Writing /tmp/build_4314fe0fe5c1c451df6755a9edded670/public/assets/application-3977927cf8f68a5ebafbe59011904fad.js
Asset precompilation completed (61.06s)
Cleaning assets
Running: rake assets:clean
Picked up JAVA_TOOL_OPTIONS: -Djava.rmi.server.useCodebaseOnly=true
WARNING:
Include 'rails_12factor' gem to enable all platform features
See https://devcenter.heroku.com/articles/rails-integration-gems for more information.
-----> Discovering process types
Procfile declares types -> web
Default types for Ruby -> console, rake, worker
-----> Compressing... done, 112.5MB
-----> Launching... done, v7
https://jkutner-test.herokuapp.com/ deployed to Heroku
Verifying deploy... done.
To https://git.heroku.com/jkutner-test.git
7288c10..7814b0d master -> master
Heroku has detected that your application uses JRuby, it installed the JDK, ran bundler and launched your web process. If you were to open the app by running heroku open you'll probably get an error, because the default landing page is disabled in this environment. But you can create standard Rails templates by running rails generate controller welcome as you would with MRI.

There is a great deal more that Heroku can do for you. You can provision a database, a queuing system, a scheduler, monitoring tools, or even telephony services. And of course, you can begin to leverage the power of the JVM for your Ruby app.

You can download the complete source code for the application presented in this article from the heroku-jruby-rails-4 repo on Github.