In my rails 3.2 app, I'm using jbuilder to render responses from my JSON api.
I want to provide a common structure to all API responses, and a layout would be the likely solution to keep my views DRY.
ex: I'd like every response to be of the following form :
{
status: "ok|error|redirect",
data: { ... JSON specific to the current view ... },
errors: [ ... ],
notes: [ ... ]
}
(where the value for data is a json structure provided by the view, everything else is from the layout)
However: I can't get the jbuilder layout yielding to the view correctly.
# in layout
json.data yield
# in view
json.some "value"
results in:
{"data":"{\"some\":\"value\"}"} # arg! my json has become a string
Trying things another way:
# in layout
yield
# in view
json.data do |json|
json.some "value"
end
results in :
{}
Has anyone had success doing this with jbuilder, or another json templating gem/method?
This juilder github issue suggests it's possible, but indicates others are having similar issues.
I see rabl (https://github.com/nesquena/rabl/) is supposed to support layouts (https://github.com/nesquena/rabl/wiki/Using-Layouts), but I've decided not to use it for other reasons (rabl makes complex json structures a nightmare, particularly when trying to control object roots etc).
You can do this by this way
# api.v1.json.jbuilder - layout
json.request do
json.message "your message"
json.status 200
end
json.data JSON.parse(yield)
# show.json.jbuilder - action view
json.name 'Some item name'
I'll give you an alternative based on a solution we came up with:
# app/helpers/application_helper.rb
module ApplicationHelper
def envelope(json, status, errors, notes)
json.status status
json.data do
yield if block_given?
end
json.errors errors
json.notes notes
end
end
then, in the view, you can call envelope and include your json code like:
# app/views/api/v1/test/show.json.jbuilder
envelope(json, "OK" ) do
json.some 'value'
end
Late answer, but helped me get what I was looking for...
Success Result:
{ "something": {"id": 42, "message": "hello"}, "status": "ok", "errors": [] }
Error Result:
{ "something": null, "status": "error", "errors": ["could not do the thing"] }
Code:
app/controllers/api/v1/base_controller.rb
class Api::V1::BaseController < ActionController::API
layout 'api/v1/application'
before_action :setup_layout_elements
def setup_layout_elements
@status = :ok
@errors = []
end
def error!(message)
@status = :error
@errors << message
nil
end
end
app/controllers/api/v1/some_controller.rb
class Api::V1::SomeController < Api::V1::BaseController
def index
@something = begin
possibly_error_causing_code
rescue
error!('could not do the thing')
end
render builder: 'api/v1/something/index'
end
end
app/views/layouts/api/v1/application.json.jbuilder
json.merge! JSON.parse(yield)
json.status @status
json.errors @errors
app/views/api/v1/something/index.json.jbuilder
json.something do
json.id @something.id
json.message @something.to_s
end
JBuilder does not support using json.jbuilder
as your layout (see issue #172 on Github).
I managed to avoid doing an extra round of parse
&generate
by using json.erb
as my layout format.
app/controllers/api/base_controller.rb:
class Api::BaseController < ActionController::Base
layout "api.v1"
end
app/views/layouts/api.v1.json.erb:
{
<% if @api_errors.present? %>
"errors": <%= raw JSON.dump @api_errors %>,
<% else %>
"data": <%= yield %>,
<% end %>
"meta": <%= raw JSON.dump @api_meta %>
}
In case you don't want to include extra key you can do so
class UsersController < ApplicationController
layout: 'json_layout'
end
In /app/views/layouts/json_layout.json.jbuilder
json.success true
r = JSON.parse(yield)
r.each{|k,v|
json.set! k,v
}
jbuilder is pretty simple technique for API views here you can add partials so if you want the same response for all the API create a decorator or create partial for the common response and call that response where ever you need that
Lets say if you want
{
status: "ok|error|redirect",
data: { ... JSON specific to the current view ... },
errors: [ ... ],
notes: [ ... ]
}
create a partial for this /views/api/common/_some_partial
json.status "ok whatever the message"
json.data do
json.message "message"
end
json.errors @errors
json.notes @notes_array
Its pretty much simple solution for your question.
Cheers
来源:https://stackoverflow.com/questions/11516616/rails-json-api-layouts-with-jbuilder-or-other