Representable: How to represent a list without a parent node? - ruby-on-rails

I'm using Representable gem to map from a JSON return.
The JSON returned is like:
[
{
"id": 1,
"createdAt": "2014-05-08T18:05:09-03:00",
"updatedAt": "2014-05-08T18:05:09-03:00",
},
{
"id": 2,
"createdAt": "2014-05-08T18:08:39-03:00",
"updatedAt": "2014-05-08T18:08:39-03:00",
}
]
But I don't know how to represent it without a parent node as described here.
My representers is like:
module Representers
module OrdersCollectionRepresenter
include Representable::JSON
collection :list, extend: Representers::OrderCollectionItemRepresenter,
class: OrderCollectionItem
end
end
module Representers
module OrderCollectionItemRepresenter
include Representable::JSON
property :id
end
end

Try:
MultiJson.load(json).map { |hash| Representers::OrderCollectionItemRepresenter.from_hash(hash) }

Related

Rails Serialization - fast_jsonapi / active_model_serializers

I am a bit lost getting on board fast_jsonapi / active_model_serializers to build an API. I have the basics down but seem stuck on a custom solution.
I have this as a serializer:
class AreaSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name, :cost_center, :notes
has_many :children
end
In my area model I have:
has_many :children, -> { Area.where(ancestry: id) }
My controller looks like:
class Api::V1::AreasController < ApiController
def index
render json: AreaSerializer.new(Area.root).serialized_json
end
end
Areas are nested in a hierarchy with the ancestry gem. The output is:
{
"data": [{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"name": "Calgary",
"cost_center": "123456",
"notes": ""
},
"relationships": {
"children": {
"data": [{
"id": "3",
"type": "child"
}]
}
}
}, {
"id": "2",
"type": "area",
"attributes": {
"id": 2,
"name": "Edmonton",
"cost_center": "78946",
"notes": ""
},
"relationships": {
"children": {
"data": []
}
}
}]
}
I am looking for an out put like this:
{
"data": [{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"name": "Calgary",
"cost_center": "123456",
"notes": ""
},
"relationships": {
"areas": {
"data": [{
"id": "3",
"type": "area",
"attributes": {
"id": 3,
"name": "Child Area",
"cost_center": "123456",
"notes": ""
}
}]
}
}
}, {
"id": "2",
"type": "area",
"attributes": {
"id": 2,
"name": "Edmonton",
"cost_center": "78946",
"notes": ""
}
}]
}
The idea being where the nested relationship shows the details etc.
I just started using fast_jsonapi in my rails project.
fast_jsonapi adheres to JSON:API spec which you can see here.
So, you will not be able to use the relationship helper functions (:has_many, :belongs_to) to achieve the output you want, which is nesting the attributes of area inside the relationships key.
"relationships": {
"areas": {
"data": [{
"id": "3",
"type": "area",
"attributes": { // nesting attributes of area
"id": 3,
"name": "Child Area",
"cost_center": "123456",
"notes": ""
}
}]
}
}
JSON:API specifies that:
To reduce the number of HTTP requests, servers MAY allow responses
that include related resources along with the requested primary
resources. Such responses are called “compound documents”.
So instead, you will have the attributes of area inside a key called included.
In order to get the included key in your response, you will need to provide an options hash in your controller to the serializer.
I don't quite understand your model Area relationships, but assume an Area has many SubArea.
class Api::V1::AreasController < ApiController
def index
// serializer options
options = {
include: [:sub_area],
collection: true
}
render json: AreaSerializer.new(Area.root, options).serialized_json
end
end
You Cannot use Association in fast_jsonapi . To Get a response in nested format . You need to add methods and need to create another serializer .
class AreaSerializer
include FastJsonapi::ObjectSerializer
set_type 'Area'
attributes :id, :name, :cost_center, :notes
attribute :childrens do |area, params|
ChildrenSerializer.new(area.childrens, {params:
params})
end
end
class ChildrenSerilizer
include FastJsonapi::ObjectSerializer
set_type 'Area'
attributes :id, :name ...
end
I started using the technique listed above but ended up forking and re-writing the jsonapi-serializer gem so it allows nesting (up to 4 levels deep) and does away with the concept of having relationships and attributes keys. I was also frustrated that it only output ID and TYPE keys of which TYPE is redundant most of the time since the object typically stays the same class in an array as an example and people seldom use real polymorphism (although it still supports this and outputs ID/type for polymorphic relationships).
Probably the best part of my re-write is that it allows deterministic field select-ability anywhere within the json key tree via a new fields input.
https://github.com/rubesMN/jsonapi-serializer

ActiveModelSerializer not serialize object nested children

I have a model Category that use awesome_nested_set gem, so it has children of the model itself. I have created CategorySerializer for the model
class CategorySerializer < ActiveModel::Serializer
attributes :id, :parent_id, :lft, :rgt, :text, :permalink, :children
def children
object.children
end
end
But children is not serialized. I have also tried add has_many :children, serializer: self, the result is this
{
"id": 25918,
"parent_id": null,
"lft": 3,
"rgt": 8,
"text": "ARAG",
"permalink": "25918-arag",
"children": [
{
"id": 25919,
"parent_id": 25918,
"lft": 4,
"rgt": 7,
"text": "Coperchi",
"permalink": "25919-coperchi",
"children": [
{
"id": 25920,
"parent_id": 25919,
"lft": 5,
"rgt": 6,
"text": "Ribaltabili",
"description": "",
"page_title": "",
"meta_key": "",
"meta_description": "",
"key_1": null,
"key_2": null,
"key_3": null,
"extra": null,
"created_at": "2019-03-01T21:08:15.000+01:00",
"updated_at": "2019-04-02T12:27:05.000+02:00"
}
]
}
]
}
Second level of children is successfully serialized but it children is not. Is there a way or alternative to serialize all object children?
If you want deep nesting by default, then you can set following config property in the initializer file
# config/initializers/active_model_serializer.rb
ActiveModelSerializers.config.default_includes = '**
For more details, you can check this.
You can also add another serializer for children as follow
class CategorySerializer < ActiveModel::Serializer
attributes :id, :parent_id, :lft, :rgt, :text, :permalink, :children
def children
ActiveModel::SerializableResource.new(object.children, each_serializer: ChildrenSerializer)
end
end
For more information, you can refer to this link
How about this solution with invoking serializer on children? Be careful to not get into infinite loop with deep nesting
def children
object.children.map { |obj| SomeSerializer.new(obj) }
end

