How to include child associations when serializing to json? - ruby-on-rails

Before using fast_jsonapi gem I was doing this:
render json: school.to_json(include: [classroom: [:students]])
My SchoolSerializer looks like:
class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description, :classroom
end
How would I get the students included in the JSON result?
Also, the classroom association is including but it is displaying all the properties, is there a way to map the classroom property to a ClassroomSerializer ?
class School < ApplicationRecord
belongs_to :classroom
end
class Classroom < ApplicationRecord
has_many :students
end

class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description
belongs_to :classroom
end
# /serializers/classroom_serializer.rb
class ClassroomSerializer
include FastJsonapi::ObjectSerializer
attributes :.... #attributes you want to show
end
Also you can add additional association to your School model, to access Students.
like this
has_many :students, through: :classroom
and then include it in School serializer directly.
Update: also please note that you can directly point to serializer class you need. (if you want to use class with different name from model as example).
class SchoolSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :description
belongs_to :classroom, serializer: ClassroomSerializer
end

render json: SchoolSerializer.new(school, include: "classrooms.students")
The difference being the use of "include" when rendering the serializer. This tells the Serializer to add a key "included" to the returned JSON object.
class SchoolSerializer
include FastJsonapi::ObjectSerializer
belongs_to :classroom
has_many :students, through: :classroom
attributes :school_name, :description
end
StudentSerializer
include FastJsonapi::ObjectSerializer
belongs_to :classroom
belongs_to :school
attributes :student_name
end
render json: SchoolSerializer.new(school).serialized_json
will return a series of students with only the top level identifiers in the form
data: {
id: "123"
type: "school"
attributes: {
school_name: "Best school for Girls",
description: "Great school!"
...
},
relationships: {
students: [
{
id: "1234",
type: "student"
},
{
id: "5678",
type: "student"
}
]
}
}
whereas the include: "classroom.students" will return the full serialized Student Records in the form:
data: {
id: "123"
type: "school"
attributes: {
school_name: "Best school for Girls"
...
},
relationships: {
classroom: {
data: {
id: "456",
type: "classroom"
}
},
students: [
{
data: {
id: "1234",
type: "student"
}
},
{
data: {
id: "5678",
type: "student"
}
}
]
},
included: {
students: {
data {
id: "1234",
type: "student",
attributes: {
student_name: "Ralph Wiggum",
...
},
relationships: {
school: {
id: "123",
type: "school"
},
classroom: {
id: "456",
type: "classroom"
}
}
},
data: {
id: "5678",
type: "student",
attributes: {
student_name: "Lisa Simpson",
...
},
relationships: {
school: {
id: "123",
type: "school"
},
classroom: {
id: "456",
type: "classroom"
}
}
}
},
classroom: {
// Effectively
// ClassroomSerializer.new(school.classroom).serialized_json
},
}
}

Related

ROR Self Join for self reference

I am using the Self Joins to add reference to its own table.
class Employee < ApplicationRecord
belongs_to :manager, class_name: "Employee", optional: true
has_many :customers
end
class Customer
belongs_to :employee
end
I am fetching the employee data based. One employee will have one employee_head and employee_head will have a boss.
In the serializer i have
class EmployeeSerializer < ApplicationSerializer
# include FastJsonapi::ObjectSerializer
attributes :id, :name, :manager
def manager
ActiveModel::SerializableResource.new(object.manager)
end
end
when i query form employee, i am expecting:
{
employee:
{
id: 1,
name: "employee name",
customer_attributes: [
{ id: 8, name: customer_name }
],
manager: {
id: 2,
name: "employee head name",
customer_attributes: [
{ id: 8, name: customer_name }
],
manager: {
id: 3,
name: "boss name",
customer_attributes: [
{ id: 8, name: customer_name }
],
}
}
}
}
I am getting the expected data using:
Employee.includes(:customers).where(name: "employee name", employee_type: employee)
but the problem is when i hit the query, the customer information is fetched from the database multiple time. I am confuse where to use includes to avoid N+1 query to DB.
Thanks in advance

How to get the fast_jsonapi to return the attributes of the relationships

I have a rails api with a number of models that are being serialized by the fast_jsonapi gem.
This is what my models look like:
class Shift < ApplicationRecord
belongs_to :team, optional: true
...
class Team < ApplicationRecord
has_many :shifts
...
This is what the serializer looks like
class ShiftSerializer
include FastJsonapi::ObjectSerializer
...
belongs_to :team
...
end
The serialization works. However, even though I am including the compound team document:
def index
shifts = policy_scope(Shift).includes(:team)
options = {}
options[:include] = [:team, :'team.name', :'team.color']
render json: ShiftSerializer.new(shifts, options)
end
I'm still getting the object formatted like so:
...
relationships: {
team: {
data: {
id: "22",
type: "Team"
}
}
}
Whereas I'm expecting to get also the attributes of my team model.
fast_jsonapi implements json api specification so respond includes "included" key, where serialized data for relationships placed.That's default behavior
If you use the options[:include] you should create a serializer for the included model, and customize what is included in the response there.
in your case if you use
ShiftSerializer.new(shifts, include: [:team]).serializable_hash
you should create a new serializer serializers/team_serializer.rb
class TeamSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :color
end
this way your response will be
{
data: [
{
id: 1,
type: "shift",
relationships: {
team: {
data: {
id: "22",
type: "Team"
}
}
}
}
],
included: [
id: 22,
type: "Team",
attributes: {
name: "example",
color: "red"
}
]
}
and you will find the custom data of your association in the response "included"
If you use like this then maybe solve your problem
class Shift < ApplicationRecord
belongs_to :team, optional:true
accepts_nested_attributes_for :team
end
In your ShiftSerializer.rb please write this code,
attribute :team do |object|
object.team.as_json
end
And you will get custom data that you want.
Reference: https://github.com/Netflix/fast_jsonapi/issues/160#issuecomment-379727174

