Cloud Zone is brought to you in partnership with:

Eric is the Editorial Manager at DZone, Inc. Feel free to contact him at egenesky@dzone.com Eric has posted 804 posts at DZone. You can read more from them at their website. View Full User Profile

How to Use JRuby for Rails Apps on Cloud Foundry

04.19.2012
| 6750 views |
  • submit to reddit

The content of this article was originally posted by Thomas Risberg at the Cloud Foundry blog.

JRuby Rails applications can be deployed to CloudFoundry.com today with simple configuration changes. JRuby applications are commonly deployed to servlet containers by creating a .war file that contains the Rails app. We will do the same for Cloud Foundry with some changes to the database configuration, so the application can also access a database service on CloudFoundry.com.

Changes needed for deploying JRuby on Rails Applications to Cloud Foundry

There are two tasks we need to accomplish in order to get a JRuby application running on CloudFoundry.com. First we need to configure the application to connect to the database service on CloudFoundry.com by modifying the database.yml file in the configuration directory. We also need to run the equivalent of ‘rake db:migrate', when we deploy the application so that the database tables are created. We can do this by adding an initializer in the config/initializers directory.

The information we need to configure the database connection is available in the environment variable, VCAP_SERVICES. We could either parse that variable programmatically or use the convenient Cloud Foundry run-time gem (see Using Cloud Foundry Services with Ruby: Part 2 – Run-time Support for Ruby Applications blogpost), which is what we will do here. To use this gem we need to include it in our Gemfile:

...
gem  'cf-runtime'
...

Now that we have added this gem, we can add some code snippets to the database.yml file to access the database service information for the production environment. The following is the production portion from a database.yml file, where we are using a MySQL database:

config/database.yml

production:
  adapter: mysql
  <% require 'cfruntime/properties' %>
  <% db_svc = CFRuntime::CloudApp.service_props('mysql') %>
  database: <%= db_svc[:database] rescue 'bookshelf_production' %>
  username: <%= db_svc[:username] rescue 'root' %>
  password: <%= db_svc[:password] rescue '' %>
  host: <%= db_svc[:host] rescue 'localhost' %>
  port: <%= db_svc[:port] rescue '3306' %>

As you can see, we added a require statement for cfruntime/properties, and then we get a hash of the service properties by calling the service_props method passing in the type of service we are using. If there is only one service of that type bound to the application, then there is no need to specify the actual name of the service. You will need to specify the actual service name if you bind multiple services of the same type to your app. The hash of service properties is stored in a variable called db_svc, and code extracts the corresponding values to be used for database, username, password, host, and port. Each of these statements have a rescue clause that provides values to use if we are not operating in a Cloud Foundry environment, in which case the db_svc would be nil.

Alternatively, the production portion of the database.yml file would look like this for PostgreSQL:

production:
  adapter: postgresql
  encoding: unicode
  <% require 'cfruntime/properties' %>
  <% db_svc = CFRuntime::CloudApp.service_props('postgresql') %>
  database: <%= db_svc[:database] rescue 'bookshelf_production' %>
  username: <%= db_svc[:username] rescue 'bookshelf' %>
  password: <%= db_svc[:password] rescue '' %>
  host: <%= db_svc[:host] rescue 'localhost' %>
  port: <%= db_svc[:port] rescue '5432' %>

Next, we turn our attention to the creation of the tables we need for our app. For this to happen, we need to add the following initializer to the config/initializers directory when we deploy the app. I named this initializer cf_db_migrate.rb:

config/initializers/cf_db_migrate.rb

require 'cfruntime/properties'
 
# Run the equivalent of rake db:migrate on startup
if CFRuntime::CloudApp.running_in_cloud?
  migrations = Rails.root.join('db','migrate')
  if migrations.directory?
    ActiveRecord::Migrator.migrate(migrations)
  end
end

We use cfruntime/properties again and check to see that we are running in the cloud. Next we check to see if the db/migrate directory exists and if it does we run the database migration using the migration files in the directory ( ActiveRecord::Migrator.migrate(migrations) ).

One additional change we have to make is to the warble configuration. It does not include the db/migrate directory in the generated war file by default, so we need to add this to the configuration by specifying config.includes = FileList["db/migrate/*"]. Here are the relevant contents of the config/warble.rb file:

config/warble.rb

# Warbler web application assembly configuration file
Warbler::Config.new do |config|
 
  # Application directories to be included in the webapp.
  config.dirs = %w(app config lib log vendor tmp)
 
  # Additional files/directories to include, above those in config.dirs
  config.includes = FileList["db/migrate/*"]
 
end

A complete example

This assumes that you have a working JRuby environment with Rails, Warbler and MySQL gems already installed.

We have seen above what changes are needed, so let’s quickly generate a Rails app and make the required changes and deploy the app to CloudFoundry.com. If you don’t already have JRuby installed a good place to start is Getting Started with JRuby.

Create the JRuby Rails App

