Relationship with 2 API's getting error - ember.js

I have 2 API's:
http://example.api.com/api.json (this file have aprox 5mb)
http://api.com/:itemId
The 1st one have all data that i need, like this:
{
"realms": [
{"name":"Azralon","slug":"azralon"}],
"auctions": [
{"auc":828911977,"item":76139,"owner":"Bloodkina","bid":15294990,"buyout":16099990,"quantity":10,"timeLeft":"VERY_LONG"},
{"auc":828911979,"item":10000,"owner":"Bloodkina", "bid":15294990,"buyout":16099990,"quantity":100,"timeLeft":"VERY_LONG"},
{"auc":829305192,"item":98828,"owner":"Tempestivå","bid":15294990,"buyout":16099990,"quantity":5,"timeLeft":"VERY_LONG"},
{"auc":829305193,"item":98728,"owner":"Tempestivå", "bid":15294990,"buyout":16099990,"quantity":2,"timeLeft":"VERY_LONG"}
]}
The 2nd one have the name of the Items, but it only responds when i pass itemId as parameter. For example the item:76139, like http://api.com/76139
{
"id": 76139,
"description": "",
"name": "Wild Jade",
"icon": "inv_misc_gem_x4_rare_uncut_green",
}
I want to show the name of item and owner, but i getting an error like <DS.PromiseObject:ember71726> im my item field, the owner field is ok. How can i do this?? (it's the Blizzard API for auctions and items)
model/auction.js
import DS from 'ember-data';
export default DS.Model.extend({
auc: DS.attr('number'),
item: DS.belongsTo('item'), //items: DS.belongsTo('item'),
owner: DS.attr('string'),
});
model/item.js
import DS from 'ember-data';
export default DS.Model.extend({
auctions: DS.hasMany('auction'),
name: DS.attr('string')
});
routes/index.js
import Route from '#ember/routing/route';
export default Route.extend({
model(){
return this.store.findAll('auction');
}
});
routes/item.js
import Route from '#ember/routing/route';
export default Route.extend({
model(params){
return this.store.findRecord('item', params.item_id)
},
});
serializers/auction.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeResponse (store, primaryModelClass, payload, id, requestType) {
return {
realms: payload.realms,
data: payload.auctions.map(ah=>{
return {
id: ah.auc,
type:'auction',
attributes: ah,
//Added this
relationships: {
item:{
data: {
id: ah.item,
type: 'item',
}
}
}
}
})
};
}
});
serializers/item.js
import DS from 'ember-data';
export default DS.JSONSerializer.extend({
normalizeResponse (store, primaryModelClass, payload, id, requestType) {
payload = {
data : payload,
id: payload.id,
name: payload.name
};
return this._super(store, primaryModelClass, payload, id, requestType)
}
});
templates/index.hbs
{{#each model as |auction|}}
<ul>
<li>{{auction.items.name}}</li>
<li>{{auction.quantity}}</li>
<li>{{auction.bid}}</li>
<li>{{auction.buyout}}</li>
<li>{{auction.timeLeft}}</li>
<li>{{auction.owner}}</li>
</ul>
{{/each}}

Hey Cas 👋 I'm going to try and answer this one as best I can while trying to describe some of the issues that you were probably facing along the way. I have it working locally so hopefully I am going to be able to communicate how you can get it working on your side.
Firstly, as I mentioned in my comment you seemed to have a missmatch between your API, your model and your template when it came to how you were referencing items. You need to make sure that each key is correct so they all match up. Here is my backend responder, my model and my template:
Backend responder using express-autoroute:
module.exports.autoroute = {
get: {
'/auctions': function getThings(req, res) {
res.json({
realms: [{
name: 'Azralon',
slug: 'azralon',
}],
auctions: [{
auc: 828911977,
item: 76139,
owner: 'Bloodkina',
bid: 15294990,
buyout: 16099990,
quantity: 10,
timeLeft: 'VERY_LONG',
},
...
});
},
},
};
Auction model (Ember)
import DS from 'ember-data';
export default DS.Model.extend({
auc: DS.attr('number'),
item: DS.belongsTo('item'),
owner: DS.attr('string'),
});
Application template (Ember)
{{#each model as |auction|}}
<ul>
<li>{{auction.item.name}}</li>
<li>{{auction.quantity}}</li>
<li>{{auction.bid}}</li>
<li>{{auction.buyout}}</li>
<li>{{auction.timeLeft}}</li>
<li>{{auction.owner}}</li>
</ul>
{{/each}}
As you can see the backend is responding with item as an attribute to an auction, the model is using item as it's attribute name and the template is also accessing the key item. This is what I meant when I said they needed to match 🙂
The second thing that I noticed is that your auction serialiser isn't saying anything about relationships. If you check out the JSON:API spec you will see how relationships are supposed to be defined, i.e. with a relationships object
I was able to get your thing working using the following code:
Auction Serializer (Ember)
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
normalizeResponse (store, primaryModelClass, payload, id, requestType) {
return {
realms: payload.realms,
data: payload.auctions.map(ah => {
return {
id: ah.auc,
type:'auction',
attributes: ah,
relationships: {
item: {
data: {
id: ah.item,
type: 'item',
}
}
}
}
})
};
}
});
as you can see I'm building the relationships object and making sure that the item key matches.
The last issue I found was that your item serialiser wasn't working, I'm assuming that this is just that you didn't get this far because you successfully implemented the Auction Serializer. Here is my implementation:
Item Serializer (Ember):
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
normalizeResponse (store, primaryModelClass, payload, id, requestType) {
return {
data : {
attributes: payload,
id: payload.id,
type: 'item',
},
};
}
});
As I said this is now working for me locally but let me know if you have any more issues 🙂

Related

EmberJS hasMany doesn't fetch relationships

I have a problem with serializing my response which looks like:
{
"data": [
{
"id": 930,
"uniqueId": "0d3a04cb-231c-4998-b4d3-9436a0a3138e",
"name": "DRINKI",
"lastEditDate": "2018-02-12T13:30:32",
"lastEditDateUTC": "2018-02-12T12:30:32",
"deleted": false,
"discountable": true,
"productCategoryPointOfSales": []
},
{
"id": 921,
"uniqueId": "5fbf423a-4932-47ca-b32f-5d3612dd73ee",
"name": "BALOTYNKI SOLO",
"lastEditDate": "2019-02-07T14:20:15",
"lastEditDateUTC": "2019-02-07T13:20:15",
"deleted": false,
"label": "",
"color": "#a0a5a9",
"discountable": true,
"productCategoryPointOfSales": [
{
"id": 142,
"pointOfSaleUniqueId": "98e370f2-9d37-4473-9446-d82e442593fe",
"directionId": 54,
"directionUniqueId": "f0c986c0-ef85-4a46-86ea-cd997981fe8a",
"kitchenUniqueId": "f0c986c0-ef85-4a46-86ea-cd997981fe8a",
"inactive": false
}
]
}
],
"total": 0
}
And the error I get:
Encountered a relationship identifier without a type for the hasMany relationship 'productCategoryPointOfSales' on <category:5fbf423a-4932-47ca-b32f-5d3612dd73ee>, expected a json-api identifier with type 'product-category-point-of-sale' but found '{"id":"142","pointOfSaleUniqueId":"98e370f2-9d37-4473-9446-d82e442593fe","directionId":54,"directionUniqueId":"f0c986c0-ef85-4a46-86ea-cd997981fe8a","kitchenUniqueId":"f0c986c0-ef85-4a46-86ea-cd997981fe8a","inactive":false}'. Please check your serializer and make sure it is serializing the relationship payload into a JSON API format.
Models:
export default DS.Model.extend({
productCategoryPointOfSales: DS.hasMany('product-category-point-of-sale'),
uniqueId: DS.attr('string'),
name: DS.attr('string'),
label: DS.attr('string'),
color: DS.attr('string'),
discountable: DS.attr('boolean')
});
export default DS.Model.extend({
category: DS.belongsTo('category'),
pointOfSaleUniqueId: DS.attr('string'),
directionId: DS.attr('string'),
directionUniqueId: DS.attr('string'),
kitchenUniqueId: DS.attr('string'),
inactive: DS.attr('boolean')
});
And my serializer:
export default DS.RESTSerializer.extend(EmbeddedRecordMixin, {
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
payload = {
category: payload.data,
};
return this._super(store, primaryModelClass, payload, id, requestType);
},
primaryKey: 'uniqueId',
attrs: {
productCategoryPointOfSales: {embedded: 'always'}
}
});
I'm very new to EmberJS and have no idea how to solve this problem. I followed some tutorials and tried with EmbeddedRecordMixin but it didn't help me. Could you please help me figuring this out?
Your api payload doesn't match, to what ember-data's default JSONAPISerializer expects (no type attribute => missing type error).
You posted a custom serializer based on RESTSerializer, but it seems not to be in the right place, so ember-data still uses the default JSONAPISerializer. Also you might be better of with a JSONSerializer.
As your payload has different attributes for record id (uniqueIdand pointOfSaleUniqueId) you have to create a custom serializers per model, to set different primaryKey and embedded records specifics.
I've created an ember-twiddle example, it has three serializers.
An application serializer, as default:
// /app/serializers/application.js
import JSONSerializer from 'ember-data/serializers/json';
export default JSONSerializer.extend({
// use uniqueId as ember-data model id
primaryKey: 'uniqueId',
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
// extract data from payload, so JSONSerializer finds the records
let normalizedPayload = payload.data;
// call the JSONSerializer.normalizeResponse with the extracted payload
return this._super(store, primaryModelClass, normalizedPayload, id, requestType);
}
});
For category model, to support the embedded productCategoryPointOfSales, extend the application serializer and add EmbeddedRecordsMixin:
// /app/serializers/category.js
import ApplicationSerializer from './application';
import EmbeddedRecordsMixin from 'ember-data/serializers/embedded-records-mixin';
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
productCategoryPointOfSales: { embedded: 'always' }
}
});
For productCategoryPointOfSale, to use a different primaryKey:
// /app/serializers/product-category-point-of-sale.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
primaryKey: 'pointOfSaleUniqueId'
});

