Ember extractArray payload not usable in application - ember.js

I'm trying to modify my JSON payload in the REST serializer so I can use it in my Ember application as I want.
This is how the real JSON from the server looks like:
{
"folders": [
{
"file_ids": [
2001,
2002,
2003
],
"child_folder_ids": [
2
],
"id": 1,
"name": "Root folder",
"parent_folder_id": null,
"parent_folder_ids": []
}
],
"related_folders": [
{
"file_ids": [],
"child_folder_ids": [
3
],
"id": 2,
"name": "Child folder",
"parent_folder_id": 1,
"parent_folder_ids": [
1
]
}
]
}
This is how the Ember folder model looks like:
export default DS.Model.extend({
name: DS.attr('string'),
files: DS.hasMany('file'),
childFolders: DS.hasMany('folder', {inverse: 'parentFolder'}),
parentFolders: DS.hasMany('folder'),
parentFolder: DS.belongsTo('folder', {inverse: 'childFolders'}),
});
The childFolders and parentFolders are inside the related_folders in the JSON I'm getting from the server. Basically this are also folders so I want to use the folder model for this. I've tried to modify the payload in the REST serializer so the related folders will be part of the folders array.
This is how my serializer looks like:
export default DS.RESTSerializer.extend({
extractArray: function(store, type, payload) {
var rootFolders = payload.folders;
var childFolders = payload.related_folders;
var folders = [];
rootFolders.forEach(function(rootFolder) {
folders.push(rootFolder);
});
childFolders.forEach(function(childFolder) {
folders.push(childFolder);
});
payload = { folders: folders };
return this._super(store, type, payload);
}
});
At this moment I have my folders and related folders both in the store as folders but somehow I can not use the folder properties in my controller or components to use them in computed properties. I would like to determine which folders are root folders and which are child folders and then use the handlebars each helper in my template to show the right folders. Does anyone have an idea what I'm doing wrong or if there is a better way to fix this?

Hmm main problem was that I didn't extend my applicationSerializer in the folderSerializer. Now it's working.

Related

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 model relationship not matching API

I have two models who look like this:
// models/database.js
export default DS.Model.extend({
title: DS.attr(),
/*...*/
documents: DS.hasMany('document')
});
// models/document.js
export default DS.model.extend({
name: DS.attr(),
/*...*/
database: DS.belongsTo('database')
});
Is also have some routes
Router.map(function() {
this.route('databases', function() {
this.route('database', { path: ":database_id" }, function() {
this.route('documents');
});
});
});
When I call /database/[ID]/documents if got nothing.
I think the problem is I'm working with an JSON HAL API and when I ask for databases on API I receive this kind of response.
{
"_embedded": {
"databases": [
{
"_links": {
"self": {
"href": "<API_URL>/databases/7ec39267b544424c/",
"reference": "7ec39267b544424c",
"title": "00000006"
}
},
"database": {
"name": "00000006",
"reference": "7ec39267b544424c",
"title": "****"
}
},
{
"_links": {
"self": {
"href": "<API_URL>/databases/aace19af004144a4/",
"reference": "aace19af004144a4",
"title": "00000007"
}
},
"database": {
"name": "00000007",
"reference": "aace19af004144a4",
"title": "******"
}
}
}]
}
As you can see there no documents attributes on JSON returned.
If I want a database's documents I've got to call [API_URL]/databases/[ID]/domains/[ID]/documents
There's still a way to link documents to a database by specifying that the documents are recoverable at this URL (/databases/[ID]/domains/[ID]/Documents)?
I found the solution.
I've override extractRelationships method in hal serializer like this:
extractRelationships(modelClass, resourceHash, included) {
resourceHash._links["documents"] = {
"href" : "/databases/" + resourceHash.id + "/domains/" + (resourceHash.name || resourceHash[modelClass.modelName].name) + "/documents/"
};
return this._super(modelClass, resourceHash, included);
}
I just add a link to documents for each databases returned by the API.
I've modified my route for documents like this
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.modelFor('databases.database').get('documents');
}
});
And now it works perfectly.
I think what you're looking for is a HAL API to JSON API converter. To avoid doing this yourself manually I'd recommend the http://github.com/201-created/ember-data-hal-9000 Ember plugin, which I've used myself to get Ember Data to play nice with Spring Boot.
If you wanted to see a simple example here's my client-side and server-side demo app.
Additionally, if you were curious what Ember, and JSON API don't use HAL you can read here JSON API - FAQ on HAL.