First we create the new app and create the first domain object with full scaffolding.

jruby -S rails new bookshelf -d mysql
cd bookshelf
jruby -S rails generate scaffold Book title:string category:string published:integer price:decimal{10.2} isbn:string

Next we remove the generated public/index.html and modify the config/routes.rb to use ‘books’ as the root:

rm public/index.html
vi config/routes.rb

Here is the route that I added in config/routes.rb:

Bookshelf::Application.routes.draw do
  resources :books
 
  # You can have the root of your site routed with "root"
  # just remember to delete public/index.html.
  # root :to => 'welcome#index'
  root :to => 'books#index'
 
  # See how all your routes lay out with "rake routes"
 
end

Now we will run this app locally to make sure it works:

jruby -S rake db:create
jruby -S rake db:migrate
jruby -S rails server

The Rails empty list view of the Book entity shows that this is indeed working. Now I can add new books to my bookshelf.

Modify the JRuby Rails App for CloudFoundry deployment

Let’s start by making the following changes that we mentioned above:

  • add the gem cf-runtime to the Gemfile
  • modify the “production:” section of the config/database.yml file as shown above
  • add a file named config/initializers/cf_db_migrate.rb with the content shown above

Next we need to generate the Warbler config file so we run:

jruby -S warble config

Now we can:

  • modify the config/warble.rb to add the db/migrate directory as shown above

Those are all the changes needed and we are now ready to package and deploy this app.

Package and deploy the JRuby Rails App to CloudFoundry

We will use Warbler to package the app into a war and the CloudFoundry vmc command line utility to deploy it.

The process we use to package the application into a war file is a simple: bundle, pre-compile assets and run Warbler:

jruby -S bundle install
jruby -S rake assets:precompile
jruby -S warble

This creates a bookshelf.war in the root directory of our Rails app. At the moment there are some issues running the vmc command with JRuby but we are working on a fix for this. In the meantime we can move the war file to another directory, so that I can easier switch to use regular “C” Ruby. I’ll create a ‘deploy‘ directory and configure that to use Ruby 1.9.2-p290 (I’m using rbenv, but you could use RVM as well):

mkdir deploy
mv bookshelf.war deploy/.
cd deploy
rbenv local 1.9.2-p290
# (if you use RVM the command should be 'rvm ruby-1.9.2-p290')

Now we are ready to log in to CloudFoundry and deploy our application. For this part you need to have vmc installed.

vmc target api.cloudfoundry.com
vmc login cloud@mycompany.com
Password: *****
Successfully logged into [http://api.cloudfoundry.com]
vmc push bookshelf
Would you like to deploy from the current directory? [Yn]: Y
Application Deployed URL [bookshelf.cloudfoundry.com]: mybookshelf.cloudfoundry.com
Detected a Java Web Application, is this correct? [Yn]: Y
Memory reservation (128M, 256M, 512M, 1G, 2G) [512M]: 512M
How many instances? [1]: 1
Bind existing services to 'bookshelf'? [yN]: N
Create services to bind to 'bookshelf'? [yN]: Y
1: mongodb
2: mysql
3: postgresql
4: rabbitmq
5: redis
What kind of service?: 2
Specify the name of the service [mysql-a4fd7]: mysql-books
Create another? [yN]: N
Would you like to save this configuration? [yN]: N
Creating Application: OK
Creating Service [mysql-books]: OK
Binding Service [mysql-books]: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (707K): OK
Push Status: OK
Staging Application 'bookshelf': OK
Starting Application 'bookshelf': OK

The vmc commands are highlighted above. Most defaults have been accepted except for the URL and whether a service should be created. I used the URL ‘mybookshelf.cloudfoundry.com‘ instead of the default to avoid collision with existing bookshelf applications. I answered ‘Y‘ to the question of creating a new service and picked (2) mysql and gave it the name of ‘mysql-books‘.

We should now see the app running:

vmc apps

+-------------+----+---------+---------------------------------+---------------+
| Application | #  | Health  | URLS                            | Services      |
+-------------+----+---------+---------------------------------+---------------+
| bookshelf   | 1  | RUNNING | mybookshelf.cloudfoundry.com    | mysql-books   |
+-------------+----+---------+---------------------------------+---------------+

 

So we can now enter ‘http://mybookshelf.cloudfoundry.com/‘ and see the Bookshelf application come to life and add some books.

You can review and download the entire source used for this sample at cloudfoundry-samples/jruby-rails-bookshelf or if you just want to see the changes needed to deploy in Cloud Foundry look at this commit.

Conclusion

We have shown that it’s possible to deploy a simple JRuby on Rails application to cloudfoundry and use a MySQL service as the backing data store. All that is required is some modifications to the apps configuration of the database.

In a future post we’ll take a look at similar changes that we need to do for a JRuby Sinatra app that uses DataMapper for persistence.

 

Published at DZone with permission of its author, Eric Genesky.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)