Ember Cli Mirage: Active Model Adapter with JSONAPISerializer - ember.js

I am on halfway of implementing JSON API structure (with underscore attributes).
Actual state for development environment is:
I use the Active Model Adapter structure for requesting to the backend for resources and backend response me with JSON API structure.
In Application Serializer I am using JSONAPISerializer. I override methods:
serializeBelongsTo
keyForRelationship
keyForAttribute
serialize
serializeAttribute
serializeHasMany
and for development, everything works for me (backend in Rails communicate with Ember very good).
The problem is with Ember CLI Mirage and conventions (not sure if there are simple solutions or I need to override again methods in this addon).
Actual state with Ember Cli Mirage and test environment:
I am using import { JSONAPISerializer } from 'ember-cli-mirage';
and then trying to manipulate proper request and then transform it for JSON API format.
It could work like this:
Ember Adapter (Active Model Adapter format - with underscore attributes) ---> Mirage Serializer should get request (find resources created before in tests with associations) and then response it with JSON API format ---> JSON API Serializer could catch it and fill Ember DS.
For now, I have a missing part to serialize it for all cases to JSON API standard (with underscored attributes)
Where should I do this transformation to minimize overriding JSONAPISerializer Mirage Serializer.
I noticed that there are some helpers, but I have a problem to wrap this knowledge together
(http://www.ember-cli-mirage.com/docs/advanced/route-handlers#helpers)
UPDATE:
Example of structure from Backend:
{
"data": {
"id": "6",
"type": "first_resource",
"attributes": {
"id": 6,
"my_attribute": "my_attribute"
},
"relationships": {
"second_resources": {
"data": [
{
"id": "16",
"type": "second_resource"
}
]
},
"third_resource_other_type": {
"data": {
"id": "1",
"type": "third_resource"
}
},
"fourth_resource": {
"data": {
"id": "1",
"type": "fourth_resource"
}
}
},
"links": {
"fifth_resources": "/api/v1/first_resources/6/fifth_resources"
}
},
"included": [
{
"id": "1",
"type": "fourth_resource",
"attributes": {
"id": 1,
"my_attribute": "my_attribute"
},
"links": {
"sixth_resource": "/api/v1/fourth_resources/1/sixth_resource"
}
},
{
"id": "16",
"type": "second_resource",
"attributes": {
"id": 16,
"my_attribute": "my_attribute"
},
"relationships": {
"eighth_resources": {
"data": []
}
},
"links": {
"seventh_resources": "/api/v1/second_resources/16/seventh_resources"
}
},
{
"id": "17",
"type": "second_resource",
"attributes": {
"id": 17,
"my_attribute": "my_attribute"
},
"relationships": {
"eighth_resources": {
"data": []
}
},
"links": {
"seventh_resources": "/api/v1/second_resources/17/seventh_resources"
}
},
{
"id": "15",
"type": "second_resource",
"attributes": {
"id": 15,
"my_attribute": "my_attribute"
},
"relationships": {
"eighth_resources": {
"data": [
{
"id": "26",
"type": "eighth_resource"
},
{
"id": "24",
"type": "eighth_resource"
}
]
}
},
"links": {
"seventh_resources": "/api/v1/second_resources/15/seventh_resources"
}
},
{
"id": "26",
"type": "eighth_resource",
"attributes": {
"id": 26,
"my_attribute": "my_attribute"
}
}
]
}
UPDATE2
structure from mirage response:
data: {
attributes: {
my_attribute: 'my_attribute',
second_resource_ids: [36, 37],
fifth_resource_ids: []
},
id: 11,
relationships: {
third_resource_other_type: {data: null}
fourth_resource: {data: null}
second_resources: {data: []}
},
type: "first_resources"
}
resources in tests:
server.create('second-resource', {
id: 36,
first_resource_id: '11',
my_attribute: "my_attribute"
});
server.create('eighth-resource', {
id: 140,
second_resource_id: 37
});
server.create('eighth-resource', {
id: 141,
second_resource_id: 37
});
server.create('second-resource', {
id: 37,
first_resource_id: '11',
eighth_resource_ids: [140, 141]
});
server.create('first-resource', {
id: 11,
second_resource_ids: [36, 37]
});
first_resource model in mirage:
export default Model.extend({
third_resource_other_type: belongsTo(),
fourth_resource: belongsTo(),
fifth_resources: hasMany(),
second_resources: hasMany()
});

Let's try to focus a single relationship, since there's a lot going on in the question you've posted. We'll look at second-resource.
It looks like Mirage is sending back second_resource_ids under the attributes key of the first_resource primary data in the JSON:API payload. That tells me Mirage thinks second_resource_ids is an attribute of first_resource, when in fact it's a relationship.
Assuming your Models & Relationships are setup correctly, you need to tweak the way you're creating data in Mirage.
If you take a look at the Associations section of the Defining Routes guide, you'll see this message:
Mirage's database uses camelCase for all model attributes, including foreign keys (e.g. authorId in the example above)
Right now, you're doing this:
server.create('second-resource', {
id: 36,
first_resource_id: '11',
my_attribute: "my_attribute"
});
server.create('first-resource', {
id: 11,
second_resource_ids: [36, 37]
});
But from Mirage's perspective, you need to use camelCase IDs, or just pass in the relationships, to set these up correctly. Something like this:
let firstResource = server.create('first-resource', {
id: 11
});
server.create('second-resource', {
id: 36,
firstResource,
myAttribute: "my_attribute"
});
You could also pass in the foreign key on creation if you wanted - just be sure to use camelCase:
server.create('second-resource', {
id: 36,
firstResourceId: '11',
myAttribute: "my_attribute"
});
Just remember that the formatting decisions for things like attributes and foreign keys (things like some-attribute vs. some_attribute or relationship-id vs. relationship_id) are made at the serializer layer. When dealing with Mirage's ORM and database, you want to stick to camelCase, regardless of the format your Serializer emits.
For more information, have a look at these sections from the docs:
"Associations and serializers" section of the Quickstart
Defining relationships
"Working with relationships" section of the Factories guide

Related

Load multiple model data in same api call emberjs?

So here is two models that i have defined in emberjs
match.js
import DS from 'ember-data';
export default DS.Model.extend({
team: DS.belongsTo('team', {async:true}),
opponent: DS.belongsTo('team', {async: true}),
type: DS.attr('string'),
squad: DS.attr('boolean')
});
and
team.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
logo: DS.attr('string')
});
I am already loading the match as a model. In the same api call i also want to load the model data for team. The api response that i have till now is
{
"meta":{
"type":"match"
},
"data":[
{
"id":1119536,
"type":"match",
"attributes":{
"id":1119536,
"team":{
"type":"team",
"id":1,
"attributes":{
"id":1,
"name":"England",
"logo":null
}
},
"opponent":{
"type":"team",
"id":3,
"attributes":{
"id":3,
"name":"Pakistan",
"logo":null
}
}
}
}
]
}
The match model data get loaded properly but i am having issues for the same with team data. The response is from network in browser and i already checked the model using ember plugin on browser that team data doesn't load. How can i use the same api call to load multiple models.
a few things to notice:
dont put the id in attributes
dont name an attribute type. Really dont! It's a reserved keyword.
relationships are not attributes and should be under relationships
use the included array to sideload data
ids must be strings
so for example this would be a valid payload:
{
"meta": {
"type": "match"
},
"data": [
{
"id": "1119536",
"type": "team",
"attributes": {
"match-type": "match"
},
"relationships": {
"team": {
"data": {
"type": "team",
"id": "1"
}
},
"opponent": {
"data": {
"type": "team",
"id": "3"
}
}
}
}
],
"included": [
{
"type": "team",
"id": "1",
"attributes": {
"name": "England",
"logo": null
}
},
{
"type": "team",
"id": "3",
"attributes": {
"name": "Pakistan",
"logo": null
}
}
]
}

Loopback include filter within scope works for GET but fails for POST request

I have this scope defined in my order.json which has relation with branch and customer along with other properties.
"name": "Order",
"properties":{...},
"relations": {...},
"acls": {...},
"scope": {
"include": [
{"relation": "branch", "scope": { "fields": "BranchName" } },
{"relation": "customer", "scope": { "fields": "CustomerName" } }
]
}
This works well as expected in all GET requests with following results
[
{
"OrderDate": "2018-01-12T17:52:21.000Z",
"CustomerId": 39,
"BranchId": 5,
"CustomerRef": "Order by Phone",
...
"CreatedBy": 1,
"id": 1,
"branch": {
"BranchName": "aaaa",
"id": 5
},
"customer": {
"CustomerName": "xxxx",
"id": 39
}
}
]
I was expecting a similar result, however, the response array received after a successful POST request does not include BranchName and CustomerName info from the related models.
Am I doing it correctly? or is there any other way to get back information from related models after a Create/Update operation. I am just trying to avoid another GET request immediately after Create/Update.
You can use the Operation hook after save.
Order.observe('after save', function(ctx, next) {
if (ctx.instance) {
ctx.instance.relatedmodel = someFunctionToGetRelatedModel();
}
next();
});
Whatever is inside the ctx.instance should be included in loopbacks responses.
You just have to figure out how to seamlessly pull the related model details, you want to include.

