5 min read

Historically, a conventional Ruby on Rails application leverages server-side business logic, a relational database, and a RESTful architecture to serve dynamically-generated HTML. JavaScript-intensive applications and the widespread use of external web APIs, however, somewhat challenge this architecture. In many cases, Rails is tasked with performing as an orchestration layer, collecting data from various backend services and serving re-formatted JSON or XML to clients. In such instances, how is Rails’ model-view-controller architecture still relevant? In this two part post series, we’ll create a simple Rails backend that makes requests to an external XML-based web service and serves JSON. We’ll use RSpec for tests and Jbuilder for view rendering.

What are we building?

We’ll create Noterizer, a simple Rails application that requests XML from externally hosted endpoints and re-renders the XML data as JSON at a single URL. To assist in this post, I’ve created NotesXmlService, a basic web application that serves two XML-based endpoints:

http://NotesXmlService.herokuapp.com/note-one
http://NotesXmlService.herokuapp.com/note-two

Why is this necessary in a real-world scenario?

Fronting external endpoints with an application like Noterizer opens up a few opportunities:

  1. Noterizer’s endpoint could serve JavaScript clients who can’t perform HTTP requests across domain names to the original, external API.
  2. Noterizer’s endpoint could reformat the externally hosted data to better serve its own clients’ data formatting preferences.
  3. Noterizer’s endpoint is a single interface to the data; multiple requests are abstracted away by its backend.
  4. Noterizer provides caching opportunities. While it’s beyond the scope of this series, Rails can cache external request data, thus offloading traffic to the external API and avoiding any terms of service or rate limit violations imposed by the external service.

Setup

For this series, I’m using Mac OS 10.9.4, Ruby 2.1.2, and Rails 4.1.4. I’m assuming some basic familiarity with Git and the command line.

Clone and set up the repo

I’ve created a basic Rails 4 Noterizer app. Clone its repo, enter the project directory, and check out its tutorial branch:

$ git clone http://github.com/mdb/noterizer && cd noterizer && git checkout tutorial

Install its dependencies:

$ bundle install

Set up the test framework

Let’s install RSpec for testing.

Add the following to the project’s Gemfile:

gem 'rspec-rails', '3.0.1'

Install rspec-rails:

$ bundle install

There’s now an rspec generator available for the rails command. Let’s generate a basic RSpec installation:

$ rails generate rspec:install

This creates a few new files in a spec directory:

├── spec
│   ├── rails_helper.rb
│   └── spec_helper.rb

We’re going to make a few adjustments to our RSpec installation.

 First, because Noterizer does not use a relational database, delete the following ActiveRecord reference in spec/rails_helper.rb:

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!

Next, configure RSpec to be less verbose in its warning output; such verbose warnings are beyond the scope of this series. Remove the following line from .rspec:

--warnings

The RSpec installation also provides a spec rake task. Test this by running the following:

$ rake spec

You should see the following output, as there aren’t yet any RSpec tests:

No examples found.

Finished in 0.00021 seconds (files took 0.0422 seconds to load)
0 examples, 0 failures

Note that a default Rails installation assumes tests live in a tests directory. RSpec uses a spec directory. For clarity’s sake, you’re free to delete the test directory from Noterizer.

Building a basic route and controller

Currently, Noterizer does not have any URLs; we’ll create a single/notes URL route. 

Creating the controller

First, generate a controller:

$ rails g controller notes

Note that this created quite a few files, including JavaScript files, stylesheet files, and a helpers module. These are not relevant to our NotesController; so let’s undo our controller generation by removing all untracked files from the project. Note that you’ll want to commit any changes you do want to preserve.

$ git clean -f

Now, open config/application.rb and add the following generator configuration:

config.generators do |g|
  g.helper false
  g.assets false
end

Re-running the generate command will now create only the desired files:

$ rails g controller notes

Testing the controller

Let’s add a basic NotesController#index test to spec/controllers/notes_spec.rb. The test looks like this:

require 'rails_helper'

describe NotesController, :type => :controller do
  describe '#index' do
    before :each do
      get :index
    end

    it 'successfully responds to requests' do
      expect(response).to be_success
    end
  end
end

This test currently fails when running rake spec, as we haven’t yet created a corresponding route.

Add the following route to config/routes.rb

get 'notes' => 'notes#index'

The test still fails when running rake spec, because there isn’t a proper #index controller action.

 Create an empty index method in app/controllers/notes_controller.rb

class NotesController < ApplicationController
  def index
  end
end

rake spec still yields failing tests, this time because we haven’t yet created a corresponding view. Let’s create a view:

$ touch app/views/notes/index.json.jbuilder

To use this view, we’ll need to tweak the NotesController a bit. Let’s ensure that requests to the /notes route always returns JSON via a before_filter run before each controller action:

class NotesController < ApplicationController
  before_filter :force_json

  def index
  end

  private

  def force_json
    request.format = :json
  end
end

Now, rake spec yields passing tests:

$ rake spec
.

Finished in 0.0107 seconds (files took 1.09 seconds to load)
1 example, 0 failures

Let’s write one more test, asserting that the response returns the correct content type. Add the following to spec/controllers/notes_controller_spec.rb

it 'returns JSON' do
  expect(response.content_type).to eq 'application/json'
end

Assuming rake spec confirms that the second test passes, you can also run the Rails server via the rails server command and visit the currently-empty Noterizer http://localhost:3000/notes URL in your web browser.

Conclusion

In this first part of the series we have created the basic route and controller for Noterizer, which is a basic example of a Rails application that fronts an external API. In the next blog post (Part 2), you will learn how to build out the backend, test the model, build up and test the controller, and also test the app with JBuilder.

About this Author

Mike Ball is a Philadelphia-based software developer specializing in Ruby on Rails and JavaScript. He works for Comcast Interactive Media where he helps build web-based TV and video consumption applications.

LEAVE A REPLY

Please enter your comment!
Please enter your name here