Ember Data serialize relationship without parent ID

I'm building an adapter to wrap the Keen.io API, so far I've been able to successfully load the projects resource, however the returned object looks like this:
{
partners_url: "/3.0/projects/<ID>/partners",
name: "Project Name",
url: "/3.0/projects/<ID>",
saved_queries: [ ],
events_url: "/3.0/projects/<ID>/events",
id: "<ID>",
organization_id: "<ORG ID>",
queries_url: "/3.0/projects/<ID>/queries",
api_key: "<API KEY>",
events: [
{
url: "/3.0/projects/<ID>/events/user_signup",
name: "user_signup"
},
{
url: "/3.0/projects/<ID>/events/user_converted",
name: "user_converted"
},
{
url: "/3.0/projects/<ID>/events/user_created_project",
name: "user_created_project"
}
]
}
Excluding a lot of cruft, Ember has no trouble mapping the name and id attributes using the RESTSerializer, but if I add an events relation to my Project model it blows up with:
Error while loading route: TypeError: Cannot set property 'store' of undefined
at Ember.Object.extend.modelFor (http://localhost:3000/assets/ember-data.js?body=1:9813:23)
at Ember.Object.extend.recordForId (http://localhost:3000/assets/ember-data.js?body=1:9266:21)
at deserializeRecordId (http://localhost:3000/assets/ember-data.js?body=1:10197:27)
at deserializeRecordIds (http://localhost:3000/assets/ember-data.js?body=1:10211:9)
at http://localhost:3000/assets/ember-data.js?body=1:10177:11
at http://localhost:3000/assets/ember-data.js?body=1:8518:20
at http://localhost:3000/assets/ember.js?body=1:3404:16
at Object.OrderedSet.forEach (http://localhost:3000/assets/ember.js?body=1:3247:10)
at Object.Map.forEach (http://localhost:3000/assets/ember.js?body=1:3402:10)
at Function.Model.reopenClass.eachRelationship (http://localhost:3000/assets/ember-data.js?body=1:8517:42)
From my investigation this seems to be because it can't find the inverse relation to map an Event back to a Project because there's no parent ID.
Is it possible to create a relation in Ember Data to support this? Or is it possible to modify the Serializer to append a projectId to each event before loading?
I'm using Ember 1.5.0-beta.4 and Ember Data 1.0.0-beta.7+canary.f482da04.
Assuming your Project model is setup the following way:
App.Project = DS.Model.extend({
events: DS.hasMany('event');
});
You need to make sure that the JSON from your API is in a certain shape that Ember-Data expects.
{
"project": {
"id": 1,
"events": ["1", "2"],
},
"events": [{
"id": "1",
"name": "foo"
}, {
"id": "2",
"name": "bar"
}]
}
You can, however, implement extractArrayin your model's serializer to transform the JSON from the server into something similar like the above example.
There's a working example and an explanation in the Ember docs.

HasMany relationships between sideloaded models

Here's my Ember-Data model:
Lrt.Option = DS.Model.extend({
option_relation_value: hasMany('option')
});
Here is an example of the JSON: (Shortened for the sake of this question)
{
"optionGroups": [],
"optionSubGroups": [
{
"id": "3",
"optionType": [
"80",
"81",
"82",
"83",
"84",
"248",
"278"
],
"title": "Option Group for 80"
}
],
"options": [
{
"id": "45",
"option_relation_value": [
"80"
]
},
{
"id": "80",
"option_relation_value": []
}
]
}
There are also "OptionGroup" and "OptionSubGroup" models which are sideloading options in.
The issue I'm having is that after adding in the 'hasMany' into the model, I can no longer do query the store for Options like this:
this.get('store').find('option')
It simply returns "0", however in the Ember Inspector, I get 400+ entries so I know the data loaded.
When using the chrome inspector and break on ALL Exceptions, it breaks on line 2246 of Ember-Data at the following line:
2246: Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]);
The error is:
"Cannot call method 'toString' of undefined"
"type" in this line is 'undefined'.
What am I doing wrong with this hasMany relationship?
I am using Ember-Data 1.0 Beta 2.
Technically, this is not side loading, it's really more like nested loading. That may have something to do with it.
For true side loading you'd want a structure like this as your outer SON response :
{
"option" : {
"id" : 3,
...
"options" : [45,80]
}
"options" : [
{ "id":45 , ... },
{ "id":80 , ... }
]
}
Here are the docs about JSON conventions : http://emberjs.com/guides/models/connecting-to-an-http-server/#toc_json-conventions The comments are an example of side loading.
I know that this works for separate model structures (post -> comment), but I'm not sure about self referential tree type structures.

Ember-Data, embedded relation records and server JSON response

Working on an App with Ember RC6 and Ember-Data v0.13-54 along a custom server PHP API am running into some problems with Models relations (mainly many-to-many) and side loading.
The models in questions are:
App.Member = DS.Model.extend({
apiToken: DS.attr('string'),
apiTokenExpire: DS.attr('string'),
favourites: DS.hasMany('App.Presentation')
});
App.Presentation = DS.Model.extend(
{
title: DS.attr('string'),
description: DS.attr('string'),
date: DS.attr('date'),
category: DS.belongsTo('App.Category'),
tags: DS.hasMany('App.Tag'),
employees: DS.hasMany('App.Member'),
presentation: DS.belongsTo('App.File'),
presenterNotes: DS.belongsTo('App.File'),
cover: DS.belongsTo('App.Image')
});
To get the RESTAdapater to send the relation with the Member model I have:
App.APIRESTAdapter.map('App.Member', {
favourites: {embedded: 'always'}
});
When loading /members/1 the server returns:
{
"member": {
"api_token": "9fa236ad58726584d7b61bcf94b4dda37cbd3a24",
"api_token_expire": "1383832335",
"id": 1,
"favourite_ids": [ 3 ],
"group_ids": [ 2 ]
},
"presentations": [
{
"title": "That's a new one!",
"category_id": 2,
"id": 3,
"tag_ids": [ 1 ],
"employee_ids": [ 1 ]
}
]
}
But if I log get('member.favourites').mapProperty('id') I get an empty array and none of the favourites are actually added to the Member model.
If I disable the embedding of favourites on the RESTAdapter, all works fine. I am just wondering if there is something that I am missing or if there is some issues with how the JSON response is formatted?
EDIT:
Done some digging and seems that the issue comes from the fact that the relation names (favourites, employees) is different from the model names (Member, Presentation) which are used when sideloading data. Weird, since rev. 12 https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md models should be Sideload by Type.
Doing some tests I've added a new many-to-many relation for those 2 models:
App.Member gets presentations: DS.hasMany('App.Presentation')
App.Presentation get members: DS.hasMany('App.Member')
The JSON returned by the server is exactly the same, and when logging get('member.presentations') I now get the list of presentations as it should.
At this point this looks like a bug to me, but maybe am missing something? I've tried mappings on the RESTAdapater for favourites and employees but that didn't help... Maybe there is some other Adapter config that could help?
This isn't a sideloading issue but more a misunderstanding on my side about embedded data and what the configuration meant.
Since the Adapter was configured with:
App.APIRESTAdapter.map('App.Member', {
favourites: {embedded: 'always'}
});
The expected JSON response from the server is:
{
"member": {
"api_token": "b84fd204b37d3fa3cee8a2b2cac8bd4fd02635a1",
"api_token_expire": "1384027367",
"id": 1,
"favourites": [
{
"title": "Some kind of title",
"category_id": 1,
"id": 2,
"tag_ids": [ 1 , 2 ],
"employee_ids": [ 1 ]
}
]
}
}
So "favourite_ids": [ X, X, X ] should have been "favourites": [ HASH, HASH, HASH ] when records are flagged as embedded.