I have Model "user" and I have a model that includes an array of objects:
{users: [
{
id: 1,
name: user1,
status: new
},
{
id: 2,
name: user2,
status: new
},
...
]}
But, I have to change the status of a single user. I try:
actions: {
checkAccept(id) {
this.store.findRecord('user', id).then((record) => {
record.set('status', 'accepted');
record.save();
});
}
With the same method to remove the recording works if used record.DestroyRecord. Why instead PUT method sent GET ???
UPDATED
I'm using:
actions: {
checkAccept(record) {
this.store.adapterFor('userNetwork').updateRecord(this.store, this.userNetwork.type, record);
}
}
but get this ERROR:
Uncaught Error: Assertion Failed: The `attr` method is not available on DS.Model, a DS.Snapshot was probably expected. Are you passing a DS.Model instead of a DS.Snapshot to your serializer
Related
I have set up has one relation between two models product and product-prices. The product has one product-price and product-price belongs to the product. But I am unable to use get method that is been added to product repository after successful implementation of has one relation.
Here is the code
#get('/products/{id}/product-price', {
responses: {
'200': {
description: 'Array of productPrice\'s belonging to Product',
content: {
'application/json': {
schema: { type: 'array', items: getModelSchemaRef(ProductPrice) },
},
},
},
},
})
async find(
#param.path.number('id') id: number,
#param.query.object('filter') filter?: Filter<ProductPrice>,
#param.query.object('where', getWhereSchemaFor(ProductPrice)) where?: Where<ProductPrice>,
): Promise<ProductPrice[]> {
return this.productRepository.productPrices(id).get(filter) // error
}
here is the error
Type 'ProductPrice' is missing the following properties from type 'ProductPrice[]': length, pop, push, concat, and 26 more
I think you should get back to TypeScript problem. You are querying beLongTo that mean your response should be Promise<ProductPrice> and not Promise< ̶P̶r̶o̶d̶u̶c̶t̶P̶r̶i̶c̶e̶[̶]̶>
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.
I have defined a model(app/models/job.js)
import DS from 'ember-data';
export default DS.Model.extend({
status: DS.attr(),
result: DS.attr()
});
And I am trying to load it from the index controller(app/controllers/index.js)
import Ember from 'ember';
export default Ember.Controller.extend({
productName: "",
customerName: "",
startDate: "",
endDate: "",
actions: {
search: function() {
let data = this.store.find("job", '9e5ce869-89b3-4bfc-a70f-034593c21eae');
return data;
}
}
});
The HTTP response I get is:
{
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}
}
How ever I get following error and warning:
WARNING: Encountered "status" in payload, but no model was found for model name "status" (resolved model name using next-gen-analytics#serializer:job:.modelNameFromPayloadKey("status"))
WARNING: Encountered "result" in payload, but no model was found for model name "result" (resolved model name using next-gen-analytics#serializer:job:.modelNameFromPayloadKey("result"))
TypeError: Cannot read property '_internalModel' of undefined
at finders.js:50
at Object.Backburner.run (ember.debug.js:224)
at ember$data$lib$system$store$$Service.extend._adapterRun (store.js:2043)
at finders.js:45
at tryCatch (ember.debug.js:56151)
at invokeCallback (ember.debug.js:56166)
at publish (ember.debug.js:56134)
at ember.debug.js:32577
at Queue.invoke (ember.debug.js:910)
at Object.Queue.flush (ember.debug.js:974)onerrorDefault # ember.debug.js:32616exports.default.trigger # ember.debug.js:56792Promise._onerror # ember.debug.js:57758publishRejection # ember.debug.js:56065(anonymous function) # ember.debug.js:32577Queue.invoke # ember.debug.js:910Queue.flush # ember.debug.js:974DeferredActionQueues.flush # ember.debug.js:770Backburner.end # ember.debug.js:160Backburner.run # ember.debug.js:228run # ember.debug.js:20238ember$data$lib$system$adapter$$default.extend.ajax.Ember.RSVP.Promise.hash.success # rest-adapter.js:831jQuery.Callbacks.fire # jquery.js:3148jQuery.Callbacks.self.fireWith # jquery.js:3260done # jquery.js:9314jQuery.ajaxTransport.send.callback # jquery.js:9718
Any suggestion will be appreciated
UPDATE
I was thinking this is kind of a bug, so I went ahead to log a bug on ember-data github repo, got the response from #wecc in an hour or so (NICE)
https://github.com/emberjs/data/issues/3683
So to fix this issue, I wrote my own serializer.
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizePayload: function(payload) {
return {
'job': {
id: '9e5ce869-89b3-4bfc-a70f-034593c21eae',
status: payload.status,
result: payload.result
}
};
}
});
And now, it starts working.
OPINION I can think of why they implemented the default RESTSerializer this way, but we probably should give more information to the users on the documentation of the ember-data, otherwise, newbie who is trying to use it will get lost.
1) Your server response should have a root element with the same name, as model's. So, for single object it shuld be:
{
"job": {
"id": "<backend must provide an id, integer preferred>"
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}
}
And for multiple objects:
{
"jobs": [{
"id": "<backend must provide an id, integer preferred>"
"status": "OK",
"result": {
"b": 2,
"a": 2,
"see": 1,
"c": 1
}, {/*next object*/}]
}
I tried to debug it, and I found that the code goes to rest-serializer.js and extractSingle function, and goes to the line "if (!store.modelFactoryFor(modelName)) {" and it returns "false".
extractSingle: function (store, primaryTypeClass, rawPayload, recordId) {
Ember.deprecate("`serializer.normalizePayload` has been deprecated. Please use `serializer.normalizeResponse` with the new Serializer API to modify the payload.", this.normalizePayload === JSONSerializer.prototype.normalizePayload, {
id: "ds.serializer.normalize-payload-deprecated",
until: "2.0.0"
});
var payload = this.normalizePayload(rawPayload);
var primaryRecord;
for (var prop in payload) {
var modelName = this.modelNameFromPayloadKey(prop);
if (!store.modelFactoryFor(modelName)) {
Ember.warn(this.warnMessageNoModelForKey(prop, modelName), false, {
id: "ds.serializer.model-for-key-missing"
});
continue;
}
var isPrimary = this.isPrimaryType(store, modelName, primaryTypeClass);
var value = payload[prop];
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.
Summary
I am using Ember-Model and I need help debugging this error message.. it is actually two separate (but similar) error messages:
TypeError: Cannot read property 'map' of undefined
-and-
Uncaught Error: Assertion Failed: TypeError: Cannot read property 'map' of undefined
Details
In my app, there is a route called "view". It pulls a collection of accounts from an API.
I have the model and custom adapter defined almost correctly and I get a successful response from the server with a JSON payload. I do not know what to do next as there is an error in the console (see above).
Somewhere in the Ember-Model source code there is a property called map inside of the materializeData function, and do not know what it is trying to do... (I see a comment in that function // FIXME which is making me nervous)
Here is my code that got me to the problem:
View Model:
import ViewAdapter from '../../adapters/accounts/view';
var attr = Ember.attr, hasMany = Ember.hasMany, belongsTo = Ember.belongsTo;
var View = Ember.Model.extend({
debtor_id: attr(),
debtor_legacy_account_number: attr(),
debtor_full_name: attr(),
debtor_balance: attr()
});
View.adapter = ViewAdapter.create();
View.url = 'api/rest/debtor/list';
View.rootKey = 'data';
View.collectionKey = 'debtors';
View.primaryKey = 'debtor_id';
export default View;
View Adapter:
var ViewAdapter = Ember.RESTAdapter.extend({
buildURL: function(klass, id) {
var urlRoot = Ember.get(klass, 'url');
if (!urlRoot) { throw new Error('ViewAdapter requires a `url` property to be specified'); }
if (!Ember.isEmpty(id)) {
return urlRoot + "/" + id;
} else {
return urlRoot;
}
},
ajaxSettings: function(url, method) {
return {
url: url,
type: method,
headers: {
"Accept": "application/json; version=1.0.0"
},
dataType: "json"
};
}
});
export default ViewAdapter;
View Route:
import View from '../../models/accounts/view';
export default Ember.Route.extend({
model: function() {
return View.find({page_size: 10, page_number: 1});
}
});
Note: my use of the model hook here to pass parameters to the server is temporary, I am searching a better way to do server-side pagination in a separate case... but this at least works for now...
What am I missing?
Full Stack Trace
TypeError: Cannot read property 'map' of undefined
at Ember.RecordArray.Ember.ArrayProxy.extend.materializeData (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:60646:24)
at Ember.RecordArray.Ember.ArrayProxy.extend.load (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:60630:31)
at Ember.RESTAdapter.Ember.Adapter.extend.didFindQuery (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:61925:15)
at http://local-03-02-xx.lariatcentral.net/assets/vendor.js:61916:12
at invokeCallback (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:23709:19)
at publish (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:23379:9)
at publishFulfillment (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:23799:7)
at http://local-03-02-xx.lariatcentral.net/assets/vendor.js:29217:9
at DeferredActionQueues.invoke (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:21747:18)
at Object.DeferredActionQueues.flush (http://local-03-02-xx.lariatcentral.net/assets/vendor.js:21797:15) vendor.js:17062
logToConsole vendor.js:17062
RSVP.onerrorDefault vendor.js:59834
__exports__.default.trigger vendor.js:22673
Promise._onerror vendor.js:23397
publishRejection vendor.js:23804
(anonymous function) vendor.js:29217
DeferredActionQueues.invoke vendor.js:21747
DeferredActionQueues.flush vendor.js:21797
Backburner.end vendor.js:21260
Backburner.run vendor.js:21315
apply vendor.js:21145
run vendor.js:19777
settings.success vendor.js:62014
fire vendor.js:3214
self.fireWith vendor.js:3326
done vendor.js:9370
callback
JSON Payload Sample
{
"status":"success",
"data":{
"debtors":[
{
"debtor_id":1048,
// ...
// ...
},
{
"debtor_id":1049,
// ...
// ...
},
{
"debtor_id":1050,
// ...
// ...
},
// ...more JSON...
],
"count":10,
"total":475,
"current_page":1,
"total_pages":48,
"page_size":10
}
}
I also found this github issue that looks exactly like my problem.
When you call View.find({page_size: 10, page_number: 1}); ember-model will send a request to the server, get the payload and call the didFindQuery hook. Since you're using the RESTAdapter the current implemantation is the following:
didFindQuery: function(klass, records, params, data) {
var collectionKey = Ember.get(klass, 'collectionKey'),
dataToLoad = collectionKey ? data[collectionKey] : data;
records.load(klass, dataToLoad);
},
The fourth param data is your payload. And because you setup the View.collectionKey to 'debtors' it will call records.load with data['debtors'] which returns undefined. That undefined value is passed to materializeData and you're receiving the TypeError: Cannot read property 'map' of undefined. What you need is data['data']['debtors'].
What you can do is:
Change your payload structure to:
{
"status":"success",
"debtors":[
{
"debtor_id":1048,
// ...
// ...
},
{
"debtor_id":1049,
// ...
// ...
},
{
"debtor_id":1050,
// ...
// ...
},
// ...more JSON...
],
"count":10,
"total":475,
"current_page":1,
"total_pages":48,
"page_size":10
}
}
Or override the didFindQuery and didFindAll to:
var ViewAdapter = Ember.RESTAdapter.extend({
// others methods ...
didFindQuery: function(klass, records, params, data) {
var collectionKey = Ember.get(klass, 'collectionKey'),
dataToLoad = collectionKey ? data['data'][collectionKey] : data;
records.load(klass, dataToLoad);
},
didFindAll: function(klass, records, data) {
var collectionKey = Ember.get(klass, 'collectionKey'),
dataToLoad = collectionKey ? data['data'][collectionKey] : data;
records.load(klass, dataToLoad);
}
})
I hope it helps