How to merge attributes from associated models into one nested attribute in serializer?

Below is my json after rendering '#products'. As you can see, there are 2 other models nested (vendor_products and vendors). The association between product and vendor models is many-to-many with 'vendor_products' being connecting tables. What I wanted to achieve here is - instead of having both 'vendor_products' and 'vendors' models being nested I just want to add 'vendor name' as another attribute inside the vendor_products model.
{
id: 1,
barcode: 3045320001525,
name: "xyz",
size: "370 g",
brand: "abc",
img_url: "http://xyx"
vendor_products: [
{
id: 1,
v_item: "JAM101",
vendor_id: 1,
case_price: 72
},
{
id: 2,
v_item: "1001",
vendor_id: 2,
case_price: 65
}
],
vendors: [
{
name: "vendor_xyz"
},
{
name: "vendor_123"
}
]
},
Below is the format of json I wanted:
{
id: 1,
barcode: 3045320001525,
name: "xyz",
size: "370 g",
brand: "abc",
img_url: "http://xyx"
vendor_products: [
{
id: 1,
v_item: "JAM101",
vendor_id: 1,
vendor_name: "vendor_xyz",
case_price: 72
},
{
id: 2,
v_item: "1001",
vendor_id: 2,
vendor_name: "vendor_abc",
case_price: 65
}
],
Here are my serializer classes:
class ProductSerializer < ActiveModel::Serializer
attributes :id, :barcode, :name, :size, :brand, :img_url
has_many :vendor_products
has_many :vendors
end
class VendorProductSerializer < ActiveModel::Serializer
attributes :id, :v_item, :vendor_id, :case_price
belongs_to :product
belongs_to :vendor
end
class VendorSerializer < ActiveModel::Serializer
attributes :name
has_many :products
has_many :vendor_products
end
Try adding a custom attribute in vendor_products serializer,
class VendorProductSerializer < ActiveModel::Serializer
attributes :id, :v_item, :vendor_id, :case_price, :vendor_name
belongs_to :product
belongs_to :vendor
def vendor_name
object.vendor.name #object is current vendor_product object get name from that
end
end

Add dependent field to Mongoid's output

I have two models:
class City
include Mongoid::Document
field :alternate_names, type: String
field :coordinates, type: Array
field :name, type: String
index({ coordinates: '2d' }, { min: -180, max: 180 })
belongs_to :country
end
and
class Country
include Mongoid::Document
field :iso_3166_code, type: String
field :name, type: String
has_many :cities
end
In the controller I use
#cities = City.where(alternate_names: /#{params[:query].downcase}/).limit(10)
to receive cities list.
Here is an JSON output for each city:
...
"country_id": {
"$oid": "56fc453eae3bbe5c2abcd933"
}
...
How can I get country instead of it's country_id?
I found a solution.
#cities = City.where(alternate_names: /#{params[:query].downcase}/).includes(:country).limit(10)
render json: #cities, except: :country_id, include: :country

How to specify a different root name for the embedded objects?

In my app I had BlogPost model and User model that are related through relation named author. To serve data from my Rails app I use active_model_serializers with definition:
class Blog::PostSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :title, :text, :created_at, :updated_at
has_one :author
has_many :assets
end
When I fetch this using Ember model:
Admin.BlogPost = DS.Model.extend({
author: DS.belongsTo('User'),
title: DS.attr('string'),
text: DS.attr('string'),
createdAt: DS.attr('date'),
updatedAt: DS.attr('date')
});
There is an error:
Uncaught Error: Assertion Failed: You looked up the 'author' relationship on a 'blog.post' with id 1 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.belongsTo({ async: true })`)
Which is caused by that my response looks like:
{
'blog_posts': [
{
id: 1,
author_id: 1
},
// …
],
'authors': [
{ id: 1, /* … */ }
]
}
Is there any way to change 'authors' in response to 'users' or use 'authors' as alias to 'users' in serializer?
From active_model_serializers 0.8 description: https://github.com/rails-api/active_model_serializers/tree/0-8-stable
You can also specify a different root for the embedded objects than the key used to reference them:
class PostSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :title, :body
has_many :comments, :key => :comment_ids, :root => :comment_objects
end
This would generate JSON that would look like this:
{"post": {
"id": 1,
"title": "New post",
"body": "A body!",
"comment_ids": [ 1 ]
},
"comment_objects": [
{ "id": 1, "body": "what a dumb post" }
]
}
Just define a method in your serializer named users and return authors in it I.e.
attributes :id, :title, :text, :created_at, :updated_at, :users
def users
object.authors
end

Resources