Grape API Mounted on RACK w/ Static Pages, Tests, JQuery UI, Backbone.js and Even Mongo

Back | rack, mongoid, jquery, javascript, grape | 2/22/2012 |

Expanding from my previous post on a Grape API mounted on RACK.

Refactoring the Application Instance

Instead of sticking all of the Rack application code into config.ru, lets build a cleaner Acme::App (in app/acme_app.rb). We’re going to drop Rack::TryStatic and build this logic ourselves, since we might need to deal with other error codes than 404 (depending on your URL strategy you may be tripping over a 405). The logic remains the same: we try a bunch of static files and delegate to the API otherwise. You can also build primitive routing instead, so that everything requesting /api goes to the API and everything else goes to Rack::Static. Your mileage will vary.

  1. module Acme
  2.   class App
  3.     def initialize
  4.       @filenames = [ '', '.html', 'index.html', '/index.html' ]
  5.       @rack_static = ::Rack::Static.new(
  6.         lambda { [404, {}, []] }, {
  7.           :root => File.expand_path('../../public', __FILE__),
  8.           :urls => %w[/]
  9.         })
  10.     end
  11.     def call(env)
  12.       request_path = env['PATH_INFO']      
  13.       # static files
  14.       @filenames.each do |path|
  15.         response = @rack_static.call(env.merge({'PATH_INFO' => request_path + path}))
  16.         return response if response[0] != 404
  17.       end
  18.       # api
  19.       Acme::API.call(env)
  20.     end
  21.   end
  22. end

RSpec API Tests

Now that we have an application class, we can add API and Capybara integration tests. We start with RSpec and Rack test gems in Gemfile.

  1. group :test do
  2.   gem "rspec"
  3.   gem "rack-test"
  4.   gem "rspec-core"
  5.   gem "rspec-expectations"
  6.   gem "rspec-mocks"
  7. end

The spec/spec_helper.rb adds Rack::Test.

  1. require 'rubygems'
  2.  
  3. ENV["RACK_ENV"] ||= 'test'
  4.  
  5. require 'rack/test'
  6.  
  7. require File.expand_path("../../config/environment", __FILE__)
  8.  
  9. RSpec.configure do |config|
  10.   config.mock_with :rspec
  11.   config.expect_with :rspec
  12. end

Testing an API involves making requests on the Rack application, pretty straightforward.

  1. require 'spec_helper'
  2.  
  3. describe Acme::API do
  4.   include Rack::Test::Methods
  5.  
  6.   def app
  7.     Acme::API
  8.   end
  9.     
  10.   context "v1" do
  11.     context "system" do
  12.       it "ping" do
  13.         get "/api/v1/system/ping"
  14.         last_response.body.should == { :ping => "pong" }.to_json
  15.       end
  16.     end
  17.   end
  18.  
  19. end

RSpec Capybara Integration Tests

Notice that in the tests above we’re mounting the Rack application and making requests directly to it. Does it actually work in a browser? Do we see the public/index.html page?

We start by adding capybara into Gemfile. At the time of the writing we need to use the code from Capybara head, since it adds support for Capybara.app.

  1. group :test do
  2.   gem "capybara", :git => "https://github.com/jnicklas/capybara.git"
  3. end

The spec/spec_helper.rb requires capybara/rspec, which brings in methods like page.visit and assigns an instance of the application to Capybara.app. Capybara will launch the application for us.

  1. require 'capybara/rspec'
  2. Capybara.configure do |config|
  3.   config.app = Acme::App.new
  4. end

An integration test can go into spec/integration and must be marked with request: true and js: true (the latter forces the use of the Selenium driver that will popup a browser). Lets look for a proper title on the homepage.

  1. require 'spec_helper'
  2.  
  3. describe "Grape on RACK", :js => true, :type => :request do
  4.   context "homepage" do
  5.     before :each do
  6.       visit "/"
  7.     end
  8.     it "displays index.html page" do
  9.       page.find("title").text.should == "Rack Powers Web APIs"
  10.     end
  11.   end
  12. end

A POST, PUT and Some JQuery

The sample source in https://github.com/dblock/grape-on-rack also adds JQuery, extends the API to simulate a persisted counter, and makes PUT requests to it. Complete with an integration test. Run bundle install and bundle exec rackup to see it and bundle exec rspec spec to run the tests.

Backbone.js w/ Mongo

@knewter put together a neat Backbone.js + MongoDB w/ Mongoid demo using Grape that is built in a similar manner, https://github.com/knewter/grape_demo.