README.md

Restfulness

Because REST APIs are all about resources, not routes.

Introduction

Restfulness is an attempt to create a Ruby library that helps create truly REST based APIs to your services. The focus is placed on performing HTTP actions on resources via specific routes, as opposed to the current convention of assigning routes and HTTP actions to methods or blocks of code. The difference is subtle, but makes for a much more natural approach to building APIs.

The current version is very minimal, as it only support JSON content types, and does not have more advanced commonly used HTTP features like sessions or cookies. For most APIs this should be sufficient.

To try and highlight the diferences between Restfulness and other libraries, lets have a look at a couple of examples.

Grape is a popular library for creating APIs in a "REST-like" manor. Here is a simplified section of code from their site:

The focus in Grape is to construct an API by building up a route hierarchy where each HTTP action is tied to a specific ruby block. Resources are mentioned, but they're used more for structure or route-seperation, than a meaningful object.

Restfulness takes a different approach. The following example attempts to show how you might provide a similar API:

I, for one, welcome our new resource overloads. They're a clear and consise way of separating logic between different classes, so an individual model has nothing to do with a collection of models, even if the same model may be provided in the result set.

Installation

Add this line to your application's Gemfile:

gem 'restfulness'

And then execute:

$ bundle

Or install it yourself as:

$ gem install restfulness

Usage

Defining an Application

A Restfulness application is a Rack application whose main function is to define the routes that will forward requests on a specific path to a resource. Your applications inherit from the Restfulness::Application class. Here's a simple example:

The add router method can also except a block which will be interpreted as a scope. The following example will provide the same paths as the journeys scope and resource defined above. The most important factor to take into account is that the Journeys::ListResource will be added to the route after the active and terminated resources. Order is important!

Resources

Resources are like Controllers in a Rails project. They handle the basic HTTP actions using methods that match the same name as the action. The result of an action is serialized into a JSON object automatically. The actions supported by a resource are:

get

head

post

patch

put

delete

options - this is the only action provded by default

When creating your resource, simply define the methods you'd like to use and ensure each has a result:

Checking which methods are available is also possible by sending an OPTIONS action. Using the above resource as a base:

curl -v -X OPTIONS http://localhost:9292/project

Will include an Allow header that lists: "GET, PUT, OPTIONS".

Resources also have support for simple set of built-in callbacks. These have similar objectives to the callbacks used in Ruby Webmachine that control the flow of the application using HTTP events.

The supported callbacks are:

exists? - True by default, not called in create actions like POST or PUT.

authorized? - True by default, is the current user valid?

allowed? - True by default, does the current have access to the resource?

last_modified - The date of last update on the model, only called for GET and HEAD requests. Validated against the If-Modified-Since header.

etag - Unique identifier for the object, only called for GET and HEAD requests. Validated against the If-None-Match header.

To use them, simply override the method:

classProjectResource < Restfulness::Resource# Does the project exist? only called in GET requestdefexists?!project.nil?
end# Return a 304 status if the client can used a cached resourcedeflast_modified
project.updated_at.to_s
end# Return the basic objectdefget
project
end# Update the objectdefpostProject.create(params)
endprotecteddefproject@project||=Project.find(request.path[:id])
endend

I18n in Resources

Restfulness uses the http_accept_language gem to automatically handle the Accept-Language header coming in from a client. After trying to make a match between the available locales, it will automatically set the I18n.locale. You can access the http_accept_language parser via the request.http_accept_language method.

For most APIs this should work great, especially for mobile applications where this header is automatically set by the phone. There may however be situations where you need a bit more control. If a user has a preferred language setting for example.

Resources contain two protected methods that can be overwritten if you need more precise control. This is what they look like in the Restfulness code:

The Resource#set_locale method is called before any of the other callbacks are handled. This is important as it allows the locale to be set before returning any translatable error messages.

Most users will probably just want to override the Resource#locale method and provide the appropriate locale for the request. If you are using a User object or similar, double check your authentication process as the default authorized? method will be called after the locale is prepared.

Authentication in Resources

Restfulness now provides very basic support for the HTTP Basic Authentication. To use it, simply call the authenticate_with_http_basic method in your resource definition.

Here's an example with the authentication details in the code, you'd obviously want to use something a bit more advanced than this in production:

The request object provided in the resource, described below, provides access to the HTTP Authorization header via the Reqest#authorization method. If you want to use an alternative authentication method you can use this to extract the details you might need. For example:

We don't yet provide support for Digest authentication, but your contributions would be more than welcome. Checkout the HttpAuthentication/basic.rb source for an example.

Restfulness doesn't make any provisions for requesting authentication from the client as most APIs don't really need to offer this functionality. You can acheive the same effect however by providing the WWW-Authenticate header in the response. For example:

Requests

All resource instances have access to a Request object via the #request method, much like you'd find in a Rails project. It provides access to the details including in the HTTP request: headers, the request URL, path entries, the query, body and/or parameters.

Restfulness takes a slightly different approach to handling paths, queries, and parameters. Rails and Sinatra apps will typically mash everything together into a params hash. While this is convenient for most use cases, it makes it much more difficult to separate values from different contexts. The effects of this are most noticable if you've ever used Models Backbone.js or similar Javascript library. By default a Backbone Model will provide attributes without a prefix in the POST body, so to be able to differenciate between query, path and body parameters you need to ignore the extra attributes, or hack a part of your code to re-add a prefix.

The forbidden! bang method will call the error! method, which in turn will raise an HTTPException with the appropriate status code. Exceptions are permitted to include a payload also, so you could override the error! method if you wished with code that will automatically re-format the payload. Another example:

This can be a really nice way to mold your errors into a standard format. All HTTP exceptions generated inside resources will pass through error!, even those that a triggered by a callback. It gives a great way to provide your own JSON error payload, or even just resort to a simple string.

The currently built in error methods are:

not_modified!

bad_request!

unauthorized!

payment_required!

forbidden!

resource_not_found!

request_timeout!

conflict!

gone!

unprocessable_entity!

If you'd like to see me more, please send us a pull request. Failing that, you can create your own by writing something along the lines of:

defim_a_teapot!(payload="")
error!(418, payload)
end

Reloading

We're all used to the way Rails projects magically reload files so you don't have to restart the server after each change. Depending on the way you use Restfulness in your project, this can be supported.

The Rails Way

Using Restfulness in Rails is the easiest way to take advantage support reloading.

The recomended approach is to create two directories in your Rails projects /app path:

/app/apis can be used for defining your API route files, and

/app/resources for defining a tree of resource definition files.

Add the two paths to your rails autoloading configuration in /config/application.rb, there will already be a sample in your config provided by Rails:

# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths +=%W(#{config.root}/app/resources #{config.root}/app/apis )

Your Resource and API files will now be autoloadable from your Rails project. The next step is to update our Rails router to be able to find our API. Modify the /config/routes.rb file so that it looks something like the following:

You'll see in the code sample that we're only loading the Restfulness API during development. Our recommendation is to use Restfulness as close to Rack as possible and avoid any of the Rails overhead. To support request in production, you'll need to update your /config.rb so that it looks something like the following:

Thats all there is to it! You'll now have auto-reloading in Rails, and fast request handling in production. Just be sure to be careful in development that none of your other Rack middleware interfere with Restfulness. In a new Rails project this certainly won't be an issue.

The Rack Way

If you're using Restfulness as a standalone project, we recommend using a rack extension like Shotgun.

Writing Tests

Test your application by creating requests to your resources and making assertions about the responses.

RSpec

Configure rack-test to be included in your resource specs. One way to does this would be to create a new file /spec/support/example_groups/restfulness_resource_example_group.rb with something similar to the following:

moduleRestfulnessResourceExampleGroupextendActiveSupport::ConcernincludeRack::Test::Methods# Used by Rack::Test. This could be defined per spec if you have multiple AppsdefappMy::Api.newendprotected:app# Set the request content type for a JSON payloaddefset_content_type_json
header('content-type', 'application/json; charset=utf-8')
end# Helper method to POST a json payload# post(uri, params = {}, env = {}, &block)defpost_json(uri, json_data= {}, env= {}, &block)
set_content_type_json
post(uri, json_data.to_json, &block)
end
included do
metadata[:type] =:restfulness_resourceend# Setup RSpec to include RestfulnessResourceExampleGroup for all specs in given folder(s)RSpec.configure do |config|
config.includeself,
:type => :restfulness_resource,
:example_group => { :file_path => %r(spec/resources) }
end# silence loggerRestfulness.logger =Rack::NullLogger.new(My::Api)
end

Make sure in your spec_helper all files in the support folder and sub-directories are being loaded. You should have something like the following:

Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

Now you can add a resource spec in the spec/resources directory. Here's an example