Ember Data model.save misses fields in request body when mapping changed in keyForAttribute - ember.js

I run into a problem when use Ember-data to save a model. The JSON structure for my model looks like:
{ post: {
id: 1,
name: 'post-1',
trigger: ['trigger-1', 'trigger-2'],
data: ['data-1', 'data-2']
}
}
Because 'data' and 'trigger' are reserved keywords for DS.Model, I created a mapping and renamed them to sc_data and sc_trigger as suggestion by Jurre using
Application.SERIALIZATION_KEY_MAPPINGS = {
'sc_data': 'data',
'sc_trigger': 'trigger'
};
Application.ApplicationSerializer = DS.ActiveModelSerializer.extend({
keyForAttribute: function (attr) {
if (Application.SERIALIZATION_KEY_MAPPINGS.hasOwnProperty(attr)) {
return Application.SERIALIZATION_KEY_MAPPINGS[attr];
} else {
return this._super(attr);
}
}
});
So my model for post looks like:
Application.Post = DS.Model.extend({
name: DS.attr('string'),
sc_trigger: DS.attr(),
sc_data: DS.attr()
});
the sc_trigger and sc_data are renmaed mapping for data and trigger.
It all worked fine when use this.store.find('post') and this.store.find('post', 1), i.e. GET calls. When I try to create a record using this.store.createRecord('post'), it creates a record with the correct attribute name sc_data and sc_trigger.
var newPost = this.store.create('post', {
name: 'test post',
sc_data: [],
sc_trigger: []
})
And the serialize function interprets the mapping correctly as well. newPost.serialize() returns
{
name: 'test post',
data: [],
trigger: []
}
But when I call newPost.save(), in the HTTP request body of the POST call, data and trigger field is missing. It only has
{
name: 'test post'
}
I have no idea why newPost.save() doesn't generate the correct request body when serialize() is working just fine.
Update
I managed to get around this by removing the keyForAttribute mapping and using
Application.ApplicationSerializer = DS.ActiveModelSerializer.extend({
attrs: {
sc_data: {key: 'data'},
sc_trigger: {key: 'trigger'}
}
});
This seems to be the suggested way to handle data with reserved keywords.

Which ember data version and emberjs version are you using?
Try saving with id like-
var newPost = this.store.create('post', {
id:1
name: 'test post',
sc_data: [],
sc_trigger: []
});
Save and create always expects id. So it's better to save/create record with id.

Related

how to serialize nested object without creating a serializer for the model in ember