Ember Data with hasMany relation on embedded data

I'm using Ember 2.4.1 and trying to build the hierarchical tree with self reference model.
Here is the my Ember model
// app/models/category.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
parent: DS.belongsTo('category', { inverse: 'children' }),
children: DS.hasMany('category', { embedded: 'always', async: false, inverse: 'parent' })
});
And server response in route /categories with embedded data (I use RESTAdapter on Ember side):
{"categories":[
{
"id":4,
"name":"Root element w/o children",
"type":"Category",
"children":[]
},
{
"id":5,
"name":"Root element with a child",
"type":"Category",
"children":[
{
"id":6,
"name":"A child",
"type":"Category",
"children":[]
}
]
}
]}
As you can see Category with id 5 has a child with id 6 and it embedded. But while Ember loading the page it makes an error: Assertion Failed: You looked up the 'children' relationship on a 'category' with id 5 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.hasMany({ async: true }))
If I include the category with id 6 in JSON-root like {categories: [{id:4, …}, {id:5, …}, {id:6, …}]} error will disappear but I don't know why I need embedding in this case. May be I don't understand how embedding works in Ember.
So I'd like to ask advice how to make properly work Ember Data with embedded response without duplicating it and without async: true.
UPD #TheCompiler answered on my question in comments. Adding serializer with mixin solve my problem:
// app/serializers/category.js
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
children: { embedded: 'always' }
}
});
No need to duplicate the record. The Rest Adapter just doesn't play nice with nested JSON objects. Try formatting the data like this:
{"categories":[
{
"id":4,
"name":"Root element w/o children",
"type":"Category",
"children":[]
},
{
"id":5,
"name":"Root element with a child",
"type":"Category",
"children":[6]
},
{
"id":6,
"name":"A child",
"type":"Category",
"children":[]
}
]}

