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) }
Related
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
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 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.
I'm trying to get some virtual (non-persisted) attributes to show up in the JSON representation of some Mongoid models, but can't seem to get it to work:
class MyModel
include Mongoid::Document
def virtual_attribute
#my_attribute || false
end
def virtual_attribute=(value)
#my_attribute=value
end
end
class MyController
def myaction
false_values=MyModel.where( whatever )
true_values=MyModel.where( something_else ).map{ |model| model.virtual_attribute=true }
#val['my_models']=false_values+true_values
render json: #val.to_json( :include => {:my_models => {:methods => %w(virtual_attribute)}} )
end
end
virtual_attribute doesn't appear in the json. What am I doing wrong?
Edit - ok, so I guess my actual problem is that I can't figure out how to invoke the virtual_attribute method on each of an array of objects that is nested in the root object.
to_json passes the options directly to the array and the objects. :include is only a Mongoid thing:
render json: #val.to_json(methods: :virtual_attribute)
I'm using acts_as_taggable_on in my rails app. I'd like these tags to show up in the to_json representation of my model.
For example, to_json of one instance of my model looks like this:
{"created_at":"2012-02-19T03:28:26Z",
"description":"Please!",
"id":7,
"points":50,
"title":"Retweet this message to your 500+ followers",
"updated_at":"2012-02-19T03:28:26Z"}
...and I'd like it to look something like this:
{"created_at":"2012-02-19T03:28:26Z",
"description":"Please!",
"id":7,
"points":50,
"title":"Retweet this message to your 500+ followers",
"updated_at":"2012-02-19T03:28:26Z"
"tags" :
{"id":1,
"name":"retweet"},
{"id":2,
"name":"twitter"},
{"id":3,
"name":"social"}
}
My controller code is just the default that scaffolding gives me:
def show
#favor = Favor.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #favor }
end
end
Note that I can already access #favor.tags in the template, and #favor.tags.to_json works as expected, I just needed that data to be included when outputting #favor.to_json.
You can pass options to the json call by calling to_json. Or by redefining as_json in your favor model.
render json: #favor.to_json(include: :tags)
In your favor model override as_json method.
def as_json(options={})
super(:include => :tags)
end