Forums/Knowledge Bases/CloudFoundry.com Knowledge Base

Getting Started with the RabbitMQ Service from a Rails application

David Wragg
posted this on August 03, 2011 04:03

This tutorial explains how to use the RabbitMQ service for Cloud Foundry from a Rails 3 application.

There are a number of ways that messaging can be useful in the context of a Rails web application. See the Getting Started area on the main RabbitMQ web site for some ideas. But in this tutorial, we'll build a very simple application that uses RabbitMQ, focusing on the basics of connecting to the RabbitMQ service. Once you understand that, you will be able to integrate more realistic uses of the service into your Rails applications on Cloud Foundry.

This tutorial goes through the complete process of creating a very simple Rails application on Cloud Foundry, and hooking it up to the RabbitMQ service. If you already have your own applications on Cloud Foundry, much of this material may already be familiar. If so, you can skip straight to the last section, where we discuss how to access the RabbitMQ service from your application. You can also find the complete code for the finished application on GitHub.

The application we are going to build will consist of a single page, looking like this:

page.png

This application can publish messages to and get messages from a single RabbitMQ queue. We can type a message into the input box and click the Publish button to publish it to the queue. Alternatively, we can click the Get button to take a message from the queue; the next message will be displayed, or it will say that the queue was empty.

Prerequisites

To start off, there are a few things that need to be installed on your development machine. This guide will assume that you already have a reasonably recent version of Ruby 1.8 or 1.9 installed, and rubygems too. If that is not the case, please look for instructions specific to your operating system explaining how to get started with Ruby development.

Next, we need the vmc command line tool used to interact with Cloud Foundry. Unless this tutorial is your first contact with Cloud Foundry, you probably already have vmc installed. It is easily installed with rubygems:

$ gem install vmc
[...]
Successfully installed vmc-0.3.12
[...]

Next, set vmc to target the cloudfoundry.com site, and log in:

