Rails ApiController and Concerns

Feb 22, 2016

Hi there! Today I want to share with you some code architecture decisions
I’ve made during the development of
rails-api-base project.
This post is about how I’ve structured the code
that concerns about controllers common behavior
(I’ll only speak about controllers,
but the concept can be applied to models too).

ApiController versioning

The DRY (don’t repite yourself) basic principle
forces us to collect all spread common code of the controllers
and put it inside the ApplicationController.
Nevertheless, when developing an api we must care about code versioning too.
As is normal, almost all of this common code
is related with the input parameters
or the output format and affects directly to the api version contracts.
Because of that, I’ve decided that it needs to be versioned,
like the other controllers.
That’s the reason to create ApiController,
which has the same purpose as ApplicationController,
but it’s placed under app/controllers/api/v1 folder.
Hence, we’ll use one or the other
depending on if the generic code must be versioned or not.

So finally, the inheritance path of an ordinary controller will look like this:

ActiveSupport::Concern

But what are those “concerns”?
In order to don’t mess the ApiController
with too much and unrelated code,
it can be structured by independent modules using the
Rails ActiveSupport::Concern
module type.
Each file under /concerns implements one of these modules,
whom are included in the ApiController, thus, to all api controllers.
Concerns let you define variables, methods…
as usual, but also include behavior to the controller itself
(e.g.: define a before_action or rescue_from).
That is all we need to refactor the ApiController.

I’ve applied this pattern to the project
and the result is an ApiController as clean and simple as that:

and here you can see an example of one of the concern modules:

The unique remarkable thing is that
the module extends from ActiveSupport::Concern
and places the controller specific code, such as an before_action,
under included
(the filters, rescues… code that only works from inside a controller class).

Concern modules testing

We must define a fake controller that inherits from ApiController
and check that the behavior included by the target module
works as expected.

Here is an example:

Here we can see how the fake controller is defined adhoc inside the spec.
In this case, I’ve also taken the opportunity to test the current_user
method, apart from the most important concern purpose defined by the
before_action :auth_with_token!.

Few lines below the controller, appears the also fake and adhoc routing.
Usually, the drawn route would be the following one:

routes.draw{get'fake_method_name'=>'anonymous#fake_method_name'}

but from Rspec version 3 it raises the following error
when the inherited controller is not in the app/controllers/ folder root: