Ember Data: Mapping Arrays - ember.js

I am trying to define an Ember Data Model from the following response I am getting from the API. I am having issues defining the num_activity_per_map field since its an array of JSON objects however they do not have an id. If I use DS.hasMany('activityPerDate', {async:true}) complains it doesn't have a primary key.
{
"stats": {
"num_users": 44,
"num_activity_per_date": [
{
"date": "04-10-2015",
"count": 6
},
{
"date": "05-10-2015",
"count": 8
},
{
"date": "06-10-2015",
"count": 7
},
{
"date": "07-10-2015",
"count": 5
},
{
"date": "08-10-2015",
"count": 3
}
],
}
}

You have a few different options:
Treat num_activity_per_date as a raw attribute by using DS.attr() to declare it.
Treat num_activity_per_date as a custom attribute type by writing a custom transform.
Treat num_activity_per_date as an embedded relationship.
It seems like you might want to go with #3. You'll want to override the serializer for the parent model to look something like this:
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
activityPerDate: { embedded: 'always' }
}
});
Then declare activityPerDate: DS.hasMany('activityPerDate') and it should be taken care of. (I say should because I've never gotten embedded records to work properly. But it's also been a very long time since I've tried. YMMV.)

Related

Creating model with nested objects and arrays in Loopback version LB4

I've just started with Loopback for the first time and I've started with LB4, their newest release. I'm looking to create a model with nested objects and arrays as per my JSON schema to which I followed the documentation which allowed me to create the base values of my schema, but I need to create the fields inside the objects and arrays, but I can't find the documentation or articles to help me understand this...
This is my JSON schema I'm trying to create a LB4 model with:
"socialProfiles": {
"facebook": {
"linked": 1,
"pullData": 1,
"linkID": 4434343,
"profile": "https://www.facebook.com/FBURL",
"registered": {
"date": "2018-05-04T12:41:27.838Z",
"verified": "2018-05-04T12:41:27.838Z",
"by": {
"id": 1,
"user": "USER"
}
}
},
}
Using the LB4 documentation, I can create my main field socialProfiles but I can't find where I go to create my fields inside this object... Here's my LB4 model code
import {Entity, model, property} from '#loopback/repository';
#model()
export class Users extends Entity {
#property({
type: 'object',
})
socialProfiles?: object;
constructor(data?: Partial<Users>) {
super(data)
}
}
How to do this?
If you want to store the object in the model itself (not with a relation), you can create an interface with something like:
export interface ISocialProfile {
"linked": number,
"pullData": number,
"linkID": number,
"profile": string,
"registered": {
"date": Timestamp,
"verified": Timestamp,
"by": {
"id": number,
"user": string
}
}
}
and then in your model, you can just add the type:
socialProfiles?: {[name: string]: ISocialProfile};

Ember Nested Data in JSON Response

