





















































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.
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
Fronting external endpoints with an application like Noterizer opens up a few opportunities:
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.
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
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.
Currently, Noterizer does not have any URLs; we’ll create a single/notes URL route.
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
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.
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.
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.