I'm trying to get my Rails API to render all JSON responses in camelCase. Currently I am using Netflix Fast JSON API for my serializer and rendering errors like so:
render json: { errors: command.errors }, status: :unauthorized
For the Netflix Fast JSON API serializers I've been adding set_key_transform :camel_lower to every serializer, which seems to do the trick (although if anyone knows how to make that a default it would be much appreciated).
For rendering errors however I'm not sure the best way to go about camel casing. If anyone has any experience with this please let me know how you go about it! Ideally there is a method of doing this that doesn't add too much syntax to every render call being made.
UPDATE
In serializing errors I added a helper method on the application controller:
def render_error(errors_params, status)
render json: {
errors: errors_params
}.deep_transform_keys { |key| key.to_s.camelize(:lower) }, status: status
end
For the Netflix Fast JSON API I took the suggestion of #spickermann and added an application serializer for the other serializers to inherit from:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class SomeSerializer < ApplicationSerializer
attributes :attribute, :other_attribute
end
You could create an ApplicationSerializer and all other serializers could inherit from it:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class FooBarSerializer < ApplicationSerializer
attributes :buzz, :fizz
# ...
end
You could monkey patch the serializer
Rails.application.config.to_prepare do
FastJsonapi::ObjectSerializer.class_eval do
set_key_transform :camel_lower
end
end
and for handling errors you can probably create an error serializer
render serializer: ErrorSerializer, json: {status: : unauthorized, errors: resource.errors
Have a look here and here
Related
I am using serializer to format json response of my rails-api projector.
I am using a concern to format final response.
My code snippets are as follows
entry_controller.rb
class EntriesController < ApplicationController
include Response
def index
#entries = #current_user.entries
json_response(#entries)
end
end
concerns/response.rb
module Response
def json_response(response, error = nil, message = 'Success', code = 200)
render json: {
code: code,
message: message,
error: error,
response: response
}
end
end
application_serializer.rb
class ApplicationSerializer < ActiveModel::Serializer
end
entry_serializer.rb
class EntrySerializer < ApplicationSerializer
attributes :title, :content, :is_encrypted, :entry_date
end
In entries#index if i use json_response(#entries) my final response of request is not formatted and each entry is as in database. instead if i use render json: #entries. Im getting as per serializer. I want to use concern method json_response(#entries) along with serializers. Can someone suggest a way to use serializers in concern methods in a generic way as multiple controllers use same concern method.
Thanks in advance.
Something related to serializer params is what you want to customise your response.
class EntriesController < ApplicationController
include Response
def index
#entries = #current_user.entries
render json: #entries, serializer_params: { error: nil, message: "Success", code: 200}
end
end
class EntrySerializer < ApplicationSerializer
attributes :title, :content, :is_encrypted, :entry_date
params :error, :message, :code
def attributes
super.merge(:error, :message, :code)
end
end
I’m not sure I fully understand your question, but I don’t believe render :json calls to_json recursively if it is given a hash, like in this case. So you may be looking for something like this in your concern:
module Response
def json_response(response, error = nil, message = 'Success', code = 200)
render json: {
code: code,
message: message,
error: error,
response: response.to_json
}
end
end
Considering a Thing model and serializer defined like this:
# models/thing.rb
class Thing < ApplicationRecord
end
# serializers/thing_serializer.rb
class ThingSerializer < ActiveModel::Serializer
attributes :id, :name
end
In a controller I'd like to serialize my #thing, so
render json: #thing
returns as expected
{"id":3,"name":"rocket"}
However, when I try nesting the response deeper like so:
render json: { thing: #thing }
the #thing is serialized, but not using the defined serializer (outputs all fields).
My question is two-fold:
Is there a clean dynamic way to make serializing hashes use the "default" serializer for objects nested inside without using render json: { thing: ThingSerializer.new(#thing) }?
If so, can this be also applied to collection serializers, so objects don't have to be wrapped like
render json: { my_key: ThingSerializer::CollectionSerializer.new(#things) }
I have 2 non database attributes in my model. If one of them has a value, I need to return the other one in the json response:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def attributes
if !self.track.nil?
super.merge('max_speed_on_track' => self.max_speed_on_track)
end
end
end
The problem is that the line 'if !self.track.nil?' throws an error when the controller tries to return the json
Perhaps there is a better way as I read that using attr_accessor is a code smell.
What I am trying to do is if the user passes me a track value as a query parameter, then I pass that value to the model and it uses it to calculate the max_speed_on_track, and return that value.
Obviously if no track is provided by the user then I don't want to return max_speed_on_track in the json.
The controller method is very basic for now (I still need to add the code that checks for the track param). The code throws the error on the save line.
def create
#car = Car.new(car_params)
if #car.save
render json: #car, status: :created
else
render json: #car.errors, status: :unprocessable_entity
end
end
Try this out:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def as_json(options = {})
if track.present?
options.merge!(include: [:max_speed_on_track])
end
super(options)
end
end
Since Rails uses the attributes method, and you're only needing this for json output, you can override the as_json method just like in this article. This will allow you to include your max_speed_on_track method in your json output when the track is present (not nil).
I have a simple model:
class Receipt
include ActiveModel::Serialization
attr_accessor :products
end
and my controller is doing:
def create
respond_with receipt, :serializer => ReceiptSerializer
end
and the serializer:
class ReceiptSerializer < ActiveModel::Serializer
attributes :products
end
and I get:
NoMethodError:
undefined method `to_model' for #<Receipt:0x007f99bcb3b6d8>
Yet if I change my controller to:
def create
json = ReceiptSerializer.new(receipt)
render :json => json
end
Then everything works fine... what is happening???
I was using active_model_serializers 0.9.3, but just tried 0.10.2, and the results are the same.
In all the documentation I've read and personal implementation I use render json: instead of respond_with.
render json: receipt, serializer: ReceiptSerializer
I believe that respond_with has been removed from rails and isn't considered a best practice anymore but I can't find a link to validate that claim.
I'm not totally sure, but it seems in your Receipt PORO, you should rather include: ActiveModel::SerializerSupport.
I can't confirm if that works for active_model_serializers 0.10.2 though
I have active_model_serializers setup in a rails project, and can can successfully use a serializer to display json in such a fashion:
render json: #user
However, the gem seems to fail to automatically use the serializer if I am including the model object in a more complex json response as such:
render json: { "success": true, "something": "xyz", user: #user }
I've found that I can do the following to get around it, although its a pain to have to do:
render json: { "success": true, "something": "xyz", user: UserSerializer.new(#user) }
Additionally, using it as shown above seems to make it not pick up on the root:false option that I have specified as my default_serializer_options in ApplicationController. This means I have to go as far as to put:
render json: { "success": true, "something": "xyz", user: UserSerializer.new(#user, root: false) }
Is there a better way around this? It seems like having to be so explicit in every single render is going to take away from some the simplicity/benefit of AMS...
** Note: this is rails 4, ruby 2.0, and active_model_serializers 0.8.0
active_model_serializers allows one serializer to inherit from another. To get the result you're looking for, I would make an ApplicationSerializer with "success" and "something" as attributes. Other serializers can then inherit from that. Basically, something like:
class ApplicationSerializer < ActiveModel::Serializer
self.root = false
attributes :success, :something
def success
true
end
def something
"xyz"
end
end
class UserSerializer < ApplicationSerializer
attributes :name, :email, :etc
end
Note that in my example here, "success" and "something" will turn out in the json as attributes within the user, not with the user as a separate key. It's totally possible to do that, but it seems like you'd rather just say render json: #user.