RESTful architectures follow a set of principles defining how agents can communicate by exchanging resource representations.
This set of general rules has created tremendous possibilities for applications to exchange streams of data and contribute to building a Web that is more open and accessible.
In the Dark Ages of the Web it was common to meet websites that would declare their preferred, or best supported, browsers. “This website is best viewed with <browser_name_and_version>” was a popular tagline to insert in your page.
It wasn’t only a matter of preferences. Every browser would parse and display HTML code differently, and some features wouldn’t work as expected. Although we have left the Middle Ages of the Internet (mostly), web developers continue to struggle to create web applications that behave consistently across a wide range of devices.
It is important to note that the Web was invented to make computers able to communicate with each other easily, so that humans and software agents alike could act on the information shared across the system. The Web, and subsequently REST, was therefore designed with the core principles of simplicity and uniformity repeated and implemented at different layers of its architecture.
An API is an interface specifying how some software components should interact with each other. A RESTful API is therefore just a REST interface exposing some internal functionality of a software system to developers and external applications. These can now consume and interact with specific resources through the API itself.
APIs are not exactly a novelty, or something that has been developed only in the web world. In fact, an API simply specifies a set of functions that can be used to interact with some components. These actions and components can be a database system that needs to be accessed by a web server, or a video card exposing some of its functionality through a software library. In REST architectures, APIs define actions that access or modify resources through the exchanged representations (Figure 3-1).
The API design process therefore starts with some application data that needs to be accessed by one or a collection of clients or services that need to act on the data.
APIs have become a fundamental aspect of software development. Exposing some functionality through an API hides internal system complexity from the user and enforces separation of concerns.
APIs are starting to be fundamental to product development and business execution, so much so that the concept of API-first development is rapidly being adopted.
Developing the product API before anything else actually means defining how users are going to interact with our platform. The API is the skeleton of an application, and in the case of distributed applications, the API defines the way the app interacts with the outside world.
Designing the API before anything else will enable you to start customer discussions and create user stories early on, and use the API to quickly create your app interface mockups.
API-first development can be summarized as the idea that a service should be exposed as a RESTful API to the outside world before anything else. An API-first strategy encourages the use and adoption of your platform amongst developers, while also making access and integration easier for potential customers and partners.
Rather than having to import a specific library or plug-in in their code bases, developers can interface through the API to consume all the required functionality. The API is an application interface that enables developers and services to use your product, consume your data, and easily integrate your service.
One of the easier examples of API-first development is the separation of the backend of an application from its frontend. An API could be built to expose the application data and the actions to act on the app modules, hence operating at the backend level, while the application frontend could just consume the API and present the data to the final user.
There are many more possible examples. For instance, microservices architectures expose different components of their systems through APIs that are implemented and consumed not only by external applications, but also by other components.
In this chapter we are going to start designing an API. Although we are not going to implement hypermedia controls just yet, we will start building our API as a RESTful service, implementing all the principles and architectural constraints of the REST architectural style.
Our service will be developed organically and incrementally. We will start mapping our application data to resources and actions, proceeding with modularity and possible encapsulation of different modules in mind to easily enable resource exploration.
Although there are different opinions and lively discussions on what the best practices are for designing and developing an API, our objective is to adopt a pragmatic approach and cover the topics that will lead to informative decisions through the process of creating and deploying a simple application and its programming interface.
Throughout this chapter and the next ones, we will take the point of view of building microapplications while adopting many microservices paradigms and borrowing some service-oriented architecture (SOA) patterns. We will see how these microapps can cooperate in more complex systems and integrated together with external APIs.
That said, let’s start designing and developing our first API.
There are many dos (and don’ts) of API development, but some essential requirements revolve around just a few points that can be considered the “good coding standards” of API development. These are the basics that every programmer should take into consideration.
For every complex problem there is an answer that is clear, simple, and wrong.
Henry Louis Mencken,
American journalist
KISS is an acronym that stands for “Keep It Stupid Simple” or “Keep It Simple, Stupid,” or even the more polite “Keep It Simple and Straightforward.” KISS describes a design principle according to which simplicity should be a key goal, and unnecessary complexity should be avoided. KISS is a mantra that is sometimes repeated over and over and can be easily abused.
Although keeping any design neat, clean, and uncomplicated is usually a good thing, you will sooner or later face a case in which a software project or IT system carries an inherent complexity that cannot be eliminated and inevitably will be carried over from the initial requirements to the final result.
Also, any API grows in size and complexity as the number of use cases grows, and maintaining a clear interface that users can feel comfortable with over time is certainly a challenge. However, this does not mean that size and complexity are fundamentally related and it will not be possible to scale the methods and actions of a large or complex API.
Therefore, the KISS concept could be described as stating that a good API is easy to read, to use, and to extend, and is complete and consistent.
Another acronym often encountered in software development is “Don’t Repeat Yourself” (DRY). The DRY principle was formulated by Andy Hunt and Dave Thomas in their book The Pragmatic Programmer, and states that “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
Rails provides a set of tools to “DRY up your code” and avoid duplication, including partials and filters. Partials are used in views and filters in controllers.
From a REST architectural point of view, KISS and DRY translate at the API level to the constraints of uniformity, modularity, and extendability. Both can be summarized into two fundamental principles of API design in particular and system development in general:
Adopting a simple syntax means making any user’s life easier. Start from a base URL that is simple and intuitive. A straightforward base URL will encourage early adopters to just try simple operations with your API (without needing to dive into tedious documentation), thus creating a sense of instant gratification. The rule of thumb in this case is: if you cannot call your API from cURL with a neat and quick line of shell, then maybe your API isn’t ready yet and you should rethink one or two of your design choices.
Another aspect to consider is that different people will find different syntax more or less natural to use and remember. A number of syntax choices might just be subjective, so you should always be consistent, and once you choose a convention, stick with it. Don’t use mixed upper- and lowercase characters to name a resource; be consistent by using lowercase only. Don’t confuse your users with the use of dashes and underscores; keep your resource names short and concise. Another good habit is to use concrete plural nouns when defining resources, avoiding, for example, verbs. If you try to describe method calls to address possible actions regarding a certain resource, you will end up with a long list of URLs and no consistency.
Resource URLs need to be focused on two main things:
Create a small number of powerful commands. Your API methods should be self-documenting, and hence easy to understand. Your users should not need to read several pages of documentation to start using your service, and you should not have exposed your internal models and methods. Use HTTP verbs to operate on collections and elements, and avoid putting verbs in your base URLs.
URI design is a problem of syntax and semantics. URIs define a hierarchical and logical structure for accessing resources in your API and acting on them through actions.
The very first step in URI design is deciding on your API endpoint and versioning method (i.e., subdomain or directory):
With subdomain consolidation you can easily run your API completely decoupled from everything else and concentrate all API requests under one API subdomain. Generally speaking, subdomain consolidation makes things cleaner, easier, and more intuitive, while also providing an API gateway for developers wishing to explore the documentation and start using your service.
Another option is to serve all your API documentation with your dev subdomain and use the api/ directory as your endpoint. This is also a common approach.
Versioning is usually indicated right after the endpoint through directory structures. This way, if you were going to build a different version of your API, the API version, like v1/ or v2/, would be the root of your URI tree.
Versioning deals with the problem of keeping your API both forward and backward compatible, at least for a certain amount of time. This is particularly important so that you can make the changes you need to your service, without telling your developers they will lose access or their applications will be unusable if they do not update within a certain time of the release date of your upgraded product. API versioning opens up some issues regarding API compatibility and the future of the service you are designing and planning to develop. You are in fact trying to plan for and foresee an unknown future that will inherently bring unknown changes to the service you are designing at present. Generally speaking, you can adopt two opposite strategies: not doing any versioning and actually doing versioning. Of course, you are free to decide not to do any versioning, but in case you will want to provide different versions of your API, here is what you should consider.
First of all, you should decide where you want to version your API. Some APIs are versioned directly in the endpoint, but versioning with a directory makes it easier to change and evolve your URI structure. There are several possibilities here. You can version in the URL:
Or you can version in the HTTP request:
HTTP content negotiation is a mechanism defined by the HTTP protocol specification that allows a web server to return a different version of a document (or, more generally, a different resource representation) through the same URI. It was designed such that a user agent, like an application or a web browser, can specify which version of the same resource best fits its capabilities. This is defined in the Accept header of the HTTP request.
An interesting read about content negotiation is provided by the Mozilla Developer Network.
You can also version through the content of your response, like with XML namespaces or HTML doctype declarations. This can be done through server-side or transparent content negotiation techniques.
Another aspect you should consider is what you want to version. For example:
Usually a single version digit is used, although the common software practice of decimal notation sometimes extends into API versioning and most of the time there are minor changes for minor versions.
Always, always, always version your API. Versioning is one of the most important (yet sometimes overlooked) aspects of designing an API. Versioning appropriately makes iterating faster, preventing invalid requests from hitting outdated endpoints.
It also helps in providing a smoother service through any API version transition, since you can continue offering the old version for a given period of time, while your users update their applications.
Following the API version, the URI structure starts identifying resources. It is often said that concrete names to identify resources are better than abstract ones, although API architects are sometimes obsessed with abstraction. In general, taking a pragmatic approach can help in situations like this. If you think about your users, they would probably prefer to see self-documenting URIs like api.example.com/v1/products or api.example.com/stores instead of something more abstract like api.example.com/v1/objects or api.example.com/v1/items, even if by going through your documentation they will eventually understand that every item or object in your application is either a product or a store.
Abstraction is hence not always useful to developers, and could actually lead to confusing URI structures. The level of abstraction that you might want to achieve will vary based on your application or service architecture.
A pragmatic API architect should consider a balance between concrete names and abstraction, keeping the number of resources in the namespace from growing beyond a healthy limit of maybe 20 or 30 different resources in most cases. A good approach in this case is considering that users will have to remember what URIs they should use to consume a specific resource (or at least, that they will probably try to do so). Limit your URI space as much as possible for the sake of simplicity and usability.
During the API design process, and specifically while you are deciding on your URI structure, you should concentrate on the resources you want developers to access, and how this should be accomplished, always remembering that good URI design improves usability and therefore community adoption. Let’s look at a few examples. This will list all products:
$
curl
api
.
example
.
com
/
v1
/
products
This will show product 21345:
$
curl
api
.
example
.
com
/
v1
/
products
/
21345
And this will list all sneaker products for brand “sneak”:
$
curl
api
.
example
.
com
/
v1
/
products?
type
=
sneakers
&
amp
;
brand
=
sneak
This last example illustrates the use of a search query to list all products of a certain type and manufacturer. That is, we used attribute parameters to search through and filter our products resource. One might thus think that query parameters can also be used to alter the state of a resource. You must not do this! You must instead use HTTP verbs to alter state. Remember that anyone can call a URI through a browser (or a crawler), so you do not want to use GET requests to delete or modify resources. Otherwise, even a simple crawler, or the Google bot, could end up compromising your service and deleting resources from your database simply by visiting a URI.
The API created in the previous chapter was a simple “Hello Rails” application with a small twist. If a parameter user
is passed to the URL, the API will say “Hello <user>” instead. In this chapter we are going to design a more complex API, starting from some application data.
You may have been asking yourself if using Rails just to generate some JSON isn’t maybe overkill. Why not use something more lightweight like Sinatra instead?
While this may be true, especially for very simple APIs, there are two aspects of developing a RESTful API to take into consideration:
Rail’s default middleware layer handles many aspects of an application’s lifecycle. It includes the following features:
While these features can certainly be built in Rack middleware, the default Rails middleware stack already provides them out of the box. Therefore, even if your application is “just generating JSON,” Rails delivers value from the moment you run rails new <myapp>
.
Rack provides a minimal interface between web servers supporting Ruby and Ruby frameworks. Rack wraps HTTP requests and responses in the simplest way possible; it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
More information on Rack can be found at https://github.com/rack/rack.
Rails also provides a framework for handling and responding to web requests: Action Pack. In the MVC paradigm, Action Pack represents the VC part. Therefore, some aspects handled at the Action Pack layer are:
This means you will not have to spend time thinking about how to design your API in terms of HTTP requests. You will not have to think about URL generation either. Everything will be cleanly and conventionally mapped from HTTP to controllers by the Rails router.
Rails supports three types of authentication out of the box: basic, digest, and token.
Active Support provides an instrumentation API allowing developers to provide hooks that other developers may subscribe to.
Also, ActiveSupport provides generators to quickly create models, controllers, routes, and test stubs, and supports plug-ins and third-party libraries that reduce the cost of setting up an application or prototyping a new feature.
In short, Rails has a lot to offer even if we strip out the view layer.
The application we are going to design uses the Wikipedia category system to produce the category tree of a given keyword. The Wikipedia category system is a taxonomy, or classification of concepts (in this case, the Wikipedia articles).
The Wikipedia category system is the result of the activity of all users editing and classifying Wikipedia entries. The result is a graph of categories that isn’t strictly a tree or a hierarchy, but allows multiple classifications of topics simultaneously, meaning some categories might have more than one supercategory.
The Wikipedia category system can be considered as a thesaurus that is collaboratively developed and used by crowds for indexing Wikipedia articles.
We are not going to use Wikipedia’s entire database for our API; we are just going to pretend we have some prepopulated data with category extracts and links between categories.
Given this data, we are going to import it in our app, and subsequently we are going to create the models and controller to access and act on it.
With our API, we will aim to find all subcategories of a given category that is passed as a parameter in the URL. In the next few chapters we will continue extending our API and develop it incrementally as a RESTful interface for the Wikipedia category system.
Free copies of all available content on Wikipedia are available for users and multi-licensed under the Creative Commons Attribution-ShareAlike 3.0 License (CC-BY-SA) and the GNU Free Documentation License (GFDL).
Snapshots of Wikipedia categories and Wikipedia category links pre-prepared to be imported into this application can be found at the WikiCat GitHub repository.
Because the English category links table contains a large number of records, an extract of the whole dump has also been prepared for testing purposes, so that you do not have to wait several hours for the dump to be imported into your development database.
Wikipedia uses MySQL for its database, so although the dumps can be translated to another SQL dialect or NoSQL language, we are going to use MySQL for the WikiCat API.
The first step is making sure that MySQL is installed on your system. Run:
$
mysql
-
-
version
mysql
Ver
14
.
14
Distrib
5
.
6
.
20
,
for
osx10
.
9
(
x86_64
)
using
EditLine
wrapper
It may be the case that MySQL is not installed on your system. In this case, you will have to install it.
Mac OS X users can use brew and run:
$
brew
install
mysql
On Linux you will probably have to install the necessary libraries first:
$
apt
-
get
install
libmysqlclient18
libmysqlclient
-
dev
Then install MySQL Server:
$
apt
-
get
install
mysql
-
client
-
5
.
5
mysql
-
server
-
5
.
5
On Windows, you can use MySQL Installer. Instructions can be found in the MySQL documentation.
MySQL Installer simplifies the installation and updating process for a set of MySQL products. It allows the user to see which products have been installed, and configure, update, or remove them if needed. Through the installer it is also possible to install plug-ins, documentation, and tutorials; it includes a GUI and a command-line interface.
Once you’ve made sure MySQL is installed on your system and tested it is working with the command mysql --version
, it is time to install the mysql gem for Rails, to allow our Rails app to interface with our database. Run:
$
gem
install
mysql
Now we are going to create a Rails app with MySQL support instead of SQLite3. Because we want to create an API-only application, we are going to use Rails::API. To install rails-api, run:
$
gem
install
rails
-
api
The application can then be generated by running:
$
rails
-
api
new
wikicat
-
d
mysql
As before, the command will create our Rails application.
Rails::API generates a subset of a normal Rails application. It was designed specifically for creating API-only apps, where you usually don’t need the entire Rails middleware.
Now that our application has been created, we have to initialize the database:
$
cd
wikicat
$
rake
db
:
create
rake db:create
creates our development and test databases. The command is run only the first time the database is created. Subsequently we will instruct our application to run the specified database migrations. We’ll discuss this in the next section.
Once our database has been prepared, we can start coding our application logic.
The categories table contains the following columns:
cat_id
:
<
Integer
>
,
cat_title
:
<
String
>
;,
cat_pages
:
<
Integer
>
,
cat_subcats
:
<
Integer
>
,
cat_files
:
<
Integer
>
;
Each of these columns describes a property of a certain category. Let’s take a look at these in order:
cat_id
cat_title
cat_pages
cat_subcats
cat_files
We are going to generate the Category
model accordingly:
$
rails
generate
model
Category
cat_id
:integer
cat_title
:string
cat_pages
:integer
cat_subcats
:integer
cat_files
:integer
The command will generate the Category
model and a number of defaults:
invoke
active_record
create
db
/
migrate
/
20140821090455
_create_categories
.
rb
create
app
/
models
/
category
.
rb
invoke
test_unit
create
test
/
models
/
category_test
.
rb
create
test
/
fixtures
/
categories
.
yml
You can also simply use rails g
instead of writing the entire command rails generate
.
The first file to be generated is the migration file to create the categories table. Migrations are a feature provided by Active Record to evolve the database schema over time, in a consistent and easy way. The idea is that the developer doesn’t have to write schema modification code in SQL, but can use Ruby syntax to describe the changes he wants to apply to the database or a single table.
The database schema initially has nothing in it; consequently, each time a migration is run, tables, columns, or single entries are added to or removed from the database. Active Record will then update the schema accordingly, and modify the db/schema.rb file to match the up-to-date structure.
Migrations are stored in the db/migrate folder and sorted by time of creation. A timestamp is added to the filename.
We can now open our first migration file to check how it looks:
# db/migrate/<timestamp>_create_categories.rb
class
CreateCategories
>
ActiveRecord
:
:Migration
def
change
create_table
:categories
do
|
t
|
t
.
integer
:cat_id
t
.
string
:cat_title
t
.
integer
:cat_pages
t
.
integer
:cat_subcats
t
.
integer
:cat_files
t
.
timestamps
end
end
end
If we want to add more columns to our tables, we can generate a migration file. Or, as we have not applied our migration yet, we can just edit our create_categories migration file.
What we want to do is use the Wikipedia category ID as the primary ID, without generating another ID field. Also, we want to add the created_at
and updated_at
columns. We will therefore modify the migration file:
# db/migrate/<timestamp>_create_categories.rb
class
CreateCategories
>
ActiveRecord
:
:Migration
def
change
create_table
:categories
,
{
:id
=&
gt
;
false
}
do
|
t
|
t
.
integer
:cat_id
t
.
string
:cat_title
t
.
integer
:cat_pages
t
.
integer
:cat_subcats
t
.
integer
:cat_files
t
.
date
:created_at
t
.
date
:updated_at
t
.
timestamps
end
execute
"ALTER TABLE categories ADD PRIMARY KEY (cat_id);"
end
end
Now we are going to create the Link
model. We again start with the Wikipedia SQL dump to see what the CategoryLinks
table looks like:
cl_from
:
<
Integer
>
,
cl_to
:
<
String
>
,
cl_sortkey
:
<
VarBinary
>
,
cl_timestamp
:
<
Date
>
,
cl_sortkey_prefix
:
<
VarBinary
>
cl_collation
:
<
VarBinary
>
cl_type
:
<
String
>
Again, each of these columns describes a property of a certain category link:
cl_from
cl_to
cl_sortkey
cl_timestamp
cl_sortkey_prefix
cl_sortkey
.cl_collation
cl_type
In the Wikipedia SQL dump, columns are specified as VARBINARY
. In MySQL the BINARY
and VARBINARY
types can be compared to CHAR
and VARCHAR
; the difference is that they contain binary strings instead of nonbinary strings. That is, a BINARY
or VARBINARY
type contains a byte string rather than a character string. Since byte strings have no character set, operations like sorting and comparison are based on the numeric values of the bytes in the string.
In Rails, specifying a column as binary generates a BLOB column in MySQL. A BLOB is a binary large object that can hold a variable amount of data. BLOB values are treated by the database as byte strings, but they are a bit different from VARBINARY
objects. Since they have no character set, for our simple API we will consider the two types equivalent, although you should know that they are allocated differently than any other MySQL column type. Please refer to the MySQL documentation for more information.
Starting from the SQL dump schema of the categorylinks
table, we are going to generate the Link
model:
$
rails
generate
model
Link
cl_from
:integer
cl_to
:string
cl_sortkey
:binary
cl_timestamp
:date
cl_sortkey_prefix
:binary
cl_collation
:binary
cl_type
:string
Let’s take a look at our new migration file:
# db/migrate/<timestamp>_create_links.rb
class
CreateLinks
>
ActiveRecord
:
:Migration
def
change
create_table
:links
do
|
t
|
t
.
integer
:id
t
.
integer
:cl_from
t
.
string
:cl_to
t
.
binary
:cl_sortkey
t
.
date
:cl_timestamp
t
.
binary
:cl_sortkey_prefix
t
.
binary
:cl_collation
t
.
string
:cl_type
t
.
timestamps
end
end
end
Again, we want to slightly modify the create_links migration file before applying it. We will edit the file as follows:
# db/migrate/<timestamp>_create_links.rb
class
CreateLinks
>
ActiveRecord
:
:Migration
def
change
create_table
:links
,
{
:id
=&
gt
;
false
}
do
|
t
|
t
.
integer
:cl_from
t
.
string
:cl_to
t
.
binary
:cl_sortkey
t
.
date
:cl_timestamp
t
.
binary
:cl_sortkey_prefix
t
.
binary
:cl_collation
t
.
string
:cl_type
t
.
date
:created_at
t
.
date
:updated_at
t
.
timestamps
end
execute
"ALTER TABLE links ADD PRIMARY KEY (cl_from);"
end
end
We can now run the migrations:
$
rake
db
:
migrate
At this point we have two choices. The first one would be to import the two SQL dumps from Wikipedia. This will take a while, and you probably just want to go ahead with coding the WikiCat API at this point.
If you want to go ahead with the MySQL dumps, though, you can download the preprocessed zipped files.
Then unzip the file and run:
$
mysqld
&
$
mysql
-
u
root
-
p
wikicat_development
<
~
/
Downloads
/en
wiki
-
latest
-
category
.
sql
$
mysql
-
u
root
-
p
wikicat_development
<
~
/
Downloads
/en
wiki
-
latest
-
categorylinks
.
sql
You can also find the actual Wikipedia dumps at http://dumps.wikimedia.org/enwiki/latest/.
Alternatively, if you don’t want to wait several hours to have the whole Wikipedia dumps imported into your database, you can always populate it with seeds.rb.
The file db/seeds.rb in the repository for this chapter has been prepopulated with some category data and links so that you can quickly develop and test your API.
To populate the database this way, run the command:
$
rake
db
:
seed
Once our database has been set up and our models generated, we can start coding our controllers.
We are going to use ActiveModel::Serializers to encapsulate the JSON serialization of objects.
A common way to generate a JSON API in Rails is to add a respond_to
block to the controller action so that we can see the JSON representation of the returned object.
Suppose, though, we would like to have total control over the returned JSON. We could do this by overriding the as_json
method, or by passing some options through the controller.
The problem with both these approaches is that they will not provide the level of customization and simplicity that ActiveModel::Serializers delivers. So, we are going to use it in our application by adding it to the Gemfile:
source
'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem
'rails'
,
'4.1.4'
gem
'rails-api'
# Use mysql as the database for Active Record
gem
'mysql2'
# Serializer for JSON
gem
'active_model_serializers'
group
:doc
do
# bundle exec rake doc:rails generates the API under doc/api.
gem
'sdoc'
,
'~> 0.4.0'
end
group
:development
do
# Spring speeds up development by keeping your application
# running in the background. Read more:
# https://github.com/rails/spring
gem
'spring'
end
and running Bundle to install it:
$
bundle
install
We are now going to generate the serializer for our models by using the Serializers generator:
$
rails
g
serializer
category
$
rails
g
serializer
link
The generator will create a file in the folder serializers/ under app/. Here we can specify which attributes we want to serialize in our JSON replies.
For our category serializer we would like to include the category title and the number of subcategories. We are therefore going to specify the attributes cat_title
and cat_subcats
:
# serializers/category_serializer.rb
class
CategorySerializer
<
ActiveModel
:
:Serializer
attributes
:title
,
:sub_categories
end
We will now start coding our controllers. This is a good point to think about how we would like our API to be served and what URI structure we are going to use to identify our resources.
From a Rails perspective we are going to provide an api/ directory as our endpoint, followed by the API version, v1.
We are going to generate a category controller in the api/v1 namespace with one action:
$
rails
generate
controller
api
/
v1
/
category
show
This will generate an empty controller with a single empty action called show. We can modify the controller to return the category requested through a parameter:
class
Api
::
V1
:
:CategoryController
<
ApplicationController
respond_to
:json
def
show
category
=
params
[
:category
]
?
params
[
:category
]
:
"sports"
@category
=
Category
.
where
(
:cat_title
=&
gt
;
category
.
capitalize
)
.
first
render
:json
=&
gt
;
@category
,
serializer
:
CategorySerializer
,
root
:
"category"
end
end
Let us examine the controller bit by bit (not literally :).
First of all, we are instructing Rails to return only JSON by using the line respond_to :json
.
Secondly, we are telling the controller that if the category
param is not defined, the default category “sports” can be used.
Finally, we want the controller to return a category using the category serializer. We are also telling the serializer to use the string “category” as the root for the returned JSON.
Once the controller has been defined, we can define our routes. By default Rails fills the config/routes.rb file with some defaults and basic documentation on how to build routes for your application. Every time a controller is generated through the rails generate
command, the routes file is modified with the default routes.
Therefore, we will go ahead and delete (or comment out, if you wish) all the defaults added to the routes file and replace them with the following:
Rails
.
application
.
routes
.
draw
do
namespace
:api
do
namespace
:v1
do
get
'/category/:category'
,
:to
=>
'category#show'
end
end
end
The routes defined will instruct Rails to route the show action of the category controller within the api/v1 namespace. Furthermore, the portion of the path defined after /category/ will be considered as the category
parameter to pass the controller.
Finally, it is time to test our serializer, controller, and routes:
$
curl
http
:
/
/
0
.
0
.
0
.
0
:
3000
/
api
/
v1
/
category
/
science
If everything is going according to plan, we should receive the JSON string:
<
p
>
{
"category"
:{
"cat_title"
:"SCIENCE"
,
"cat_subcats"
:
34
}}
We would like, though, to use different names in our JSON replies, and to add some text preprocessing.
We modify the serializer to force ISO-8859-1 and UTF-8 conversion and to replace any possible spaces left in the category titles with underscore characters.
Furthermore, we modify the attributes named cat_title
and cat_subcats
:
# serializers/category_serializer.rb
class
CategorySerializer
<
ActiveModel
:
:Serializer
attributes
:title
,
:sub_categories
def
title
URI
:
:encode
(
object
.
cat_title
.
force_encoding
(
"ISO-8859-1"
)
.
encode
(
"utf-8"
,
replace
:
nil
)
.
downcase
.
tr
(
" "
,
"_"
))
end
def
sub_categories
object
.
cat_subcats
end
end
We can test again to see if the changes have been applied:
$
curl
http
:
/
/
0
.
0
.
0
.
0
:
3000
/
api
/
v1
/
category
/
science
{
"
category
"
:
{
"
title
"
:"
science
"
,
"
sub_categories
"
:
34
}
}
Now that we can check single categories, we would like to display all subcategories of a given category passed as a parameter.
We are going to create a controller that will use the link serializer and build the category graph:
$
rails
generate
controller
api
/
v1
/
graph
show
Then we are going to modify it as follows:
class
Api
::
V1
:
:GraphController
<
ApplicationController
respond_to
:json
def
show
cat
=
params
[
:category
]
||
'sports'
@category
=
Category
.
where
(
:cat_title
=>
cat
.
capitalize
)
.
first
@links
=
Link
.
where
(
:cl_to
=>
@category
.
cat_title
,
:cl_type
=>
"subcat"
)
render
:json
=>
@links
,
each_serializer
:
LinkSerializer
,
root
:
@category
.
cat_title
.
downcase
end
private
def
graph_params
params
.
require
(
:category
)
.
permit
(
:cl_to
,
:cl_type
)
end
end
As for the category controller, we are asking Rails to respond only with JSON. Then we define an index action where we retrieve a category and find all the corresponding subcategories. We render the JSON using the link serializer defined earlier. The category title is our root.
We also define a private function where we specify the permitted attributes for the Link
model and the required category
parameter.
Next, we modify the link serializer to rename the cl_sortkey
attribute to sub_category
. We also enforce UTF-8 and ISO-8859-1 encoding while removing whitespaces:
class
LinkSerializer
<
ActiveModel
:
:Serializer
attributes
:sub_category
def
sub_category
URI
:
:encode
(
object
.
cl_sortkey
.
force_encoding
(
"ISO-8859-1"
)
.
encode
(
"utf-8"
,
replace
:
nil
)
.
downcase
.
tr
(
" "
,
"_"
))
end
end
Then we test:
$
curl
http
:
/
/
0
.
0
.
0
.
0
:
3000
/
api
/
v1
/
graph
/
science
And if everything was set up correctly, we receive the graph JSON:
{
"science"
:
[
{
"sub_category"
:
"_%0Ascientific_disciplines"
},
{
"sub_category"
:
"_%0Ascientists"
},
.
.
.]
}
Now we are at the boring and tedious step of setting up an app: testing.
We want to make sure that our controllers will behave according to our design even if something changes in the future. So, we are going to write two simple tests to assure the default behavior for both controllers:
# test/controllers/api/v1/category_controller_test.rb
require
'test_helper'
class
Api
::
V1
:
:CategoryControllerTest
<
ActionController
:
:TestCase
test
"should get show"
do
get
:show
,
{
'category'
=>
'science'
}
assert_response
:success
assert_equal
"{
"
category
"
:
{
"
title
"
:
"
science
"
,
"
sub_categories
"
:1}}"
,
@response
.
body
end
end
require
'test_helper'
class
Api
::
V1
:
:GraphControllerTest
<
ActionController
:
:TestCase
test
"should get show"
do
get
:show
,
{
'category'
=>
"sports"
}
assert_response
:success
assert_equal
"{
"
sports
"
:
[{
"
sub_category
"
:
"
culture_and_sports_culture
"
}]}"
,
@response
.
body
end
end
Before running the tests we also need to prepare the fixtures. This is fake data that will populate our test database in order for the tests to work.
We are going to create two links and two categories. The two fixtures were already generated when we scaffolded the models, but we are going to modify both as follows:
# test/fixtures/links.yml
one
:
cl_from
:
1
cl_to
:
Sports
cl_sortkey
:
CULTURE
AND
SPORTS
CULTURE
cl_timestamp
:
2005
-
04
-
29
10
:
32
:
42
cl_sortkey_prefix
:
Culture
cl_collation
:
uppercase
cl_type
:
subcat
two
:
cl_from
:
2
cl_to
:
Science
cl_sortkey
:
SCIENTIFIC
DISCIPLINES
cl_timestamp
:
2013
-
09
-
02
22
:
56
:
59
cl_sortkey_prefix
:
Sci
cl_collation
:
uppercase
cl_type
:
subcat
# test/fixtures/categories.yml
one
:
cat_id
:
1
cat_title
:
Sports
cat_pages
:
1
cat_subcats
:
1
cat_files
:
1
two
:
cat_id
:
2
cat_title
:
Science
cat_pages
:
1
cat_subcats
:
1
cat_files
:
1
You can read more on fixtures in the Ruby on Rails documentation.
We can then run the tests as usual:
$
rake
test
We haven’t defined errors in the graph controller. What happens if the category isn’t found?
You can find the solution in the WikiCat GitHub repository.
In this chapter we learned how to create a first Rails application. In the next chapter we will explore how you can use Rails to build RESTful applications and, more specifically, how it is possible to reconcile CRUD with REST.
3.16.51.157