I know that if I want to serialize nested comments of a model called post, I need to create a serializer in app/serializer/post.js
something like :
import RESTSerializer from 'ember-data/serializers/rest';
import DS from 'ember-data';
export default RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
comments: {embedded: 'always'}
}
});
but what if I want to serialize this inside the app/serlizer/application.js ?
I dont want to define a serializer for each model. Instead I want to be able to resolve the belong-to or has-many relationship inside the normalizeQueryResponse for example.
normalizeQueryResponse(store, primaryModelClass, payload, id, requestType) {
console.log(payload);
return this._normalizeResponse(store, primaryModelClass, payload, id, requestType, true);
},
I want to be able to go thorough the payload and if a property in payload turned out to be object then resolve that.
does anyone know if that is possible?
Sure this is possible and its how serializers and models are meant to work. But I recommend relying on one model and not specifying the type in your case. Lets take this example.
Your model post.js
export default DS.Model.extend((
valueA: DS.attr('string'), // converted to string
valueB: DS.attr('boolean'), // converted to boolean
comments: DS.attr() // not converted and set as it came in the payload
));
Your serializer post.js
export default RESTSerializer.extend({
// assuming your payload comes in the format of
// { data: [ {id: 0, valueA: '', valueB: true, comments: [...]}, {id:1 ...} ]
normalizeQueryResponse(store, primaryModelClass, payload, id, requestType) {
payload.data = payload.data.map((item, index) => ({ // item = {event_type: '', values: ['','']}
id: index, // ONLY if your payload doesnt already have an id
type: 'post', // set the model type
attributes: { // attributes are the values declared in the model
valyeA: item.valueA,
valueB: item.valueB,
comments: item.comments.map( item => {
// remap your comments to whatever format you need
})
}
}));
return payload;
});
Usage in your applicatioin
this.get('store').query('post', {...query object...} ).then(
response => {
let firstItemLoadedComments = response.get('firstObject.values');
// blast comments!
},
error => {
Ember.Logger.error(`cannot load model: ${error}`);
})
.finally(() => {
// set loading to false or something else
});

How Do I Construct my Ember Models and API Data to represent data on the relationship?

I'm new to jsonapi and how the structure works and trying to get a relationship to load properly. I'm expecting ember-data to follow provided url's in the relationship.links of my objects to fetch the required information but I'm getting unexpected results.
I have Users, Territories, and a User/Territory relationship defined like this:
// User Model
const UserModel = DS.Model.extend({
username: DS.attr('string'),
territories: DS.hasMany('user-territories')
}
// Territory Model
const TerritoryModel = DS.Model.extend({
name: DS.attr('string')
}
// User-Territory Model
const UserTerritoryModel = DS.Model.extend({
notes: DS.attr('string'),
startDate: DS.attr('date'),
user: DS.belongsTo('user'),
territory: DS.belongsTo('territory')
}
I then have mock data (using http-mock) that looks like this:
// api/users/
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
},
relationships: {
territories: {
links: {
self: "http://localhost:4200/api/users/1/territories"
}
}
}
// api/users/1/territories
data: {
type: "user-territories",
id: 1,
attributes: {
notes: "This is a note",
startDate: "2017-01-01"
}
},
relationships: {
user: {
link: {
self: "http://localhost:4200/api/users/1"
}
},
territory: {
link: {
self: "http://localhost:4200/api/territories/1"
}
}
}
// api/territories/1
data: {
type: "territories",
id: 1,
attributes: {
name: "Territory #1"
}
}
In my User route, I want to request the UserModel and have access to the UserTerritory Relationship data and the Territory itself. The api calls are not what I expect though:
this.get('store').findRecord('user', 1, { include: "territories" });
EXPECTED:
api/users/1
api/users/1/territories
ACTUAL:
api/users/1
api/users/1?include=territories
If I call the user-territories model I get this:
EXPECTED:
api/users/1/territories
ACTUAL:
api/user-territories/1
If you use included, ember-data basically thinks you want to tell the server to side-load data. If you return a links, just resolve the relationship. However the relationships have to be inside the data. Also the self link is for the relationship itself, to return the data use related.
So first you do something like user = store.findRecord('user', '1'), this will fetch to api/users/. Then you should return something like this:
// api/users/
{
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
relationships: {
territories: {
links: {
related: "http://localhost:4200/api/users/1/territories"
}
}
}
}
}
Next you do user.get('territories'). This will return a promise, and fetch http://localhost:4200/api/users/1/territories, or whatever was inside that related link. However know, what ember-data will expect you to return user-territories here, because thats what you specified with territories: DS.hasMany('user-territories'). You should know what you directly can model an many-to-many relationship in ember-data without a third table.

Ember Data EmbeddedRecordsMixin won't save newly created records

I'm using Ember Data 1.13.7 and Ember 1.13.6 with ActiveModelSerializer and EmbeddedRecordsMixin.
I have 2 models:
// models/post.js
export default DS.Model.extend({
//bunch of attrs
commentStuff: DS.belongsTo('commentStuff', {async:true})
}
and
//models/comment-stuff.js
export default DS.Model.extend({
//bunch of attrs
post: DS.belongsTo('post', {async: true))
}
in my serializer I have
export default DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
isNewSerializerAPI: true,
attrs: {
commentStuff: {serialize: 'records', deserialize: 'ids'}
},
keyForAttribute(attr){
if(attr === 'commentStuff'){
return 'comment_stuff_attributes';
} else {
return this._super(attr);
}
}
});
This works perfectly when I edit existing records and then post.save() but when I create new records with:
var post = this.store.createRecord('post');
post.commentStuff = this.store.createRecord('commentStuff');
And then fill in all their respective attributes. The json sent to the server on post.save() shows none of the commentStuff attributes and just returns null.
{post: {
attribute1: 'whatever',
attribute2: 'smth',
attribute3: 4,
comment_stuff_attributes: null}
}
Is there a different way I should be saving/creating new records?
You should use .set and .get methods. Not post.commentStuff =, but post.set('commentStuff',.

EmbeddedRecordsMixin not working as expected, what am I missing?

I'm trying to use embedded records in ember data and I think I'm missing something fundamental.
I have two models
app/models/video.js:
export default DS.Model.extend({
title: DS.attr('string'),
transcriptions: DS.hasMany('transcription', { embedded: 'always' })
});
app/models/transcription.js:
export default DS.Model.extend({
video: DS.belongsTo('video')
});
I also have a custom serializer app/serializers/video.js:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs:{
transcriptions: { embedded: 'always' }
},
extractSingle: function (store, type, payload, id) {
var data = payload.data;
return {
id: data._id,
title: data.Title,
transcriptions: [{ id: "1" }]
}
}
});
I would expect that this would result in my video model being populated with transcriptions being an array of transcription object but instead I get the following error:
"Error while processing route: videos.show" "Assertion Failed: Ember
Data expected a number or string to represent the record(s) in the
transcriptions relationship instead it found an object. If this is a
polymorphic relationship please specify a type key. If this is an
embedded relationship please include the DS.EmbeddedRecordsMixin and
specify the transcriptions property in your serializer's attrs
object."
Any suggestions of what I'm doing wrong here would be greatly appreciated.
UPDATE: The solution was to modify my custom serializer to the following:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs:{
transcriptions: { embedded: 'always' }
},
extractSingle: function (store, type, payload, id) {
var data = payload.data;
var videoPayload = {
id: data._id,
title: data.Title,
transcriptions: [{ id: "1" }]
};
return this._super(store, type, videoPayload, id);
}
}
The problem is the fact you're reimplementing extractSingle yourself.
You should call this.super if you're doing this..
In extractSingle on the REST Serializer it calls the normalize function - this normalise function is where the EmbeddedRecordsMixin does all it's work.
Because you're not calling either this.super or manually calling this.normalize you miss out on what the mixin is doing.