$ vmc target api.cloudfoundry.com
Successfully targeted to [http://api.cloudfoundry.com]
$ vmc login
vmc login
Email: me@example.com
Password: ******
Successfully logged into [http://api.cloudfoundry.com]

Creating an Initial Rails 3 App

Now we will create the Rails 3 app and push it to Cloud Foundry. Install Rails via rubygems:

$ gem install rails
[...]
Successfully installed rails-3.0.9
[...]

We will get rails to generate a new Rails application to use as our starting point. Here, I will call my application “rabbit-on-rails”, but you should choose your own name.

$ rails new rabbit-on-rails
      create
      create  README
      create  Rakefile
[...]
$ cd rabbit-on-rails

The simple application shown in this tutorial does not use a database. But Rails tends to assume that you are building a database-backed web application. As we can take advantage of a Cloud Foundry database service, it is easy to go along with that assumption. And in practice, Rails applications using the RabbitMQ messaging service will typically use a database too. We will use mysql as the database for our application.

In order to get Rails to use mysql on Cloud Foundry, we do not need to follow the usual path of modifying config/database.yml: Cloud Foundry automatically configures a Rails application to use any database that has been bound to it. So the only change we need to make to the generated application is to modify the Gemfile to reference the appropriate database driver gem. For mysql with Rails 3, we need the mysql2 gem, with a ~> 0.2.7 version constraint. Apart from comment lines, the resulting Gemfile looks like this:

source 'http://rubygems.org'

gem 'rails', '3.0.9'

gem 'sqlite3'
gem 'mysql2', '~> 0.2.7'

Then we need to run the bundle install command (this is part of the bundler gem, which will have been installed by rubygems as a dependency of rails). This will generate a Gemfile.lock file which tells Cloud Foundry the precise versions of gems needed for the application:

$ bundle install
Fetching source index for http://rubygems.org/
Using rake (0.9.2)
[...]
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

Now we are ready to push the application up to Cloud Foundry. As part of the push process, vmc push will ask us whether we want to use any services with our app. We will take this opportunity to create a mysql service instance. But we won't add the rabbitmq service at this point; we will do that later, after we have the initial app running.

$ vmc push
Would you like to deploy from the current directory? [Yn]: y
Application Name: rabbit-on-rails
Application Deployed URL: 'rabbit-on-rails.cloudfoundry.com'?
Detected a Rails Application, is this correct? [Yn]: y
Memory Reservation [Default:256M] (64M, 128M, 256M, 512M or 1G)
Creating Application: OK
Would you like to bind any services to 'rabbit-on-rails'? [yN]: y
Would you like to use an existing provisioned service [yN]? n
The following system services are available:
1. mongodb
2. mysql
3. rabbitmq
4. redis
Please select one you wish to provision: 2
Specify the name of the service [mysql-dfd5c]:
Creating Service: OK
Binding Service: OK
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (6K): OK
Push Status: OK
Staging Application: OK
Starting Application: OK

If this succeeds, you should be able to go to the URL for the application and see the Rails welcome page:

welcome.png

Making the application

Now that we have the initial Rails application running on Cloud Foundry, we will write the pieces we need to demonstrate the use of the RabbitMQ service. Our application will be very simple, with only one page. So we will get Rails to generate a controller for that page:

$ rails generate controller home index
      create  app/controllers/home_controller.rb
       route  get "home/index"
      invoke  erb
      create    app/views/home
      create    app/views/home/index.html.erb
      invoke  test_unit
      create    test/functional/home_controller_test.rb
      invoke  helper
      create    app/helpers/home_helper.rb
      invoke    test_unit
      create      test/unit/helpers/home_helper_test.rb

To make this appear as the front page for our application, we need to delete the welcome page in public/index.html, and then edit config/routes.rb to put our controller's index page at the root of the site:

RabbitOnRails::Application.routes.draw do
  root :to => "home#index"

  get "home/index"
end

Then we can update the application on Cloud Foundry, and take a look:

$ vmc update rabbit-on-rails
Uploading Application:
  Checking for available resources: OK
  Processing resources: OK
  Packing application: OK
  Uploading (5K): OK
Push Status: OK
Stopping Application: OK
Staging Application: OK
Starting Application: OK

home-index.png

Next we edit app/views/home/index.html.erb to make the web page for our application. This is a straightforward Rails view. Most of it is simple HTML and Rails form tags. But in a couple of pages we inspect the flash hash in order to show the user the results of their actions.

<h1>Rabbit On Rails</h1>

<h2>Publish a message</h2>

<% form_tag({:controller => "home", :action => "publish"}, :method => "post") do %>
  <%= label_tag(:message, "Message to publish:") %>
  <%= text_field_tag(:message) %>
  <%= submit_tag("Publish") %>
<% end %>

<% if flash[:published] %>
  <p>Published a message!</p>
<% end %>

<h2>Get a message</h2>

<% form_tag({:controller => "home", :action => "get"}, :method => "post") do %>
  <%= submit_tag("Get one") %>
<% end %>

<% if flash[:got] %>
  <p>Got message: <%= h(flash[:got]) %></p>
<% end %>

We also need to update config/routes.rb to define appropriate paths for the publish and get actions referred to in the view:

RabbitOnRails::Application.routes.draw do
  root :to => "home#index"

  get "home/index"
  match "publish" => "home#publish"
  match "get" => "home#get"
end

After another vmc update, we can see the page shown in the introduction above. In the next section we will fill out the code for the actions in the controller in order to make the application fully functional.

Using the RabbitMQ Service

In this section, we will take the simple application created in the previous section and make it use the RabbitMQ service.

First, we will create an instance of the RabbitMQ service on Cloud Foundry:

$ vmc create-service
1. rabbitmq
2. mysql
3. mongodb
4. redis
Please select one you wish to provision: 1
Creating Service [rabbitmq-aaaad]: OK

At this point, the service instance has been successfully created. But in order for it to be accessible to our application, we also need to bind it:

$ vmc bind-service rabbitmq-aaaad rabbit-on-rails
Binding Service: OK
Stopping Application: OK
Staging Application: OK
Starting Application: OK

The vmc apps command confirms that the services has been bound to our application:

$ vmc apps

+-----------------+----+---------+----------------------------------+-----------------------------+
| Application     | #  | Health  | URLS                             | Services                    |
+-----------------+----+---------+----------------------------------+-----------------------------+
| rabbit-on-rails | 1  | RUNNING | rabbit-on-rails.cloudfoundry.com | rabbitmq-aaaad, mysql-dfd5c |
+-----------------+----+---------+----------------------------------+-----------------------------+

Next, we need to add an AMQP client library to the application. AMQP is the main protocol used with to RabbitMQ (RabbitMQ supports versions 0.8 and 0.9.1 of AMQP).

Two popular AMQP client libraries for Ruby are the bunny and amqp gems. The amqp gem is based on the the EventMachine framework, and so is not a good fit for a Rails application. So we will use the bunny gem. As usual, we add it to our Gemfile:

source 'http://rubygems.org'

gem 'rails', '3.0.9'

gem 'sqlite3'
gem 'mysql2', '~> 0.2.7'

gem 'bunny'
gem 'json'

Here we have also added a reference to the json gem. This is a JSON parser. When Cloud Foundry presents information about the RabbitMQ service instance to our application, it formats it as JSON, and we will need to parse that.

As usual, after modifying Gemfile, do bundle install again to update Gemfile.lock:

$ bundle install
Fetching source index for http://rubygems.org/
[...]
Installing bunny (0.7.3)
Installing json (1.5.3) with native extensions
[...]
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

Now we edit app/controllers/home_controller.rb to add the actions, using the RabbitMQ service. The code below has been liberally commented to explain what is going on.

require 'bunny'
require 'json'

class HomeController < ApplicationController
  # The index action doesn't need to do anything
  def index
  end

  # Extracts the connection string for the rabbitmq service from the
  # service information provided by Cloud Foundry in an environment
  # variable.
  def self.amqp_url
    services = JSON.parse(ENV['VCAP_SERVICES'], :symbolize_names => true)
    url = services.values.map do |srvs|
      srvs.map do |srv|
        if srv[:label] =~ /^rabbitmq-/
          srv[:credentials][:url]
        else
          []
        end
      end
    end.flatten!.first
  end

  # Opens a client connection to the RabbitMQ service, if one isn't
  # already open.  This is a class method because a new instance of
  # the controller class will be created upon each request.  But AMQP
  # connections can be long-lived, so we would like to re-use the
  # connection across many requests.
  def self.client
    unless @client
      c = Bunny.new(amqp_url)
      c.start
      @client = c
    end
    @client
  end

  # Return the "nameless exchange", pre-defined by AMQP as a means to
  # send messages to specific queues.  Again, we use a class method to
  # share this across requests.
  def self.nameless_exchange
    @nameless_exchange ||= client.exchange('')
  end

  # Return a queue named "messages".  This will create the queue on
  # the server, if it did not already exist.  Again, we use a class
  # method to share this across requests.
  def self.messages_queue
    @messages_queue ||= client.queue("messages")
  end

  # The action for our publish form.
  def publish
    # Send the message from the form's input box to the "messages"
    # queue, via the nameless exchange.  The name of the queue to
    # publish to is specified in the routing key.
    HomeController.nameless_exchange.publish params[:message],
                                             :key => "messages"
    # Notify the user that we published.
    flash[:published] = true
    redirect_to home_index_path
  end

  def get
    # Synchronously get a message from the queue
    msg = HomeController.messages_queue.pop
    # Show the user what we got
    flash[:got] = msg[:payload]
    redirect_to home_index_path
  end
end

With the controller code in place, a final vmc update gives us the fully functional application. We can publish a message:

publish1.png publish2.png

And get it back:

get1.png get2.png

So far, this application doesn't demonstrate a compelling reason to use RabbitMQ. After all, you could easily write the application we have at this point using a database. The advantage of a messaging system is that you can also wait until a message arrives (without the need to query repeatedly).

We will now extend the program above so that when it gets a message, it will wait for one (unless a message is already in the queue, in which case it continues to return it immediately).

  def self.client
    unless @client
      c = Bunny.new(amqp_url)
      c.start
      @client = c

      # We only want to accept one un-acked message
      @client.qos :prefetch_count => 1
    end
    @client
  end

[...]

  def get
    flash[:got] = :queue_empty

    # Wait for a message from the queue
    HomeController.messages_queue.subscribe(:ack => true,
                                            :timeout => 10,
                                            :message_max => 1) do |msg|
      # Show the user what we got
      flash[:got] = msg[:payload]
    end

    redirect_to home_index_path
  end

We now call the subscribe method on the queue, using the :message_max option to ask it to terminate the subscription after a single message is received (in general, we can consume many messages).

Because we only want the RabbitMQ server to give us a single message, we also call the qos method to tell it to only deliver a single un-acked message to this client. The client should then acknowledge the message it receives in order to have another one delivered; the :ack option to subscribe tells it to take care of sending these acknowledgements to the server.

Now if we vmc update, and click on the Get one button, the request will block until a message arrives in the queue. You can then try to open the application in another browser tab, and publish a message. But you may encounter a problem: By default, Cloud Foundry will only create one instance of our application. And because Rails is single threaded, if the only instance is blocked on a request, no other requests can be handled. The solution is to ask Cloud Foundry to spin up some more instances:

$ vmc instances rabbit-on-rails 4
Scaling Application instances up to 4: OK

You should now be able to open the application in two browser tabs, and see messages passed between them. If you have a Get one waiting, and you publish a message, you should see it arrive promptly.

That concludes this tutorial. You can find more resources about RabbitMQ and AMQP on the RabbitMQ web site. And you can find more samples of the use of bunny in its examples. If you have questions or feedback on this tutorial or the RabbitMQ service, please contact us using the forums on support.cloudfoundry.com.

 
Topic is closed for comments