How to extract a "ComputedProperty" to it's real value?

I'm trying to implement amCharts into a project, and it looks like when I pass it the EmberData model as it's dataProvider, it can't understand the promises.
I've tried to fix this by creating a computed property in my controller that looks like:
Route:
--route.js
import Ember from 'ember';
const {Route, RSVP} = Ember;
export default Route.extend({
queryParams: {
start: {refreshModel: true},
stop: {refreshModel: true}
},
model(params) {
let filter = {
filter: {
start: params.start,
stop: params.stop
}
};
return RSVP.hash({
users: this.store.query('userActivity', filter, {async: false}),
});
},
setupController(controller, model) {
controller.set('model', model);
controller.set('users', model.users);
},
});
Controller (there are other parts I've stripped out, but they aren't relevant, suffice to say they just change the query params which triggers a model refresh from route):
--controller.js
import Ember from 'ember';
const {Controller, computed, get, set} = Ember;
export default Controller.extend({
queryParams: [ 'start', 'stop' ],
dataProvider: computed('users', function () {
let users = get(this, 'users')
.map(function (user) {
return {
calls_out: user.calls_out,
calls_in: user.calls_in,
}
});
console.log(users);
return users;
}),
});
However, when I log this I get the following:
0: Object +
calls_in: ComputedProperty
calls_out: ComputedProperty
__proto__: Object
1: Object
2: Object
which means the object that amCharts needs to work with is still not the raw data. Is there a way to extract the data out into numbers rather than a Promise or a ComputedProperty?
Thanks!
Edit - adding userActivity model as requested:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
// Attributes
title: DS.attr('string'),
first_name: DS.attr('string'),
last_name: DS.attr('string'),
calls_in: DS.attr('number'),
calls_out: DS.attr('number'),
// Computed Attributes
full_name: Ember.computed('first_name', 'last_name', function () {
return `${this.get('first_name')} ${this.get('last_name')}`;
}),
// Relationships
user: DS.belongsTo('user')
});
Every attribute on a DS.Model is defined with DS.attr() and will be a Computed Property. This is required so that ember-data ca track changes and rollback or update attributes.
For your use case best is to use getProperties:
const {computed,get,getProperties} = Ember;
...
dataProvider: computed('users.#each.calls_out', 'users.#each.calls_in', function () {
return get(this, 'users').map(u => getProperties(u, 'calls_out', 'calls_in'));
})
Then get(this, 'dataProvider') will give you a raw Javascript Array with raw Javascript Objects with raw strings (or numbers, depending on your DS.attr).
What is in the "user" class? Are they Ember object? You may provide more code.
If "calls_in" and "calls_out" are computed properties of this user class, you should call them like this:
let users = get(this, 'users')
.map(function (user) {
return {
calls_out: user.get('calls_out'),
calls_in: user.get('calls_in'),
}
});

