I am trying to reload a model that has changed on the server. My code is as follows:
App.CustomersController = Ember.ArrayController.extend({
intervalId: undefined,
startRefreshing: function() {
var self = this;
if ( self.get( 'intervalId' ) ) {
return;
}
self.set( 'intervalId', setInterval( function() {
//self.get('model').update();
self.get('model').reload();
}, 30000 ) );
}
});
App.CustomersRoute = Ember.Route.extend({
model: function() {
return this.store.find('customer');
},
setupController: function( controller, model ){
this._super( controller, model );
controller.startRefreshing();
},
actions: {
reload: function() {
this.get('model' ).reload();
}
}
});
You can see that I have two mechanisms for reloading the data - one a timer, and also an action triggered by a button in the UI. The latter is exactly what is shown in the ember-data documentation here: http://emberjs.com/api/data/classes/DS.Model.html#method_reload
Neither works. I get undefined in both cases i.e. the model returned does not have a reload() method. update() sort of works, except it does not remove deleted records and it is not what is recommended in the documentation. What am I doing wrong here in trying to use reload?
My stack:
DEBUG: -------------------------------
DEBUG: Ember : 1.5.1+pre.07fafb84
DEBUG: Ember Data : 1.0.0-beta.7.f87cba88
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.11.0
DEBUG: -------------------------------
and I am using the following adapter in case that makes any difference:
App.Store = DS.Store.extend({
// Override the default adapter with the `DS.ActiveModelAdapter` which
// is built to work nicely with the ActiveModel::Serializers gem.
adapter: '-active-model'
});
reload exists on a record, not a collection.
You would need to iterate the collection and call reload on each record.
self.get('model').forEach(function(record){
record.reload();
});
But I'm guessing you don't want to waste the callbacks to the server. In this case I'd recommend returning a filter as your model, then make another call to the server for all records.
App.CustomersRoute = Ember.Route.extend({
model: function() {
this.store.find('customer');
return this.store.all('customer');
},
setupController: function( controller, model ){
this._super( controller, model );
controller.startRefreshing();
},
actions: {
reload: function() {
this.get('model' ).reload();
}
}
});
App.CustomersController = Ember.ArrayController.extend({
intervalId: undefined,
startRefreshing: function() {
var self = this;
if ( self.get( 'intervalId' ) ) {
return;
}
self.set( 'intervalId', setInterval( function() {
self.store.find('customer'); // get all customers again, updating the ones we have
}, 30000 ) );
}
});
Related
This question is related to EmberJS best practice. I have two routes:
/diners
/diners/register
Now that I want to reload the model of /diners after transitioning from /diners/register (as a result of a successful diner registration, using transitionToRoute).
The questions are:
I use this.refresh() in actions: willTranstion; Is it normal?
Should I do it this way?
What are other ways to refresh the model of
parent routes after transitioning from a child route?
How can I can
maintain the data consistency on client side when POSTing a new
record to back end?
Ember version info:
DEBUG: Ember : 1.13.11
DEBUG: Ember Data : 1.13.15
DEBUG: jQuery : 1.11.3
UPDATED with code
Diner Register Controller:
export default Ember.Controller.extend({
actions: {
save () {
var data = this.model;
var record = this.store.createRecord('diner', data);
record.save().then(onFulfill, onFail);
var self = this;
function onFulfill () {
Ember.$('.ui.modal').modal('destroy');
self.transitionToRoute('diners');
}
function onFail (e) {
console.error(e.message || 'Unknown error');
}
},
cancel () {
Ember.$('.ui.modal').modal('close');
this.transitionTo('diners');
}
}
});
Diner List Route:
export default Ember.Route.extend({
queryParams: {
page: {
refreshModel: true
}
},
setupController (controller, model) {
let pageSize = 10;
let page = this.paramsFor('diners').page;
let total = model.meta.total;
controller.set('model', model);
if (page > 1) {
controller.set('prevPage', page - 1);
}
else {
controller.set('prevPage', undefined);
}
if (total > pageSize * page) {
controller.set('nextPage', page + 1);
}
else {
controller.set('nextPage', undefined);
}
},
model (params) {
let page = params.page || 1;
return this.store.find('diner', {
page
});
},
actions: {
willTransition () {
this.refresh();
}
}
});
I am working on a survey application and we are using an existing API. Our models look like:
App.User = DS.Model.extend({
name: DS.attr('string'),
participations: DS.hasMany('participation', {async: true})
});
App.Participation = DS.Model.extend({
user: DS.belongsTo('user', {async: true}),
survey: DS.belongsTo('survey', {async: true}),
hasCompleted: DS.attr('boolean'),
hasAccepted: DS.attr('boolean')
});
App.Survey = DS.Model.extend({
participations: DS.hasMany('participation', {async: true}),
title: DS.attr('string'),
locked: DS.attr('boolean')
});
I would like to return a live record array from my model hook via store.filter however this filter needs to deal with both survey's and the async participant record for the current user. How can I handle the async relation resolution in my filter callback function?
model: function() {
return Ember.RSVP.hash({
user: this.store.find('user', 1),
surveys: this.store.filter('survey', {}, function(survey) {
return !survey.get('locked'); //How do I get the participation record for the current user for the current poll so I can also filter out the completed true
})
});
}
If using a live record array of survey's is not the best way to deal with this what is?
Edit:
I've updated the approach to try:
App.SurveysRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
user: this.store.find('user', 1),
all: this.store.find('survey'),
locked: this.store.filter('survey', function(survey) {
return survey.get('locked');
}),
completed: this.store.filter('participation', {user: 1}, function(participation) {
return participation.get('hasCompleted');
}),
outstanding: this.store.filter('participation', {user: 1}, function(participation) {
return !participation.get('hasCompleted') && !participation.get('poll.locked');
})
});
}
});
App.SurveysCompletedRoute = Ember.Route.extend({
model: function() {
return this.modelFor('surveys').completed.mapBy('survey');
}
});
http://jsbin.com/vowuvo/3/edit?html,js,output
However, does the usage of the async property participation.get('poll.locked') in my filter pose a potential problem?
Had originally written my response in ES6 and ember-cli format, while localising Ember references... please excuse if this is a touch basic as I reverted it to ES5 and used commonly understood code structure for Ember.
Try this:
// beforeModel() and model() are skipped if coming from a collection
// ie: from '/users' to '/users/1'
// setting this up is purely for direct linking to this route's path.
model: function(params) {
return this.store.findRecord('user', params.id).then(function(user) {
return user.get('participations');
});
},
// only fired once!
// soon to be obsolete...
setupController: function(controller, model) {
controller.set('model', model);
var store = this.store,
userId, availSurveys, completed, outstanding;
store = this.store;
userId = model.get('id');
// this is a promise!
// also, these filters can be applied elsewhere that Store is available!
availSurveys = store.filter(
// modelName to be filtered.
'surveys',
// this part is the query - sent as a request to server, not used as a filter
{ locked: false },
// this is the active filter that will be applied to all survey records in client,
// updating 'availSurveys' as the records change
function(survey) {
return !survey.get('locked');
});
completed = store.filter('participation',
{
user : userId,
hasCompleted : true
},
function(participation) {
return participation.get('hasCompleted');
});
outstanding = store.filter('participation',
{
user : userId,
hasCompleted : false,
survey : { locked: false }
},
function(participation) {
// this is also a promise!
return participation.get('survey').then(function(survery) {
return !participation.get('hasCompleted') && !survey.get('locked');
});
});
// alternatively, hashSettled waits until all promises in hash have resolved before continuing
Ember.RSVP.hash({
availSurveys : availSurveys,
completed : completed,
outstanding : outstanding
}).then(function(hash) {
controller.set('availSurveys', hash.availSurveys);
controller.set('completed', hash.completed);
controller.set('outstanding', hash.outstanding);
});
}
I'm using Firebase as the backend for my Ember app and successfully hooked up Firebase with EmberFire. However, when I try to save my models after creating a record, while they are posted to Firebase, they are not updated in the client's ember template.
My code for the action handler before I realized this was:
actions: {
publishPost: function() {
var newPost = this.store.createRecord('post', {
title: this.get('post.title'),
body: this.get('post.body'),
timestamp: new Date()
});
newPost.save();
}
}
And my code to try and solve this (but that doesn't work) by catching the promise returned by the save() and find() methods is:
actions: {
publishPost: function() {
var newPost = this.store.createRecord('post', {
title: this.get('post.title'),
body: this.get('post.body'),
timestamp: new Date()
});
this.store.find('post').then(function(posts) {
posts.addObject(newPost);
posts.save().then(function() {
newPost.save();
});
});
}
}
Am I just not handling the logic in the above code correctly to update the template or is there another Firebase/EmberFire compatible solution for this?
On https://github.com/broerse/ember-cli-blog-fire I did this:
import Ember from "ember";
export default Ember.ObjectController.extend({
actions: {
createPost: function() {
var newPost = this.get('store').createRecord('post');
newPost.set('date' , new Date());
newPost.set('author' , 'C.L.I. Ember');
this.get('target').transitionTo('post', newPost.save());
}
}
});
transitionTo can handle the newPost.save() promise.
Try it on: http://exmer.com/bloggrfire/posts
In Ember App Kit, there are a number of testing examples that ship with the initial repo. One of those is a basic Route Unit test. This test is trivial, if the data is hard-coded in the model hook, like this:
test("#model", function(){
deepEqual(route.model(), ['red', 'yellow', 'blue']);
});
How do you use the isolated container to test the model hook if it returns a promise from ember-data?
Here's the test:
import Activities from 'appkit/routes/activities';
var route;
module("Unit - ActivitiesRoute", {
setup: function(){
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
}
});
test("#model", function(){
deepEqual(route.model(), ['activity', 'activity2', 'activity3']);
});
And the actual Route's model hook:
export default Ember.Route.extend({
model: function() {
return this.get('store').find('activity');
}
});
UPDATE:
After implementing the different approaches below from kingpin2k. Here is a summary of the outcomes.
First approach: works great ... yet no promise.
Second approach: returns the promise object (appears to be resolved), but the array, and correct values are assigned to _detail property.
test("#model", function(){
deepEqual(route.model()['_detail'], ['activity', 'activity2', 'activity3']); //passes
});
I'd like for store creation to be taken care of within the module setup().
...
module("Unit - ActivitiesRoute", {
setup: function(){
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
var store = {
find: function(type){
return new Em.RSVP.Promise(function(resolve){
resolve(['activity', 'activity2', 'activity3']); // or made up model(s) here
});
}
};
route.set('store', store);
}
});
And the test:
test("#model", function(){
deepEqual(route.model(), ['activity', 'activity2', 'activity3']); // ???
});
Third approach:
...
module('Unit - ActivitiesRoute', {
setup: function() {
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
var store = {
find: function() {
var promise = new Ember.RSVP.Promise(function(resolve) {
Em.run.later(function() {
resolve(Activity.FIXTURES);
}, 10);
});
return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
promise: promise
});
}
};
route.set('store', store);
}
});
And in the test, calling route.model() returns an empty object {} :
test("#model", function(){
deepEqual(route.model(), Activity.FIXTURES); // returns {}
});
UPDATE #2
It was also necessary to add asyncTest() instead of test() and to also call start() to prevent the test runner from hanging.
asyncTest('#model', function(){
Em.run(function(){
route.model().then(function(result){
ok(result);
equal(result, Activity.FIXTURES);
start();
});
});
});
Simple approach, it's a unit test, so really you aren't testing the store, so setup a mock store and result.
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
return ['activity', 'activity2', 'activity3'];
}
}
route.set('store', store);
Even better you can also replicate the promise
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
return new Em.RSVP.Promise(function(resolve){
resolve(['activity', 'activity2', 'activity3']); // or made up model(s) here
});
}
}
route.set('store', store);
If you want to more closely replicate Ember Data you might use an ArrayProxy implementing the PromiseProxyMixin...
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
var promise = new Ember.RSVP.Promise(function(resolve){
Em.run.later(function(){
resolve(['activity', 'activity2', 'activity3']);
}, 10);
});
return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
promise: promise
});
}
}
route.set('store', store);
Update
Using your last approach you should implement it like this
test("#model", function(){
route.model().then(function(result){
deepEqual(result, Activity.FIXTURES); // returns {}
});
});
But, there is something tricky here, since it has an async response you'll want to wrap it in an Ember run loop
test("#model", function(){
Em.run(function(){
route.model().then(function(result){
deepEqual(result, Activity.FIXTURES); // returns {}
});
});
});
I'm using EmberData and wonder how I can I fetch a model from path like this:
products/:id/comments
Considering that you are using the default RESTAdapter, this is one possible way — although I'm not sure if it's the best one:
App = Ember.Application.create();
App.ProductCommentsRoute = Ember.Route.extend({
model: function() {
var productId = this.controllerFor('product').get('model').get('id');
return App.Comment.find({ product_id: productId });
}
});
App.Router.map(function() {
this.resource('products', function() {
this.resource('product', { path: ':product_id' }, function() {
this.route('comments');
})
});
});