I want to use a serializer within another serializer so I can add a key-value pair at the top level but seems like when I do, the lower level serializer isn't working anymore-
My files:
ItemsController
class ItemsController
def index
open_items = Items.
select("distinct on (open_item_id) *").
preload(:company, :project)
total = open_items.count("id")
render json: {
total: total,
items: paginate(open_items, per_page: 2), serializer: ItemsSerializer
}, status: :ok
end
end
ItemsSerializer
class ItemsSerializer < ActiveModel::Serializer
attribute :total
has_many :items, serializer: ItemSerializer
end
ItemSerializer
class ItemSerializer < ActiveModel::Serializer
attributes :id,
:project,
:company,
def company
{
name: object.company.name,
id: object.company.id
}
end
def project
{
name: object.project.name,
id: object.project.id
}
end
end
I want to get another key/value pair to my serializer output in the below so that I can get something like this:
{
"total": 1,
"items": [
{
"id": 42920375,
"company": {
"id": 123,
"name": "CompanyName"
},
"project": {
"id": 456,
"name": "ProjectName"
}
}
]
}
But currently, I'm getting:
{
"total": 1,
"items": [
{
"id": 42920375,
"company_id": 5842,
"project_id": 191741,
}
]
}
I don't think you can use ItemsSerializer that way. It needs to correspond to a model.
Active-Model-serializer will automatically serialize each object in an association with its own serializer:
"In your controllers, when you use render :json for an array of objects, AMS will use ActiveModel::ArraySerializer (included in this project) as the base serializer, and the individual Serializer for the objects contained in that array."
So there's no need to reinvent the wheels. Just do this:
render json: paginate(open_items, per_page: 2), status: :ok
Then each item will be processed by ItemSerializer. I don't see a way to add total here though.
Related
I have order model which has one serialise column as order_details. When I tries to call index action it returns the hash in order_details key.
I want all the values of order_details in main object of order.
My order model as below:
# models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
serialize :order_details
....
end
Controller
# controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
orders = Order.select('id, user_id, total, order_details')
render json: orders, status: :ok
end
end
The JSON response I received is as below:
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"order_details":{
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
}
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"order_details":{
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
}
]
But here I need response in below format
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
]
I was trying to parsed each response using each but result can have hundreds of user object.
Please help here.
Thanks in advance.
Krishna
you should add as_json to your Order model to override the existing method with the same name for you to meet your expected output
def as_json(options = nil)
if options.blank? || options&.dig(:custom)
attrs = attributes.slice("id", "user_id", "total")
attrs.merge(order_details)
else
super
end
end
then, in your controller
def index
orders = Order.all
render json: orders, status: :ok
end
hope that helps
FYR: https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
I want to use the ActiveRecord model serializer to show results from the primary key table and foreign key table. However, I want the results to be presented grouped by a column in the foreign key table.
cats = Cats.paginate(page: params[:page], per_page: 20)
render json: cats, meta: pagination(cats), adapter: :json
In the ActiveRecord model serializer:
class CatSerializer < ActiveModel::Serializer
attributes :cat, :persons
def persons
object.person
end
def cat
{ id: object.id, cat_name: object.name}
end
Now cat_name is not unique and Persons can share many cat_names. please note that Person => has_many Cats, but cat_name can be similar to multiple Persons. How can I show the data in this format:
"results": [
{
"cat": {
"id": 11,
"cat_name": "Luzi",
...
},
"persons": [
{
"id": 1,
"name": "andy"
},
{
"id": 2,
"name": "david"
}
Please also note that groyp_by(&:cat_name) does not work with pagination.
You can use custom serializer that accepts an already groupby ActiveRecord result
def index
#cats = Cat.joins(:persons).group("persons.name")
render json: #cats, serializer: GroupedCatSerializer
end
And you can define custom serializer like
class GroupedCatSerializer < ActiveModel::Serializer
# method override
def serializable_object(options={})
#object.map do |group_key, models|
[ group_key , serialized_models(models) ]
end.to_h
end
private
def serialized_models models
models.map{ |model| CatSerializer.new(model, root:
false) }
end
end
I'm trying to write an update method that processes JSON. The JSON looks like this:
{
"organization": {
"id": 1,
"nodes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
Organization model:
has_many :nodes
accepts_nested_attributes_for :nodes, reject_if: :new_record?
Organization serializer:
attributes :id
has_many :nodes
Node serializer:
attributes :id, :title, :description
Update method in the organizations controller:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_attributes: node_params.except(:id))
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(nodes: [:id, :title, :description])
end
I also tried adding accepts_nested_attributes_for to the organization serializer, but that does not seem to be correct as it generated an error (undefined method 'accepts_nested_attributes_for'), so I've only added accepts_nested_attributes_for to the model and not to the serializer.
The code above generates the error below, referring to the update_attributes line in the update method. What am I doing wrong?
no implicit conversion of String into Integer
In debugger node_params returns:
Unpermitted parameters: id
{"nodes"=>[{"id"=>101, "title"=>"gsdgdsfgsdg.", "description"=>"dgdsfgd."}, {"id"=>1, "title"=>"ertret.", "description"=>"etewtete."}]}
Update: Got it to work using the following:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_params)
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(:id, nodes_attributes: [:id, :title, :description])
end
To the serializer I added root: :nodes_attributes.
It now all works, but I'm concerned about including the id in node_params. Is that safe? Wouldn't it now be possible to edit the id of the organization and node (which shouldn't be allowed)? Would the following be a proper solution to not allowing it to update the id's:
if organization.update_attributes(nodes_params.except(:id, nodes_attributes: [:id]))
looks super close.
Your json child object 'nodes' need to be 'nodes_attributes'.
{
"organization": {
"id": 1,
"nodes_attributes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
You can do this sort of thing. Put this in your controller.
before_action do
if params[:organization]
params[:organization][:nodes_attributes] ||= params[:organization].delete :nodes
end
end
It will set the correct attribute in params and still use all the accepts_nested_attributes features.
I am trying to use custom serializers for the relationships in a serializer and the json_api adapter enabled. However the relationships are not serialized correctly (or, better, not at all displayed/serialized).
PostController.rb
def index
render json: Post.all, each_serializer: Serializers::PostSerializer
end
Serializer
module Api
module V1
module Serializers
class PostSerializer < ActiveModel::Serializer
attributes :title, :id
belongs_to :author, serializer: UserSerializer
has_many :post_sections, serializer: PostSectionSerializer
end
end
end
end
JSON output:
{
"data": [
{
"attributes": {
"title": "Test Title"
},
"id": "1",
"relationships": {
"author": {
"data": {
"id": "1",
"type": "users"
}
},
"post_sections": {
"data": [
{
"id": "1",
"type": "post_sections"
}
]
}
},
"type": "posts"
}
]
}
As you can see, the relationships are not fulfilled, which happens only if I specify a custom serializer for the relationships!!
If I do something like this:
module Api
module V1
module Serializers
class PostSerializer < ActiveModel::Serializer
attributes :title, :id
belongs_to :author # no custom serializer!
has_many :post_sections # no custom serializer!
end
end
end
end
The relationships are shown correctly, but not using a custom serializer...
What's the issue here ?
EDIT
According to the json API 1.0 Format, what I am getting back is the so-called resource identifier object.
The following primary data is a single resource identifier object that
references the same resource:
{ "data": {
"type": "articles",
"id": "1" } }
Is there a way to prevent getting resource identifier objects, and get the actual data instead ?
Relationships only returns id and type according to json-api exmaples. If you need to return more information about this relation you should add include option on your render action.
Ex.
PostController.rb
class PostsController < ApplicationController
def show
render json: #post, include: 'comments'
end
end
Serializers
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :content
has_many :comment, serializer: CommentSerializer
end
class CommentSerializer < ActiveModel::Serializer
attributes :id, :title, :content
end
JSON output:
{
"data": {
"id": "1",
"type": "post",
"attributes": {
"title": "bla",
"content": "bla"
},
"relationships": {
"comment": {
"data": [
{
"type": "comments",
"id": "1"
}
]
}
}
},
"included": {
{
"id": "1",
"type": "comments",
"attributes": {
"title": "test",
"content": "test"
}
}
]
}
Just to add to #Bruno Bacarini's answer, you may also include chained associations by using:
render #posts, include: ['authors.profile', 'comments']
source: joaomdmoura's comment on github
I have a couple models. Let's call them Widget and Gadget.
My #index for for Widget and Gadget looks something like this
def index
widgets = Widget.all
if widgets
respond_with widgets, each_serializer: Api::V1::WidgetSerializer
else
render json: { message: [t(:not_found_widget)] }, status: :not_found
end
end
def index
gadgets = Gadget.all
if gadgets
respond_with gadgets, each_serializer: Api::V1::GadgetSerializer
else
render json: { message: [t(:not_found_gadget)] }, status: :not_found
end
end
And my serializers...
class Api::V1::WidgetSerializer < ActiveModel::Serializer
attributes :id, :desc
end
class Api::V1::GadgetSerializer < ActiveModel::Serializer
attributes :id, :desc
end
However, I have the need for a resource that returns both of those in 1. I need both widgets and gadgets returned at once. So the json would look like...
{
"widgets": [
{
"id": 1,
"desc": "One"
},
{
"id": 2,
"desc": "Two"
}
],
"gadgets": [
{
"id": 1,
"desc": "One"
},
{
"id": 2,
"desc": "Two"
}
]
}
How can I achieve this. Something like
widgets = Widget.all
gadgets = Gadget.all
respond_with widgets, each_serializer: Api::V1::WidgetSerializer, gadgets, each_serializer: Api::V1::GadgetSerializer
However, this clearly doesn't work.
Serializer classes don't have to match AR models. Serializer classes should be used as your representation of your JSON you want to produce. In your example, let's assume to call a new serializer DashboardSerializer, then you can put both widgets and gadgets there:
class DashboardSerializer < ActiveModel::Serializer
self.root = false
attributes :widgets, :gadgets
def widgets
Widget.all
end
def gadgets
Gadget.all
end
end
The only way I can see this working is if you had a model that has_many widgets and has_many gadgets with it's own serializer as AMS will apply the correct serializers to associations declared in a serializer.
something like
class Composite < ActiveRecord::Base
has_many :widgets
has_many :gadgets
end
class CompositeSerializer < ActiveModel::Serializer
attributes :id
has_many :widgets
has_many :gadgets
end