Ember-Data 2.1.0 hasMany not working (cannot read property 'replace' of undefined)

I searched everywhere but can't seem to find an answer for this simple problem here on SO.
Problem:
I have a hasMany relationship in a model that is loaded by the route in a findAll(). Payload looks fin according to many answers I've seen here, but I get "TypeError: Cannot read property 'replace' of undefined". More details below.
How can I get this hasMany relationship to work? I'm using asynch:false and sending the sideload as recommended.
Using: Ember 2.1.0, Ember-Data 2.1.0.
Stacktrace:
TypeError: Cannot read property 'replace' of undefined
at Object.func (ember.debug.js:36026)
at Object.Cache.get (ember.debug.js:13165)
at decamelize (ember.debug.js:36068)
at Object.func (ember.debug.js:35974)
at Object.Cache.get (ember.debug.js:13165)
at Object.dasherize (ember.debug.js:36072)
at ember$data$lib$system$normalize$model$name$$normalizeModelName (normalize-model-name.js:13)
at ember$data$lib$serializers$json$serializer$$default.extend.modelNameFromPayloadKey (json-api-serializer.js:267)
at ember$data$lib$serializers$json$serializer$$default.extend._extractType (json-api-serializer.js:258)
at ember$data$lib$serializers$json$serializer$$default.extend.normalize (json-api-serializer.js:290)
Route:
app/routes/search.js
export default Ember.Route.extend({
model(params) {
if(params.query){
return this.store.findAll('search-result');
}
return null;
},
actions:{
sendSearch: function(queryString){
this.store.unloadAll('search-result');
this.refresh();
}
}
});
Models:
app/models/search-result.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
url: DS.attr('string'),
tags: DS.hasMany('search-result-tag', {async:false})
});
app/models/search-result-tag.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
});
Adapter (for search-result)
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:8080',
urlForFindRecord(id, modelName, snapshot) {
let url = this._super(...arguments);
let query = Ember.get(snapshot, 'adapterOptions.query');
if (query) {
url += '?' + Ember.$.param(query); // assumes no query params are present already
}
return url;
},
urlForFindAll(modelName) {
var queryDict = {};
location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]})
let url = this._super(...arguments);
let query = queryDict['query'];
if (query) {
url += '?query=' + query; // assumes no query params are present already
}
return url;
}
});
Payload
{
"search-result-tags": [
{
"name": "this-is-tag-#-0",
"id": 0
}
],
"search-results": [
{
"description": "This is description for blabla2",
"id": 0,
"title": "Blabla 2",
"url": "http://blablabla2.com",
"tags": []
},
{
"description": "This is description for blabla",
"id": 1,
"title": "Blabla",
"url": "http://blabla.com",
"tags": [
0
]
}
]
}
You need to use the RESTSerializer in addition to the RESTAdapter. So app/serializers/application.js would be -
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
});
See the docs. You may need to override keyForAttribute, if you need to change cases / underscores of your keys.
Note that if you are using Rails API for the backend you want ActiveModelAdapter and ActiveModelSerializer, which are available as an addon.