What should I use for my model?

I have a template which works with this model structure:
App.BuildingRoute = Ember.Route.extend({
model: function() {
return {
imgSrc: 'img/Images/44.png',
name: 'name',
addr1: 'Address Line 1',
addr2: 'Address Line 2'
}
}
}
But I need this data to be up-date-able and coming from our server. So I tried this:
App.BuildingData = DS.Model.extend({
imgSrc: 'img/Images/44.png',
name: 'name',
addr1: 'Address Line 1',
addr2: 'Address Line 2'
});
App.BuildingRoute = Ember.Route.extend({
model: function() {
var store = this.get('store');
var stuff = store.find('BuildingData');
return stuff;
}
}
Which I get a bunch of errors for. Is there a simple example somewhere of how to have a model which can be updated by a function somewhere else in the program?
I have a function getNodeInfo() which gets data from our server and returns something like this:
{
imgSrc: 'img/Images/44.png',
name: 'name',
addr1: 'Address Line 1',
addr2: 'Address Line 2'
}
Where am I supposed to call getNodeInfo() from, I had it inside the model function, but that got errors as well.
I dont even know if i need to use ember-data, maybe Ember.Object would work.
No need to use ember data (until it final) so you can use basic javascript variable or array to serve you as a model. But you should put that variable in global context so just try to do like this:
var imageModel = {
imgSrc: 'img/Images/44.png',
name: 'name',
addr1: 'Address Line 1',
addr2: 'Address Line 2'
};
App.BuildingRoute = Ember.Route.extend({
model: function() {
return imageModel;
}};
Remember to use Ember API to modify model's properties like this:
Ember.set(imageModel,'name','hereIsTheNewName');
So Ember will update everywhere on views.
If you want to fetch data from server then use jQuery.ajax call:
App.BuildingRoute = Ember.Route.extend({
model: function() {
return $.get('/your/app/url').then(function(data){
//suppose your server returns JSON
imageModel = data;
return imageModel;
});
}};
So what it shows? Ember can operate with javascript promise objects!
Didar's solution works, but if you want to make use of Ember's LoadingRoute and eliminating unnecessary global variable, here it is:
App.BuildingRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.Promise(function(resolve) {
$.getJSON('/app/url', function(data) {
resolve(Ember.Object.create(data));
});
});
}
});
Want to change properties? Just get the model in your route or controller.
this.get('model').set('name', 'My New Name');