How to iterate and get the properties and values of a JSONAPI response in emberJS?

I have following ember request
this.store.createRecord('food_list',requestObj
).save().then((response) => {
console.log(response);
console.log(response.id); // This is working
console.log(response.food_list_code); //this does NOT work !!!!!
}
It will call an API and save a record to database and then returns following response.
{
"links": {
"self": "/api/food_list"
},
"data": {
"type": "",
"id": "da6b8615-3f4334-550544442",
"attributes": {
"food_list_date": "2013-02-14 23:35:19",
"food_list_id": "da6b8615-3f4334-550544442",
"food_list_code": "GORMA",
},
"relationships": {
"food_list_parameters": {
"data": [
{
"type": "food_list_parameter",
"id": "RERAFFASD9ASD09ASDFA0SDFASD"
}
]
},
"food_new_Name": {
"data": {
"type": "food_new_Name",
"id": "AKASDJFALSKDFKLSDF23W32KJ2L23"
}
}
},
"links": {
"self": "/api/BLAH/BLAH/BLAH"
}
}
}
but since above response is a JSONAPI in form of an ember object, I dont know how to parse it.
If I try to get response.id, I get the string da6b8615-3f4334-550544442
But how to get value for food_list_code in response block. Or how to iterate the response object to get "food_list_code" and "food_list_date" ?
The output for console.log(response) is as following ember class
Class {__ember1500143184544: "ember1198", store: Class, _internalModel: InternalModel, currentState...
I appreciate your help.
M.

multiple relations using same model type in ember data

I have an account model that has the following:
export default Model.extend({
primaryStaff: belongsTo('staff'),
secondaryStaff: belongsTo('staff')
});
primaryStaff maps to the primaryStaffId key on the account model and secondaryStaff maps to secondaryStaffId. Is there a way to allow these to both point to the staff model?
Not having any luck. Thanks!
Update:
I'm using the JSON API Adapter -- here's a sample payload:
{
"data": {
"type": "account",
"id": "17",
"attributes": {
"businessId": 1,
"userId": 22,
"scopes": [
"customer",
"customer-22"
],
"keyCode": null,
"lockboxCode": null,
"notes": null,
"active": false,
"createdAt": "2017-01-31T20:13:39.465Z",
"updatedAt": "2017-02-20T03:49:17.308Z",
},
"relationships": {
"business": {
"data": {
"type": "business",
"id": "1"
}
},
"customer": {
"data": {
"type": "customer",
"id": "22"
}
},
"secondaryStaff": {
"data": null
},
"primaryStaff": {
"data": {
"type": "staff",
"id": "1"
}
}
}
}
}
You need to modify application (or model) serializer.
Please take a look at this twiddle

How do you handle large relationship data attributes and compound documents?

If an article has several comments (think thousands over time). Should data.relationships.comments return with a limit?
{
"data": [
{
"type": "articles",
"id": 1,
"attributes": {
"title": "Some title",
},
"relationships": {
"comments": {
"links": {
"related": "https://www.foo.com/api/v1/articles/1/comments"
},
"data": [
{ "type": "comment", "id": "1" }
...
{ "type": "comment", "id": "2000" }
]
}
}
}
],
"included": [
{
"type": "comments",
"id": 1,
"attributes": {
"body": "Lorem ipusm",
}
},
.....
{
"type": "comments",
"id": 2000,
"attributes": {
"body": "Lorem ipusm",
}
},
]
}
This starts to feel concerning, when you think of compound documents (http://jsonapi.org/format/#document-compound-documents). Which means, the included section will list all comments as well, making the JSON payload quite large.
If you want to limit the number of records you get at a time from a long list use pagination (JSON API spec).
I would load the comments separately with store.query (ember docs), like so -
store.query('comments', { author_id: <author_id>, page: 3 });
which will return the relevant subset of comments.
If you don't initially want to make two requests per author, you could include the first 'page' in the authors request as you're doing now.
You may also want to look into an addon like Ember Infinity (untested), which will provide an infinite scrolling list and automatically make pagination requests.