My form has body and subject inputs, and an input for tags, so the user can enter any number of tags (saved to tagList) and then submit the request. Problem: JSON.stringify(z) does something like this
New request:{"subject":"this is subject","body":"this is body","tags":["fixture-0","fixture-1"]}
instead of getting the tags to be the text I entered, I get fixture-0...
import Ember from "ember";
export default Ember.ArrayController.extend({
tagList: [],
actions: {
addRequest: function() {
var z = this.store.createRecord("Request", {body: this.get("body"), subject: this.get("subject")
});
this.get("tagList").forEach(function(entry){
console.log("adding tag to request: "+entry.get("tagt"));
z.get("tags").pushObject(entry);
});
console.log("New request:" + JSON.stringify(z));
z.save();
},
addTag: function(){
console.log("adding " + this.get("tag"))
var t = this.store.createRecord("tag", {tagt: this.get("tag")});
this.get("tagList").pushObject(t)
}
}
});
First of all, I don't think you can rely on JSON.stringify to convert your records to JSON properly, that's generally the job of a serializer. (Although I guess the toJSON method on the object could defer to the serializer, but I don't think it does.)
Second, this is expected behavior for Ember-Data. The names of the text aren't in the JSON because they don't need to be. You have a hasMany relationship, which means that the record only keeps references (IDs) to tag objects. Keeping the actual text in the object would be duplicating that information.
As a side note, judging by the fact that you're using Request as a model type name, I can say with a pretty good degree of certainty that you're using Ember-Data incorrectly. This may be part of the reason you aren't expecting Ember-Data to behave the way it is. I would suggest reading Ember's guide on models to better understand what Ember-Data is for, and why it's probably not a good fit for your use case.
Related
I have a little problem using findAll in my component. It concerns the value it returns at the end.
users: Ember.computed(function() {
return this.get('store').findAll('user');
}),
In my case, I want to get the name of the first object. So in my handlebar:
users.firstObject.name
'users' is a class in this case. But I'm trying to return directly the first object in the property, like this:
user: Ember.computed(function() {
return this.get('store').findAll('user')
.then(function(user){
return user.get('firstObject');
});
}),
But in this case, in my handlebar, user.name is undefined and user is a promise. There is something I can't understand with promises, how they work ...
Can somebody help me to get the correct user without using 'firstObject' on my users ?
Thanks in advance !
The shortest way to solve your problem is to install an ember-promise-helpers addon and apply it in your template as follows:
{{#if (await user)}}
{{get (await user) 'name'}}
{{/if}}
However, AFAIK, it is not recommended to use promises as values for computed properties, although you can still do it. here I would recommend the documentation for Ember.PromiseProxyMixin as well as reading some (although older) forum threads (for instance this one).
I think the problem you're having is not with the promises, but how you're using the computed property.
The best place for a findRecord/findAll is in a model hook in the route. It is best to not rely on firstObject for what you're describing. You have no guarantees about which record will be first as your app's back end data changes.
Computed properties are really meant for watching other data and updating the displayed information about that data, not for fetching things from the store on their own. They require arguments that are the names of the data they are supposed to be watching for updates. Since your computed property isn't watching anything, it never fires. Here's an example.
firstName: null,
lastName: null,
fullName: Ember.computed('firstName', 'lastName', function() {
let firstName = this.get('firstName');
let lastName = this.get('lastName');
return `${firstName} ${lastName}`;
})
You can read more under Computed Properties in the Guides.
You will have a much easier time if you define a model hook in a route that gets the user's info using findRecord and then passes it to child components or sets it on a service if many different components on different routes need the user information.
Ok, so I'm new to Ember so bear with me.
I have an Ember application that is communicating with an API that does not adhere to JSONAPI standards, thus I have begun writing my own serializers in order to use Ember Data. However I am finding that when I make multiple requests to the same resource, the data is having trouble writing to the store. Consecutive requests to the same resource always responds with the following error:
TypeError: Cannot convert object to primitive value
Which from my limited understanding, implies that the data I am sending to the store is being treated like a string.
In my Application route I have written a findAll to my model 'listing-item' like so:
model: function() {
return this.store.findAll('listing-item');
},
In a nested 'user' route, when I do any type of request for the listing-item data that returns an array response (query, findAll) for the listing-item data, I get:
TypeError: Cannot convert object to primitive value
at EmptyObject.SETTER_FUNCTION [as title] (ember.debug.js:20672)
at assign (<anonymous>)
at InternalModel.setupData (internal-model.js:244)
at Class._load (store.js:1728)
at Class._pushInternalModel (store.js:2055)
at Class._push (store.js:1995)
at finders.js:141
at Backburner.run (ember.debug.js:720)
at Class._adapterRun (store.js:2253)
at finders.js:139
(Title is a field in my listing item model).
As I mentioned earlier, my API does not adhere to JSONAPI standards, so I've written a listing-item serializer like so:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
normalizeArrayResponse(store, primaryModelClass, payload) {
payload.data = [];
payload.listing_item._data.forEach(this.formatListingItemArray, payload.data);
delete payload.listing_item;
return payload;
},
formatListingItemArray(listingItem) {
this.push({
type: "listing-item",
id: listingItem.id,
attributes: {
title: listingItem.title,
description: listingItem.description,
buy_now_price: listingItem.buy_now_price,
created_at: listingItem.created_at,
category_id: listingItem.category_id,
subcategory_id: listingItem.subcategory_id,
identity_id: listingItem.identity_id,
listing_number: listingItem.listing_number,
brand_new: listingItem.brand_new,
sold: listingItem.sold,
},
});
},
});
So I suppose my question is, what is Ember Data doing with my data object for this error to occur, and what might I be doing wrong in formatting my data for Ember data to consume.
UPDATES:
It appears as though only the top 3 fields are causing this error to occur. If I comment out the attributes 'title', 'description' and 'buy_now_price' in my serializer, I don't get this error. Also, it appears this only occurs when I navigate to the route, If I am in the /user route when the application loads, both requests work as expected.
Ok, so I've been crawling through ember-data code and found that in the internal-model.js file there is a setup function that looks at the current attributes in the store and compares them to the data being passed from the serializer. It then does an assign() to copy the serializers new data over to the stores object. However for some reason it seems that my stores object has a set of 'getter' and 'setter' function that come back from the store for the problematic fields (title, description and buy_now_price). What I need to know now is why are these functions coming along for the ride and what have I done to cause this?
Picture of getters/setters on ember-data object
Thanks in advance, let me know if there's any more information I need to provide in order to give better context.
So I found the solution to my problem for anyone that experiences a similar issue.
The symptom was as stated above, it seemed as though ember-data was returning getter and setter functions back from the store and having problems placing my new values in their stead. However the clue was that it was only the fields that I was rendering in my template.
The problem seems to be that I was using an Ember Concurrency task to perform the loading of my data, then passing the task straight in to an internal component from the model template. Something like this (not tested code):
The WRONG pattern (one I was using to experience the issue).
//route file - application.js
export default Ember.Route.extend({
model: function() {
return {
listings: this.get('getListings').perform(),
};
},
getListings: task(function * () {
return yield this.store.findAll('listing-item');
}),
})
//application template - application.hbs
{{listing-index listings=model.listings}}
//listing index component template.hbs
{{#if listings.value}}
{{#each listings.value.content as |listing|}}
{{listing._data.title}}
{{listing._data.description}}
{{listing._data.buy_now_price}}
{{/each}}
{{/if}}
The CORRECT pattern seems to be as thus:
//route file - application.js
export default Ember.Route.extend({
model: function() {
return {
listings: this.get('getListings').perform(),
};
},
getListings: task(function * () {
return yield this.store.findAll('listing-item');
}),
})
//application template - application.hbs
{{#if listings.value}}
{{listing-index listings=model.listings.value}}
{{/if}}
//listing index component template.hbs
{{#each listings as |listing|}}
{{listing.title}}
{{listing.description}}
{{listing.buy_now_price}}
{{/each}}
So the problem seems to lie in this pattern of passing the task rather than the content of the task in to my component. When I looped over the listings in the model template then passed the listings straight in to the component, it seemed to have solved my issues. I gather this is something to do with the use of these _data properties. A further explanation would be appreciated but i'll mark this as resolved for now.
So let's say you have a User model which is set up to a fairly standard API. On the front end you have an Ember project which also has that User model. The normal creation call would be something like the following:
store.createRecord('user', {email: 'test#gmail.com'}).save();
This would send off a POST request to something like /api/users. However, something fairly extensive API's support is the creation of multiple models at once. So for instance, instead of the POST call just sending a single object under user: {email: 'test#gmail.com'} it would send an array of objects like users: [{email: 'test#gmail.com'}, {email: 'test2#gmail.com'}, ...].
How I have seen this handled in ember is to just do multiple creation calls at runtime. However, this is terribly inefficient and I am wondering if Ember supports saving multiple models at the same time? How would you achieve this in Ember?
You cannot save an array of models in a single POST request Ember Data as you describe it, however there is a way.
You can save a parent model which hasMany 'user' with the EmbeddedRecordsMixin, which will include either relationship ids or full records. Your serializer would look like -
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
users: { embedded: 'always' },
}
});
Depending on your use case it may make sense to create a parent model only for this purpose which hasMany 'user'. If you want to use an existing model and don't always want to embed its user records there is an answer here.
If you do decide to save the models individually, you would want to do users.invoke('save'), which will trigger a POST for each model.
If you're asking specifically about Ember Data, I don't know of any way of doing that (I don't think it's possible to use any equivalent of save() on a collection/array). There could be alternative Data libraries that may work (for instance you could check Orbit.JS - which is something I haven't done yet)
The way I've done it it to have a custom endpoint on my backend that receives a certain JSON payload and creates the resources. You do it by issuing a regular ajax call, see this example (from a project of mine).
let content = //get content that you want to post
let accessToken = this.get('session.session.authenticated.token');
Ember.$.ajax({
data: JSON.stringify(content),
dataType: 'json',
method: 'POST',
url: 'path/to/my/custom/end/point',
headers: {
'Content-Type': 'application/json',
'Authorization': `Beader ${accessToken}`
}
}).then((result) => {
// Code for success
}, (jqXHR) => {
// Code for error
}).always(() => {
// Code for always/finally
});
As you can see this is all custom code, not leveraging Ember Data store or models. So far I haven't found a better answer.
EDIT: After seeing andorov's answer. I forgot to mention something. I'm using Ember Data 2.0 (JSONAPI by default) and EmbeddedRecordsMixin does not work property with JSON API
I have an endpoint /activities which behaves in a typical fashion for a RESTful API using JSON. I can of course do things like the following:
model: function() {
return this.store.find('activity');
}
to return a list of activities. Or I can:
model: function(params) {
return this.store.find('activity', params.id);
}
get a specific Activity record. This works fine. But the API also allows certain filtering to be done using URL parameters. So for instance,
GET /activities/type/exercise
Unlike a full list it would only bring back activities which are associated with "exercise". What is the best way for one to pull back data using ember-data? At a bare minimum I want the data to come back, be associated to the correct model, and be iterable via a `DS.RecordArray or similar object.
Ideally I would also like to avoid making network requests every time I hit this route although possibly this is something that I can't get "out of the box". Still interested if there are any best practices.
Ember can already query for data, by default using query params i.e.
store.find(activities, {type: 'exercise'}); // /activities?type=exercise
you could override findQuery on your activities adapter so that if conforms to your api:
// /adapters/activity.js
import DS form 'ember-data';
export default DS.RESTAdapter.extend({
findQuery: function(store, type, query) {
for(var key in query) break; // get first key in the example 'type'
var path = ['', key, query[key]].join('/');
return this.ajax(this.buildURL(type.typeKey) + path, 'GET');
},
});
I haven't tested this but it should work.
I'm using an ArrayController in my application that is fed from a Ember Data REST call via the application's Router:
postsController.connectOutlet('comment', App.Comment.find({post_id: post_id}));
For the Post UI, I have the ability to add/remove Comments. When I do this, I'd like to be able to update the contentArray of the postsController by deleting or adding the same element to give the user visual feedback, but Ember Data is no fun:
Uncaught Error: The result of a server query (on App.Comment) is immutable.
Per sly7_7's comment below, I just noticed that the result is indeed DS.RecordArray when there is no query (App.Comment.find()), but in the case where there is a query (App.Comment.find({post_id: post_id}), a DS.AdapterPopulatedRecordArray is returned.
Do I have to .observes('contentArray') and create a mutable copy? Or is there a better way of doing this?
Here is what I ended up implementing to solve this. As proposed in the question, the only solution I know about is to create a mutable copy of the content that I maintain through add and deletes:
contentChanged: function() {
var mutableComments = [];
this.get('content').forEach(function(comment) {
mutableComments.pushObject(comment);
});
this.set('currentComments', mutableComments);
}.observes('content', 'content.isLoaded'),
addComment: function(comment) {
var i;
var currentComments = this.get('currentComments');
for (i = 0; i < this.get('currentComments.length'); i++) {
if (currentComments[i].get('date') < comment.get('date')) {
this.get('currentComments').insertAt(i, comment);
return;
}
}
// fell through --> add it to the end.
this.get('currentComments').pushObject(comment);
},
removeComment: function(comment) {
this.get('currentComments').forEach(function(item, i, currentComments) {
if (item.get('id') == comment.get('id')) {
currentComments.removeAt(i, 1);
}
});
}
Then in the template, bind to the this computed property:
{{#each comment in currentComments}}
...
{{/each}}
I'm not satisfied with this solution - if there is a better way to do it, I'd love to hear about it.
A comment will be too long...
I don't know how do you try to add a record, but you can try to do this: App.Comment.createRecord({}). If all goes right, it will update automatically your controller content. (I think the result of App.Comment.find() works as a 'live' array, and when creating a record, it's automatically updated)
Here is how we do this in our app:
App.ProjectsRoute = Ember.Route.extend({
route: 'projects',
collection: Ember.Route.extend({
route: '/',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet({
name: 'projects',
context: App.Project.find()
});
}
})
and then, the handler of creating a project (in the router):
createProject: function (router) {
App.Project.createRecord({
name: 'new project name'.loc()
});
router.get('store').commit();
},
Just for the record: as of today (using Ember Data 1.0.0-beta), the library takes this situation into account. When a record in an array gets deleted, the array will be updated.
If you try to delete an element on that array manually, for example by using .removeObject(object_you_just_deleted) on the model of the containing controller (which is an ArrayController, hence its model an array of records), you'll get an error like:
"The result of a server query (on XXXXX - the model you try to update manually) is immutable".
So there is no need anymore to code by hand the deletion of the record from the array to which it belonged. Which is great news because I felt like using ED and working it around all the time... :)
Foreword
I had a similar problem and found a little tricky solution. Running through the Ember-Data source code and API docs cleared for me the fact that AdapterPopulatedRecordArray returns from the queried find requests. Thats what manual says:
AdapterPopulatedRecordArray represents an ordered list of records whose order and membership is determined by the adapter. For example, a query sent to the adapter may trigger a search on the server, whose results would be loaded into an instance of the AdapterPopulatedRecordArray.
So the good reason for immutability is that this data is controlled by the server. But what if I dont need that? For example I have a Tasklist model with a number of Tasks and I find them in a TasklistController in a way like
this.get('store').find('task',{tasklist_id: this.get('model').get('id')})
And also I have a big-red-button "Add Task" which must create and save a new record but I dont want to make a new find request to server in order to redraw my template and show the new task. Good practice for me will be something like
var task = this.store.createRecord('task', {
id: Utils.generateGUID(),
name: 'Lorem ipsum'
});
this.get('tasks').pushObject(task);
In that case I got announced error. But hey, I want to drink-and-drive!
Solution
DS.AdapterPopulatedRecordArray.reopen({
replace: DS.RecordArray.replace
})
So that's it. A little "on my own" ember flexibility hack.