JSON API style sideloading in Rails ActiveModel::Serializers - ruby-on-rails

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

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, how do I get more info the JSON API response about the association?

I am using Rails 5 and AMS 10. My questions are in bold.
gem 'active_model_serializers', '~> 0.10.0.rc1'
rails -v
Rails 5.0.0.beta3
I am using the JSON API specifications. I have this in an config/initializers/active_model_serializer.rb file:
ActiveModel::Serializer.config.adapter = :json_api
Is the point of this to apply convention to JSON API responses which have total freedom in customization of the nodes? This leads to faster understanding of the data from developers who consume the API for like front-end Ember apps or mobile native apps?
This is my RentalUnitSerializer:
class RentalUnitSerializer < ActiveModel::Serializer
attributes :id, :rooms, :bathrooms, :price, :price_cents
belongs_to :user
end
This is my UserSerializer:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email
def name
names = object.name.split(" ")
"#{names[0].first}. #{names[1]}"
end
end
This is my rental_units_controller:
class RentalUnitsController < ApplicationController
before_action :set_rental_unit, only: [:show, :update, :destroy]
# GET /rental_units
def index
#rental_units = RentalUnit.all
render json: #rental_units
end
This is my JSON response when I hit the /rental_units endpoint.
{
"data": [{
"id": "1",
"type": "rental_units",
"attributes": {
"rooms": 2,
"bathrooms": 2,
"price": null,
"price_cents": 50000
},
"relationships": {
"user": {
"data": {
"id": "1",
"type": "users"
}
}
}
}, {
"id": "2",
"type": "rental_units",
"attributes": {
"rooms": 2,
"bathrooms": 2,
"price": null,
"price_cents": 50000
},
"relationships": {
"user": {
"data": {
"id": "1",
"type": "users"
}
}
}
},
How do I get the user's name in the relationships section of the JSON API response?
You need to add .includes(:user) to your query on the first line of your index method in RentalUnitsController, thusly:
#rental_units = RentalUnit.includes(:user).all
That will add the actual data from the :user relationship's target object to your RentalUnit object and to the serialized JSON.

jsonapi and active_mode_serializers how to get relationship attributes in the response?

tech context: rails 4.2.2, active_model_serializers 0.10.0.rc2
Given a cart and a list of product when I add a product to the cart I expect to get as response:
{
"data": {
"id": "575",
"type": "carts",
"attributes": {
"name": "cart 1"
},
"relationships": {
"cart_products": {
"data": [
{
"type": "cart_products",
"id": "32",
"attributes": {
"product_id": 456
}
}
]
}
}
}
}
unfortunately ,
the current response is
{
"data": {
"id": "575",
"type": "carts",
"attributes": {
"name": "cart 1"
},
"relationships": {
"cart_products": {
"data": [
{
"type": "cart_products",
"id": "32",
}
]
}
}
}
}
is there a way to have relationship attributes rendered?
The JSON:API Spec explains how the relationship data should be. What you are asking for is actually meant to be nested, or better yet "Included" as per the spec.
I would suggest you read a bit over there http://jsonapi.org/format/#document-compound-documents for more details on the spec regarding included/nested relationships
Also, regarding your question, you need to tell your serializer to render included elements, like so: render #posts, include: ['authors', 'comments']
See here for more info: https://github.com/rails-api/active_model_serializers
According to the manual:
render json: #posts, include: ['author', 'comments', 'comments.author']
# or
render json: #posts, include: 'author,comments,comments.author'
More details:
http://jsonapi.org/format/#document-compound-documents
https://github.com/rails-api/active_model_serializers/blob/a032201a91cbca407211bca0392ba881eef1f7ba/docs/general/adapters.md#included

Representable: How to represent a list without a parent node?

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) }

Ruby: Outputting Sequel model associations

I don't think this is possible using just Sequel models, but what I would like to do is have my parent model (Author) output its child model (Book) when I do something like Author.to_json. Here is my code:
require 'sequel'
require 'json'
db = Sequel.connect('postgres://localhost/testing');
class Sequel::Model
self.plugin :json_serializer
end
class Author < Sequel::Model(:author)
one_to_many :book, key: :author_id, primary_key: :id
def get_author
Author.each do |e|
books = Array.new
e.book.each do |f|
books.push(f.values)
end
e.values[:books] = books
puts JSON.pretty_generate(e.values)
end
end
end
class Book < Sequel::Model(:book)
end
author = Author.new
author.get_author
My output looks something like this:
[{
"id": 1,
"name": "Jack Johnson",
"books": [{
"id": 4,
"name": "Songs with Chords",
"genre": "Learning",
"author_id": 1
}]
}, {
"id": 2,
"name": "Mulder",
"books": [{
"id": 2,
"name": "UFOs",
"genre": "Mystery",
"author_id": 2
}, {
"id": 3,
"name": "Unexplained Paranorma",
"genre": "Suspense",
"author_id": 2
}]
}, {
"id": 3,
"name": "Michael Crichton",
"books": [{
"id": 1,
"name": "Jurassic Park",
"genre": "Incredible",
"author_id": 3
}]
}]
That's exactly how I want my output to look, but the way I'm going about it is questionable. Ideally, if there's some function already on the Author model that allows me to do this, that'd be awesome... as I don't want to have to implement a get_model function for all of my models that have different associations. Also, I was hoping NestedAttributes could lend a hand here, but it doesn't look like it.
I'm very new to Ruby and Sequel, so I'd like to know if there's a simpler way of doing this?
Sequel's json_serializer plugin already has support for associations via the :include option. Also, you need to fix your association. Something like this should work:
Author.one_to_many :books
Author.order(:id).to_json(:include=>:books)

Resources