Rendering has_many in ActiveModelSerializer conditionally - ruby-on-rails

I have the following serializers:
class V1::CategorySerializer < ActiveModel::Serializer
has_one :category_name, :class_name => 'V1::CategoryName'
has_many :brands, :class_name => 'V1::Brand'
end
class V1::CategoryNameSerializer < ActiveModel::Serializer
attributes :name
has_many :brand_names, :class_name => 'V1::BrandName'
end
class V1::BrandSerializer < ActiveModel::Serializer
has_one :brand_name, :class_name => 'V1::BrandName'
end
class V1::BrandNameSerializer < ActiveModel::Serializer
attributes :name
end
When I render via Category render json: seller.categories I do not want the BrandNames to be printed. Output should be something like:
{
"seller_id": 1,
"categories" :
[
{
"name" : "Washing Machines",
"brands" : ["LG", "Samsung"]
}
]
}
instead of
{
"brands_categories": [
{
"category_name": {
"name": "Washing Machines",
"brand_names": [
{
"name": "LG"
},
{
"name": "Samsung"
}
]
},
"brands": [
{
"brand_name": {
"name": "LG"
}
},
{
"brand_name": {
"name": "Samsung"
}
}
]
}
]
}
Also, I want to keep the current behavior of printing render json: V1::CategoryName.all as it is.

I solved my problem by changing the CategorySerializer to
class V1::CategorySerializer < ActiveModel::Serializer
attributes :category, :brands
def category
object.category_name.name
end
def brands
brand_names = []
object.brands.each do |brand|
brand_names << brand.brand_name[:name]
end
brand_names
end
end

Related

How to include a nested resource that has a polymorphic association with ActiveRecordSerializer?

I'm using Netflix's jsonapi-rails gem to serialize my API. I need to build a response.json object that includes the associated comments for a post.
Post model:
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
Polymorphic Comment model
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
PostSerializer
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :body
has_many :comments, serializer: CommentSerializer, polymorphic: true
end
CommentSerializer
class CommentSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :id, :created_at
belongs_to :post
end
Posts#index
class PostsController < ApplicationController
def index
#posts = Post.all
hash = PostSerializer.new(#posts).serialized_json
render json: hash
end
end
What I've got so far only gives me the comment type and id, but I NEED the comment's body as well.
Please help!
Thanks in advance~!
Although not very intuitive this behaviour is there by design. According to the JSON API relationship data and actual related resource data belong in different objects of the structure
You can read more here:
Fetching Relationships
Inclusion of Related Resources
To include the body of the Comments your serializers would have to be:
class PostSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :created_at
has_many :comments
end
class CommentSerializer
include FastJsonapi::ObjectSerializer
attributes :body, :created_at
end
and your controller code:
class HomeController < ApplicationController
def index
#posts = Post.all
options = {include: [:comments]}
hash = PostSerializer.new(#posts, options).serialized_json
render json: hash
end
end
The response for a single Post would look something like this:
{
"data": [
{
"attributes": {
"body": "A test post!"
},
"id": "1",
"relationships": {
"comments": {
"data": [
{
"id": "1",
"type": "comment"
},
{
"id": "2",
"type": "comment"
}
]
}
},
"type": "post"
}
],
"included": [
{
"attributes": {
"body": "This is a comment 1 body!",
"created_at": "2018-05-06 22:41:53 UTC"
},
"id": "1",
"type": "comment"
},
{
"attributes": {
"body": "This is a comment 2 body!",
"created_at": "2018-05-06 22:41:59 UTC"
},
"id": "2",
"type": "comment"
}
]
}

How can show has_many :through Association with ActiveModel::Serializer

I have many to many relation between projects and project_category through project_by_category.
My models:
Project Model
class Project < ApplicationRecord
# Relationships
has_many :project_by_categories
has_many :project_categories, through: :project_by_categories
ProjectCategory Model
class ProjectCategory < ApplicationRecord
has_many :project_by_categories
has_many :projects, through: :project_by_categories
ProjectByCategory Model
class ProjectByCategory < ApplicationRecord
# Relationships
belongs_to :project
belongs_to :project_category
My serializers:
Project Serializer
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :image, :name, :description
Project Category Serializer
class ProjectCategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :projects
The expected result:
{
"data": [
{
"id": "1",
"type": "project-categories",
"attributes": {
"name": "3D design basics"
},
"relationships": {
"projects": {
"data": [
{
"id": "1",
"image": "",
"name": "",
"description": "",
"type": "projects"
},
{
"id": "2",
"image": "",
"name": "",
"description": "",
"type": "projects"
}
]
}
}
},
But this is the result:
{
"data": [
{
"id": "1",
"type": "project-categories",
"attributes": {
"name": "3D design basics"
},
"relationships": {
"projects": {
"data": [
{
"id": "1",
"type": "projects"
},
{
"id": "2",
"type": "projects"
}
]
}
}
},
In the project relationship only show me the id and the type.
By last this is my controller
class Api::V1::CategoriesController < ApiController
def index
#categories = ProjectCategory.all
render json: #categories
end
Thank you for your answers!!
Your joins table appears to be setup correctly (and you seem to be grabbing the expected objects from your database), but you are just missing the additional serialized attributes.
Have you tried passing the serializer: or each_serializer: arguments to the respective serializer and controller? For example, your Api::V1::CategoriesController might look like this:
class Api::V1::CategoriesController < ApiController
def index
#categories = ProjectCategory.all
render json: #categories, each_serializer: ProjectCategorySerializer
end
end
Similarly, your ProjectCategorySerializer would contain:
class ProjectCategorySerializer < ActiveModel::Serializer
attributes :id, :name
has_many :projects, each_serializer: ProjectSerializer
I'm not sure if ActiveModel::Serializers are now a part of ActiveSupport, but the library seems to suggest that development has tapered off. I'd recommend switching a a different library if possible.
Hope this helps!

ActiveModel::Serializers JSON API nested associations include?

I haven't found the exactly same question on Stackoverflow. Besides AMS changes so rapidly, that even 2-year-old answers get outdated often.
I use Rails 5 API-only and the gem 'active_model_serializers' (AMS) (ver. 0.10.6).
I also use the JSONAPI response format - not simply JSON.
I need to render a nested include - not just nested relations as of now.
Code example:
serializer1:
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :title, :created_at, :updated_at
belongs_to :user
end
serializer2:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email
has_many :cities
end
serializer3:
class CitySerializer < ActiveModel::Serializer
attributes :id, :name
end
controller:
def index
#questions = Question.all
render json: #questions, include: [:user, 'user.city']
end
I get this response:
{
"data": [
{
"id": "1",
"type": "questions",
"attributes": {
"title": "First",
"created-at": "2017-03-27T13:22:15.548Z",
"updated-at": "2017-03-27T13:22:16.463Z"
},
"relationships": {
"user": {
"data": {
"id": "3",
"type": "users"
}
}
}
}
],
"included": [
{
"id": "3",
"type": "users",
"attributes": {
"email": "client2#example.com"
},
"relationships": {
"cities": {
"data": [
{
"id": "75",
"type": "cities"
}
]
}
}
}
]
}
I really do get a nested relation city. I even get the city id.
But the problem is - how do I get other city attributes like name? I need another include section - maybe inside current include section (nested?).
How to do that? (without any additional gems)
I found some solution. I don't know about how clean is it. It is based on 2 prerequisites:
https://github.com/rails-api/active_model_serializers/blob/db6083af2fb9932f9e8591e1d964f1787aacdb37/docs/general/adapters.md#included
ActiveModel Serializers: has_many with condition at run-time?
I applied the conditional relations and user-all-permissive include:
controller:
#questions = Question.all
render json: #questions,
show_user: (param? params[:user]),
show_cities: (param? params[:city]),
include: [:user, "user.**"]
serializer1:
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :title, :created_at, :updated_at
belongs_to :user, if: -> { should_render_user }
def should_render_user
#instance_options[:show_user]
end
end
serializer2:
class UserSerializer < ActiveModel::Serializer
attributes :id, :email
has_many :cities, if: -> { should_render_cities }
def should_render_cities
#instance_options[:show_cities]
end
end
serializer3:
class CitySerializer < ActiveModel::Serializer
attributes :id, :name
end
helper:
def param? param
param && param != "false" && param != "nil"
end
Conditional relations allow to control which include's actually to render.
The following should do it
render json: #questions, include: [:categories, user: :city]
You should also include the user in the parameter 'fields' and using strings instead of symbols for the parameter include
render json: #questions, fields: [:title, :user], include: ['user', 'user.cities']

Rails 4 AMS with three nested models

I am using active_model_serializers gem for the first time. Version which I'm using is 0.10.2
I have three models with associations like this:
class Song < ActiveRecord::Base
has_many :questions
end
class Question< ActiveRecord::Base
belongs_to :song
has_many :answers
end
class Answer< ActiveRecord::Base
belongs_to :question
end
I've generated three serializers like this:
class SongSerializer < ActiveModel::Serializer
attributes :id, :audio, :image
has_many :questions
end
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :text
belongs_to :song
has_many :answers
end
class AnswerSerializer < ActiveModel::Serializer
attributes :id, :text
belongs_to :question
end
but unfortunately my json response doesn't show me the question's answers, but songs and questions are showing.
after some googling I tried to add
ActiveModelSerializers.config.default_includes = '**'
or from documentation like this:
class Api::SongsController < ApplicationController
def index
songs = Song.all
render json: songs, include: '**' #or with '*'
end
end
but this led me to stack level too deep error
So what should I do in order to get json response to looks like this -
{
"id": "1",
"audio": "...",
"image": "...",
"questions": [
{
"id": "1",
"text": ".....",
"answers": [
{
"id": "1",
"text": "...."
},
{
"id": "2",
"text": "..."
}
]
},
{
"id": "2",
"text": "....."
}
]
}
because simply adding associations like I would do in models are not helping for the third association.
Any help would be appreciated!
So finally after some more searching I found solution which worked. I had to add to my controller include with nested models.
class Api::SongsController < ApplicationController
def index
songs = Song.all
render json: songs, include: ['questions', 'questions.answers']
end
end
And it worked like a charm!
You can do it in your controller with the following structure
respond_with Song.all.as_json(
only: [ :id, :audio, :image ],
include: [
{
questions: {
only: [:id, :text],
include: {
anwers: {
only: [ :id, :text ]
}
}
}
}
]
)

Generate a nested JSON array in JBuilder

I have this models in ruby on rails
Branch model: has_many :menus
class Branch < ActiveRecord::Base
belongs_to :place
belongs_to :city
has_many :menus , dependent: :destroy
belongs_to :type_place
end
Menu model: has_many :products
class Menu < ActiveRecord::Base
attr_accessible :description, :product_name, :price, :category_id, :menu_id
belongs_to :branch
has_many :products, dependent: :destroy
end
Product model:
class Product < ActiveRecord::Base
belongs_to :menu
belongs_to :category
end
with the following code in the view:
if #condition
json.code :success
json.branch do
json.array!(#branches) do |json, branch|
json.(branch, :id, :branch_name, :barcode)
json.menu branch.menus, :id, :menu_name
end
end
else
json.code :error
json.message 'Mensaje de error'
end
gets:
{
"code": "success",
"branch": [
{
"id": 1,
"branch_name": "Sucursal 1",
"barcode": "zPOuByzEFe",
"menu": [
{
"id": 2,
"menu_name": "carta sucursal 1"
}
]
},
{
"id": 2,
"branch_name": "Sucursal Viña Centro",
"barcode": "RlwXjAVtfx",
"menu": [
{
"id": 1,
"menu_name": "carta viña centro"
},
{
"id": 5,
"menu_name": "carta viña centro vinos"
}
]
},
{
"id": 3,
"branch_name": "dddd",
"barcode": "eSbJqLbsyP",
"menu": [
]
}
]
}
But as I get the products of each menu?, I suspect I need to iterate menu, but I have tried several ways without success.
I'm not sure which attributes your product can have but i would try something like:
if #condition
json.code :success
json.array!(#branches) do |json, branch|
json.(branch, :id, :branch_name, :barcode)
json.menus branch.menus do |json,menue|
json.id menue.id
json.menu_name menue.menu_name
json.products menue.products do |json, product|
json.product_attribute_1 product.product_attribute_1
end
end
end
else
json.code :error
json.message 'Mensaje de error'
end
i'm also not quite sure why you try to nest #branches under a branch as stated by:
json.branch do
...
end
i just removed that.
This is from the docs ("this" is the "json.array! method) http://www.rubydoc.info/github/rails/jbuilder/Jbuilder:array!
It's generally only needed to use this method for top-level arrays. If
you have named arrays, you can do:
json.people(#people) do |person|
json.name person.name
json.age calculate_age(person.birthday)
end
{ "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
I had unexpected behaviors using array! and regular iteration as suggested worked perfectly and made my code very readable:
json.user do
json.email #user.email
json.devices #user.devices do |device|
json.partial! 'devices/device', device: device
end
end

Resources