I'm having some trouble accessing some nested data in one of my Ember models. Below is the JSON response...
{
"fields": {
"header": {
"name": "header",
"type": "text",
"localize": false,
"options": []
},
"body": {
"name": "body",
"type": "textarea",
"localize": false,
"options": []
}
},
"entries": [
{
"header": "header test",
"body": "body test body test body test",
"_mby": "5a395e8430c2ddoc683600766",
"_by": "5a395e8430c2ddoc683600766",
"_modified": 1513709605,
"_created": 1513709605,
"_id": "5a3960253d578doc735798511"
}
],
"total": 1
}
Here is my current serializer
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType)
{
payload = {
entries: payload.entries
};
return this._super(store, primaryModelClass, payload, id, requestType);
}
});
I'm trying to get the Entries in my template. Perhaps I need some help better serializing this with NormalizeResponse? I'm very stumped so please share if you have any ideas!
Thanks!!
The ember-data RESTAdapter expects the JSON to contain keys that correspond to model names and values to be arrays of JSON objects with key-value pairs according to the respective models' attribute-name--attribute-value-type definitions*.
So if you have a model named entry containing header: attr('string'), body: attr('string'), the following JSON should lead to an entry in the store:
{
"entries": [{
"header": "header test",
"body": "body test body test body test",
"id": "5a3960253d578doc735798511"
}],
}
There is a way to convince ember-data to customize the id field name (to accept _id instead of id). The build-in pluralization rules should include the entry->entries pluralization (but I cannot vouch for it).
Any top-level key that does not correspond to a model name should be ignored anyway, so no need to filter it out manually (unless you have models named field or total).
If you then return this.store.findAll('entry') (or query('entry',{/*something*/}) in your route's model hook, you should be able to look over {{#each model as |entry|}} in your controller's template.
(It is good practice to not use model as the model name, but if you want it to be entries, you have to specify that explicitly somewhere, i.e. the controller or the route's setupController method.)
*The point of repeating the name of the model as top-level key in the json response (presumably to an API endpoint of the same name) is that you can include other models if you whish and they will be added to the store as well.

Ember DS.hasMany children not showing up with JSON API

I am trying to use the JSON API Adapter with ember-cli 2.5.1 , but I'm having a bit of trouble.
I have a todo-list.js model, which has a "hasMany" relationship to todo-list-item.js. Getting the todo-list, the server returns this:
{
"links": {
"self": "http://localhost:4200/service/v1/todolists/b-tlst-af69786c-cbaf-4df9-a4a3-d8232677006a"
},
"data": {
"type": "todo-list",
"id": "b-tlst-af69786c-cbaf-4df9-a4a3-d8232677006a",
"attributes": {
"name": "b1-TodoList",
"created-on": 1468474962458,
"modified-on": 1468474962458
},
"relationships": {
"todolistitems": {
"data": {
"type": "todo-list-item",
"id": "b-todo-b5e3c146-d93a-4f97-8540-875bbcd156ca"
}
}
}
}
}
If there had been two TodoListItem children instead of one, the value of that "data" key would have been an array, rather than an object.
After receiving this, I was expecting the Ember Chrome plug-in's "Data" tab to show 1 TodoList and 1 child TodoListItem. Instead, it shows 1 TodoList and 0 TodoListItems.
I note from the Network tab that the browser never makes a request to get the items listed in the "data" section of the response.
Is the relationships section above correct and sufficient?
It turns out to have been caused by promise misunderstandings on the client side, and additionally, on the server I had to put dashes in the "relationships" key (i.e. "todo-list-items") and make the value of "data" an array.

Action Cables, nested JSON attributes and JSONAPI

I have a fairly specific problem that I was hoping one of you really intelligent folk might know a solution for (or even a workaround at this stage)
Specifically, I'm dealing with action cables, nested JSON, and the JSONAPI.
I have an asset model, which has some attributes like name, desc etc. but it also has an attribute called state which is a complex nested JSON object.
// app/models/asset.js
export default DS.Model.extend({
// Attributes
name: DS.attr('string'),
desc: DS.attr('string'),
state: DS.attr(),
lastSeen: DS.attr('date'),
});
When anything on the asset changes in the backend, it is pushed down the cable to Ember, where it does a pushPayload(data), the payload looks like this;
{
"data": {
"id": "5",
"type": "assets",
"attributes": {
"asset_id": "962ABC",
"name": "962 ABC",
"desc": "Test Vehicle",
"activation_status": "active",
"state": {
"avl": {
"longitude": 152.9475426,
"reported_at": "2017-06-22T21:59:52Z"
},
"dfm": {
"in_alarm": false,
"reported_at": "2017-06-21T05:46:57Z",
"sensitivity": "normal",
"voice_prompt": false,
"driver_detected": true,
},
"tpms": {
"system_desc": "123ABC",
"system_type": "123_abc"
}
},
"last_seen": "2017-06-22T21:59:54.000Z"
},
"relationships": {
"company": {
"data": {
"id": "1",
"type": "companies"
}
},
"events": {
"links": {
"related": "/events/?asset_id=5"
}
},
"messages": {
"links": {
"related": "/messages/?asset_id=5"
}
}
}
}
}
This all works fine and dandy, updates to the asset & state are displayed as they happen thanks to the cable, and state is read only so I don't have to worry about saving anything. HOWEVER, I have noticed that when any single attribute on state changes in the backend, the entire asset is pushed down from the backend (this should be fine), and then this fires the observer for state and also all observers for state descendants - whereas I need it to only fire the observer for the state attribute that changed.
I have tried a number of things and each seemed to either not work at all, or still continue to update state in a way that fired off all state observers.
What I have tried;
ember-model-data-fragments (while it should work, I think the way that the action cable pushes the data must subvert this?)
embedded records (requires an ID for state, not currently compatible with JSONAPI)
raw json transform (making the json into ember objects, didn't seem to help)
Can anyone suggest a strategy or solution for me to try? I've spent nearly 2 days on this problem.. I would even settle for just splitting it up between avl/tpms/dfm, as long as when an attribute in one of those sections is changed, it doesn't notify properties from the other 2 sections.
Thanks

Ember - Only update fields returned in response JSON

we would like to add lazy loading functionality to our ember project, but it looks like ember will always override fields not returned by the response JSON with NULL. First I get a list of users:
GET https://example.com/users
{
"users": [
{
"id": 1,
"name": 'user1',
"email": 'email#user1.com',
"images": [],
"posts": []
},
{
"id": 2,
"name": 'user2',
"email": 'email#user2.com',
"images": [],
"posts": []
},
{
"id": 3,
"name": 'user3',
"email": 'email#user3.com',
"images": [],
"posts": []
},
]
}
This provides a minimal set of user information, with two empty hasMany relations "images" and "posts".
Now, if somebody want to see the users posts or images he would click a button which triggers the lazy loading:
GET https://example.com/userImages/1
{
"user": {
"id": 1,
"images": [1,2,3,4]
},
"images": [
{
"id": 1,
"name": "image1",
"path" "path/to/img1/"
},
{
"id": 2,
"name": "image2",
"path" "path/to/img2/"
},
{
"id": 3,
"name": "image3",
"path" "path/to/img3/"
},
{
"id": 4,
"name": "image4",
"path" "path/to/img4/"
}
]
}
To reduce traffic to a minimum, only the newly loaded information is included. After the adapter has deserialzed and pushed the new data to the store, all fields from User1 which are not included in the payload (name, email) are set to NULL in the ember store (tested with store.pushPayload('model', payload)).
Is there a possibility to update only incoming data? Or is there a common best practice to handle such a case?
Thanks in advance for your help
EDIT:
One possibility would be to extend the ember-data "_load()" function with the block
for (var key in record._data) {
var property = record._data[key];
if( typeof(data[key]) == 'object' && data[key] == null ) {
data[key] = property;
}
}
But this is the worst possible solution I can imagine.
I think what you want is the store's update method. It's like push (or pushPayload), except that it only updates the data that you give it.
Your property returns a promise and that promise returns whatever came back from the server.
foobar: function() {
return this.store.find('foobar');
}
When the promise resolves, you have two versions of the data, the one already rendered in the client (dataOld) and the one that just returned from the backend (dataNew). To update the client without removing what hasn't change, you have to merge the old and the new. Something along the lines of:
foobar: function() {
var dataOld = this.get('foobar');
return this.store.find('foobar').then(function(dataNew) {
return Ember.$.merge(dataOld, dataNew);
});
}