ember data not saving foreign key, sent as null

My ember app is not sending my foreign key to the back-end.
I have a table called issues which is has a related table called categories
My model is:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
category_id: DS.belongsTo('category'),
description: DS.attr('string')
});
My route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.store.findAll('issue');
},
actions: {
create: function(){
var issue = this.store.createRecord('issue');
issue.name = this.get('controller').get('newName');
issue.description = this.get('controller').get('newDescription');
issue.category_id = parseInt(this.get('controller').get('newCategory'));
//debugger;
console.log(issue);
issue.save();
},
...
other actions
...
}
}
});
the console.log from above looks like the category_id is getting set correctly:
category_id: 3
description: "foobar"
name: "test"
However my JSON payload that gets sent to the backend looks like:
{"issue":{"name":"test","description":"foobar","category_id":null}}
I tried stepping through by adding a custom serialiser in app/serializers/application.js
export default DS.RESTSerializer.extend({
...
serialize: function(snapshot,options){
console.debug('options='+options);
debugger;
var json = this._super(snapshot, options);;
return json;
}
...
});
But I got lost in all the super calling super indirection.
The snapshot.record has category_id: 3, but the json coming back from the this._super() call has category_id: null
options has includeID:true
Any clues will be much appreciated ...
Ember : 2.0.2
Ember Data : 2.0.0
Your model definition is wrong, when dealing with relationships you define them just as you would define any other attribute, there is no need to use _id.
export default DS.Model.extend({
name: DS.attr('string'),
category: DS.belongsTo('category'),
description: DS.attr('string')
});
As for the creation you should always use setters/getters when dealing with ember objects:
create: function() {
var issue = this.store.createRecord('issue', {
name: this.get('controller').get('newName'),
description: this.get('controller').get('newDescription'),
category: this.get('controller').get('newCategory') // assuming new category is a DS.Model instance of category
});
issue.save();
}
If you wish to stick to the syntax you have you would use issue.set('name', this.get('controller').get('newName')), from the looks of your code it seems you are going about this in the wrong way.
You should have a this.route('new') nested under your issues route, that way you wouldn't have to use the controller to store information.
You would simply set the model of the new route to:
model: function() {
return this.store.createRecord('issue');
}
Your template would make use of the input helpers like so:
{{input value=model.name}} and your action would just get the currentModel and call .save().