Fix Rails Converting Backbone JSON Requests to text/html
During a recent project with BackboneJs and Rails, i noticed some very strange behavior. When my controller had an implicit render to Backbone's JSON requests, Rails was throwing an error.
ActionView::MissingTemplate (Missing template users/index, base/index with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :arb]}.
Rails is complaining that no template exists for the response format of HTML. Well this is unexpected. The request, which comes from Backbone, should be expecting JSON as a return type. But for some reason, Rails is attempting to return HTML. After some investigation, you find that the problem is lying in the request object itself.
p request.headers[ 'HTTP_ACCEPT' ]
# > "application/json, text/javascript, */*; q=0.01"
p request.format.to_s
# > "text/html"
Now, request.headers
represents the raw, unprocessed headers from the client request. HTTP_ACCEPT
specifically is the requested response format. request.format
is the parsed response type Rails understood from the request headers. Somehow, it has converted the JSON based request type to text/html
. This explains why our app is complaining that no template exists for an HTML response. Rails thinks the request wants HTML formatted data.
A fix
class ApplicationController < ActionController::Base
# ...
before_action :coerce_json
def coerce_json
# Rails converts the following header:
#
# Accept: application/json, text/javascript, */*; q=0.01
#
# into text/html. Force it back to json.
if request.headers[ 'HTTP_ACCEPT' ] =~ /^\s*application\/json/
request.format = 'json'
end
end
end
This code is relatively self-explanatory. By adding a before_action
to the application_controller, our app will reassign the response format before the actual controller code executes.
Some will wonder why I used a regex instead of a hard string comparison. The reason is that i won't pretend i know how different libraries use the Accept header. But i do know that if my app requests application/json
as a return type, my Rails app better jolly well return JSON.
In a later post, i will go into the details of how the Rails code does this.
tl;dr: Rails overwrites the jQuery-style JSON request type to "text/html". This function will make Rails understand it correctly.