I have a use case where all models relations in Ember Data are loaded async. I have a route which renders Grandparents in the example below based on whether the parent.child matches a particular model.
So far I've been able to manage to resolve the grandparent and parent model async loading but then my code becomes a massive jumble.
Are there any useful strategies for filtering out the grandparents without having to deal with promises at every level?
Example model definitions
App.Grandparent = DS.Model.extend({
...
parents: DS.hasMany('Parent', { async: true })
});
App.Parent = DS.Model.extend({
...
grandParent: DS.belongsTo('Grandparent', { async: true }),
child: DS.belongsTo('Child', { async: true })
});
App.Child = DS.Model.extend({
...
});
Code Sample
var client = this.modelFor('workspace.client');
var promise = new Ember.RSVP.Promise(function(resolve)
{
client.get('sessions').then(function(sessions)
{
Ember.RSVP.all(sessions.getEach('exercises')).then(function(exercises)
{
Ember.RSVP.all(exercises.getEach('exercise')).then(function()
{
console.log("RESOLVED");
resolve(sessions);
});
});
});
});
I think you can simple get away by chaining the promises.
var client = this.modelFor('workspace.client');
return client.get('sessions').then(function(sessions) {
return Ember.RSVP.all(sessions.getEach('exercises'));
}).then(function(exercises) {
return Ember.RSVP.all(exercises.getEach('exercise'));
}).then(function(allExercises) {
console.log("RESOLVED");
return allExercises;
});
note: not sure what you are trying to do when resolving with sessions, and not doing anything with the exercises
Related
I have a model with tasks, and i wantto get data filtered by status and show result in different lists.
so i have a construction with does't work as i want.
tasks: Ember.computed(function(){
var modelTasks = this.get('store').findAll('task');
return {
todo: modelTasks.filterBy('status', 'todo'),
inProgress: modelTasks.filterBy('status', 'inprogress'),
done: modelTasks.filterBy('status', 'done')
};
}),
I'm new, so please be tolerant.
Why do you need tasks computed property?.
findAll returns Promise so your code is not correct.
Async computed properties little tricky - read this ignite article for more info.
I would say, data fetching should happen at the route level, so corresponding route js file model hook you can write,
export default Ember.Route.extend({
model() {
return this.get('store').findAll('task').then((result) => {
return {
todo: result.filterBy('status', 'todo'),
inProgress: result.filterBy('status', 'inprogress'),
done: result.filterBy('status', 'done')
};
});
}
});
inside corresponding hbs file, you can access it like model.todo
I am trying to do the following when visiting reviews/show (/reviews/:id):
Load two models from the server: One review and one user.
I only have access to the review's id, so I need to load it first, to get the userId
And then, when that has finished loading and I now have the userId, query the user using the userId attribute from the review
Finally, return both of these in a hash so I can use them both in the template
So two synchronous database queries, and then return them both at once in the model hook of the route.
I don't mind if it's slow, it's fast enough for now and I need it to work now.
This is what I've tried and it doesn't work:
reviews/show.js
export default Ember.Route.extend({
model: function(params) {
var user;
var review = this.store.findRecord('review', params.id).then(
function(result) {
user = this.store.findRecord('user', result.get('userId'));
}
);
return Ember.RSVP.hash({
review: review,
user: user
});
}
});
You can do this:
export default Ember.Route.extend({
model: function(params) {
var reviewPromise = this.store.findRecord('review', params.id);
return Ember.RSVP.hash({
review: reviewPromise,
user: reviewPromise.then(review => {
return this.store.findRecord('user', review.get('userId'));
})
});
}
});
The reason why user is undefined is because it hasn't been assigned in your first promise until the review is resolved, but Ember.RSVP.hash has received user as undefined.
Edit 11/16/14: Version Information
DEBUG: Ember : 1.7.0 ember-1.7.0.js:14463
DEBUG: Ember Data : 1.0.0-beta.10+canary.30d6bf849b ember-1.7.0.js:14463
DEBUG: Handlebars : 1.1.2 ember-1.7.0.js:14463
DEBUG: jQuery : 1.10.2
I'm beating my head against a wall trying to do something that I think should be fairly straightforward with ember and ember-data, but I haven't had any luck so far.
Essentially, I want to use server data to populate a <select> dropdown menu. When the form is submitted, a model should be created based on the data the user chooses to select. The model is then saved with ember data and forwarded to the server with the following format:
{
"File": {
"fileName":"the_name.txt",
"filePath":"/the/path",
"typeId": 13,
"versionId": 2
}
}
The problem is, the typeId and versionId are left out when the model relationship is defined as async like so:
App.File = DS.Model.extend({
type: DS.belongsTo('type', {async: true}),
version: DS.belongsTo('version', {async: true}),
fileName: DS.attr('string'),
filePath: DS.attr('string')
});
The part that is confusing me, and probably where my mistakes lie, is the controller:
App.FilesNewController = Ember.ObjectController.extend({
needs: ['files'],
uploadError: false,
// These properties will be given by the binding in the view to the
//<select> inputs.
selectedType: null,
selectedVersion: null,
files: Ember.computed.alias('controllers.files'),
actions: {
createFile: function() {
this.createFileHelper();
}
},
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path'
});
var gotDependencies = function(values) {
//////////////////////////////////////
// This only works when async: false
file.set('type', values[0])
.set('version', values[1]);
//////////////////////////////////////
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this);
Ember.RSVP.all([
selectedType,
selectedVersion
]).then(gotDependencies);
}
});
When async is set to false, ember handles createRecord().save() POST requests correctly.
When async is true, ember handles GET requests perfectly with multiple requests, but does NOT add the belongsTo relationships to the file JSON during createRecord().save(). Only the basic properties are serialized:
{"File":{"fileName":"the_name.txt","filePath":"/the/path"}}
I realize this question has been asked before but I have not found a satisfactory answer thus far and I have not found anything that suits my needs. So, how do I get the belongsTo relationship to serialize properly?
Just to be sure that everything is here, I will add the custom serialization I have so far:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
var root = Ember.String.capitalize(type.typeKey);
data[root] = this.serialize(record, options);
},
keyForRelationship: function(key, type){
if (type === 'belongsTo') {
key += "Id";
}
if (type === 'hasMany') {
key += "Ids";
}
return key;
}
});
App.FileSerializer = App.ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
type: { serialize: 'id' },
version: { serialize: 'id' }
}
});
And a select:
{{ view Ember.Select
contentBinding="controller.files.versions"
optionValuePath="content"
optionLabelPath="content.versionStr"
valueBinding="controller.selectedVersion"
id="selectVersion"
classNames="form-control"
prompt="-- Select Version --"}}
If necessary I will append the other routes and controllers (FilesRoute, FilesController, VersionsRoute, TypesRoute)
EDIT 11/16/14
I have a working solution (hack?) that I found based on information in two relevant threads:
1) How should async belongsTo relationships be serialized?
2) Does async belongsTo support related model assignment?
Essentially, all I had to do was move the Ember.RSVP.all() to after a get() on the properties:
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
file.set('type', values[0])
.set('version', values[1]);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this));
}
So I needed to get() the properties that were belongsTo relationships before I save the model. I don't know is whether this is a bug or not. Maybe someone with more knowledge about emberjs can help shed some light on that.
See the question for more details, but the generic answer that I worked for me when saving a model with a belongsTo relationship (and you specifically need that relationship to be serialized) is to call .get() on the properties and then save() them in then().
It boils down to this:
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
// belongsTo set() here
file.set('type', selectedType)
.set('version', selectedVersion);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
// Save inside then() after I call get() on promises
file.save().then(onSuccess, onFail);
}.bind(this));
I have a jquery ajax call defined like this
var fetchMessages = function(){$.getJSON(<some url>).then(function(data){ return data; }};
var messages = fecthMessages();
My routes are setup like this
App.Router.map(function() {
this.resource('messages', function() {
this.resource('message', { path: ':message_id' });
});
});
I use the promise messages in my routes like this
App.MessagesRoute = Ember.Route.extend({
model : function(){
return messages;
}
});
The above route works fine.
Next I have a nested route like shown below. This however errors out when I directly try to visit #/messages/<id of the message>. Loading #/messages followed by visiting #/messages/<id of message> works fine.
App.MessageRoute = Ember.Route.extend({
model: function(params) {
message = messages.findBy("id", params.message_id);
return message;
}
});
So how do I handle the promises in nested routes?
So how do I handle the promises in nested routes?
Apparently Ember handles these for you.
This however errors out when I directly try to visit #/messages/:
App.MessageRoute = Ember.Route.extend({
model: function(params) {
message = messages.findBy("id", params.message_id);
return message;
}
});
messages is still a promise, not an array; it doesn't have a findBy method. Instead, use
return messsages.then(function(m) {
return m.findBy("id", params.message_id);
});
I'm extending $resource with some methods, which using query, just for example:
var Resource = $resource(url, {}, {
query: {
cache: true
}
})
Resource.getByCategory = function (categoryId) {
return Resource.query({ categoryId: categoryId })
}
Resource.getBySearch = function (text) {
return Resource.query({ search: text })
}
I want to cache only the data which comes from getByCategory. as you can see, both methods use query, so I cannot define in the query configuration level - as I'm doing now and the data is cached for the both methods.
Any ideas? I thought about somehow using decorator for cached methods, not sure if it is going to help.