Recently I’ve been working on a few applications that use rails for their backend API, which is consumed by javascript based frontends.
The challenge I’ve encountered with building API controllers is handling the web of conditional logic that determines whether or not a request is successful, and also how to return a helpful API response reflecting the disposition of the request.
In dealing with this I’ve noticed a pattern emerge in my code, and it seems to help a lot in my projects in simplifying all of these concerns.
The handler class is very specifically designed to handle a request and only ever be used in an API controller context. This is in contrast to service objects which are much more flexible. The handler has specific logic for responding to requests that make it disadvantageous for other contexts (handling of status codes for example).
I’ve built a project to demonstrate all of this, available on github. (All of the following code samples are pulled from this repo.)
An instance of ApiController implemented using a handler class looks very clean and simple, the only conditional logic it cares about is whether or not the request was ultimately successful.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Api::CheckOutsController < ApiController
def create
handler = CheckOutHandler.new(params: check_out_params)
if handler.create
render json: handler.check_out, serializer: CheckOutSerializer,
status: handler.status
else
render json: { error: handler.error }, status: handler.status
end
end
private
def check_out_params
params.require(:check_out).permit(:book_id, :user_id)
end
end
The parent handler class that CheckOutHandler
inherits from contains some abstracted concerns of all handler classes, such as converting symbols to status codes and formatting errors for invalid models to be returned in the response.
The CheckOutHandler
class itself, in this case, takes the params provided by the controller and sets them on the model. The create
method called from the controller begins by checking if the model is valid in a guard clause, and then tries to save the model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class CheckOutHandler < Handler
attr_reader :params, :check_out
def initialize(args)
@params = args[:params]
@check_out = CheckOut.new(params)
super
end
def create
return if record_invalid?
save
end
private
def save
check_out.save!
rescue StandardError => ex
@error = ex.message
@status = status_for(:bad_request)
return
end
def record_invalid?
result = check_out.invalid?
result.tap do |invalid|
if invalid
@error = error_message_for(check_out)
@status = status_for(:bad_request)
end
end
end
end
This is where I was seeing a lot of complexity in trying to do all of this from the controller. I wanted to return responses appropriate for the error encountered. So with this pattern, that is simply a matter of setting the error message and status code from the guard clause method. In this was create
will return false and render json with the error and status set in handler.
I have a lot of handlers with update
methods that check record_not_found?
and return 404 with a helpful not found message. And I have other handlers that check things very specific to the model as well.
I could see a future where the private methods in CheckOutHandler
are abstracted out to the parent at some point. But I want to note that while what happens in the begin
block here seems pretty stupid and generic, it is only a simple example.
It is completely imaginable to have a complex action like performing some kind of transaction, which could have a dozen or more guard methods with all kinds of request dispositions. I haven’t written anything that complex with this pattern yet but I think it would help manage that sort of logic.
Potential Concerns
There are a couple concerns I have with this pattern, even though on the whole I really like it.
One is that it’s a little more awkward to test than a typical service object. I usually throroughly test the controller for all edge cases and leave the handler class minimally tested and consider it more as an implementation detail.
That’s not to say it couldn’t be thoroughly tested. But tests would end up checking error messages and response statuses, which I think is more naturally suited for controller testing. Given RESTful structuring of resources, a handler should really always correspond to only one controller, so I don’t see this as too great of a code smell.
Please comment with any constructive feedback, would love to hear thoughts on this idea as well as suggestions for improvement.