How to map a JSON list to Virtus Model?

I have this JSON like:
[
{
"id": 1,
"createdAt": "2014-05-08T18:05:09-03:00",
"updatedAt": "2014-05-08T18:05:09-03:00",
},
{
"id": 2,
"createdAt": "2014-05-08T18:08:39-03:00",
"updatedAt": "2014-05-08T18:08:39-03:00",
}
]
I'm trying to map this return for a Virtus model. My model is like:
class OrdersCollection
include Virtus.model
attribute :list, Array[OrderCollectionItem]
end
class OrderCollectionItem
include Virtus.model
attribute :id
end
The list attribute is always empty, and I couldn't find out why.
Can someone help me understand how can I map this in a better way?
It keeps returning me: NoMethodError. Expected response.body to respond to #to_hash
Your JSON isn't formatted correctly. You aren't defining the list attribute in your JSON so Virtus doesn't know how to map to it correctly. Try formatting it like this:
{
"list": [
{
"id": 1,
"createdAt": "2014-05-08T18:05:09-03:00",
"updatedAt": "2014-05-08T18:05:09-03:00",
},
{
"id": 2,
"createdAt": "2014-05-08T18:08:39-03:00",
"updatedAt": "2014-05-08T18:08:39-03:00",
}
]
}

JSON API style sideloading in Rails ActiveModel::Serializers

I'm trying to build a JSON API style API using AM::Serializer. I'm running into an issue with sideloading.
I want to be able to build JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"links": {
"secondaries": [ 1, 2, 3 ]
}
}],
"linked" : {
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
}
The code I've been able to come up with looks like:
class PrimarySerializer < ActiveModel::Serializer
attributes :id, :data
has_many :secondaries, key: :secondaries, root: :secondaries
embed :ids, include: true
end
Which generates JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"secondaries": [ 1, 2, 3 ]
}],
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
Is there a way to override the location of the in-element secondaries and sideloaded secondaries such that they live in child nodes link and linked?
The above code is an abstraction of the actual code and may not work. Hopefully it illustrates the point sufficiently.
Thanks!
ActiveModel Serializers can do this. The problem is that the built-in association methods are to restrictive. Instead you must build up the links & linked parts manually.
(This answer refers to the stable 0.8.1 version of ActiveModel Serializers)
Here's a Gist with a complete JSON-API solution https://gist.github.com/mars/97a637560109b8ddfb27
Example:
class ExampleSerializer < JsonApiSerializer # see Gist for superclass
attributes :id, :name, :links
def links
{
things: object.things.map(&:id),
whatzits: object.whatzits.map(&:id)
}
end
def as_json(*args)
hash = super(*args)
hash[:linked] = {
things: ActiveModel::ArraySerializer.new(
object.things,
each_serializer: ThingsSerializer
).as_json,
whatzits: ActiveModel::ArraySerializer.new(
object.whatzits,
each_serializer: WhatzitsSerializer
).as_json
}
hash
end
end

wrong child root name in rabl and can't set child root name

I have to following rabl code to generate some JSON data.
object #event
attributes :id, :EID, :name, :address, :description, :latitude, :longitude, :time, :created_at
node(:rsvp_count) { |event| event.rsvp_users.count }
node(:check_in_count) { |event| event.checkedin_users.count }
node(:FID) { |event| event.creater.FID if event.creater}
child :rsvp_users, :object_root => false do
extends 'users/index'
end
child :checkedin_users, :object_root => false do
extends 'users/index'
end
And the data it generates looks like this:
[
{
"event": {
"id": 2,
"EID": 123458,
"name": "event no.2",
"address": "189 elm st",
"description": "awesome event",
"latitude": 10,
"longitude": 10,
"time": "2013-10-20T18:00:00Z",
"created_at": "2013-08-15T21:06:21Z",
"rsvp_count": 3,
"check_in_count": 0,
"FID": 12345678,
"users": [
{
"id": 4,
"FID": 112233445,
"name": "name1",
"using_app": true
},
{
"id": 3,
"FID": 9999,
"name": "name2",
"using_app": false
},
{
"id": 2,
"FID": 123456789,
"name": "name3-robot",
"using_app": true
}
],
"checkedin_users": []
}
}
]
You can ignore the event hash, the weird stuff is happening at the bottom in the 2 users array.
So as you can see, the child rsvp_users array is showing up with the name users even if I set the root param to "rsvp_users". However, for checkedin_users array (which is empty right now), I don't need to do anything, and it's name is automatically checkedin_users. What is happening here? Is it a bug in rabl? Or is it something that I am missing?
I've encountered the same exact bug, the problem seems to be setting the object_root to false.
Following the comment of Bigxiang I have experimented a bit and found that this works fantastically:
child( {:rsvp => :rsvp}, {:object_root => false} ) do
extends "users/index"
end
Note both the round parentheses "()" and braces "{}".

Resources