find() and find(id) triggering Ajax calls to the server - ember.js

In my application, I have the common parent/child relationship in my route map.
App.Router.map(function () {
this.resource('strats', {path: "/"}, function() {
this.route('strat', {path: "/strat/:strat_id"});
});
});
My understanding is that when Ember first enters the parent route, it calls find() to get all models, which generally triggers an Ajax call to the server. Then when Ember subsequently transitions to a child route, it first calls find(), followed by find(id). If I'm using a data layer with an identity-map implementation (such as Ember-Data or Ember-Model), these subsequent calls to find() and find(id) should result in data being fetched from local memory, and Ember should not have to initiate another Ajax call to the server as a result of calls to these functions as long as the application is running. If this understanding is correct, then I should not have to implement find(id) on the server side.
I'm using Ember-Model in my application. As I'm navigating between routes, I see on the server side requests for an individual model coming through once in awhile, which means calls to find(id) are sometimes triggering Ajax calls to the server, which was unexpected. Where's the flaw in my logic described above?

Firstly, Ember itself only does what your model hooks tell it to do.
And those hooks are only called if you are visiting the route anew. I'll give some examples of this using your router above.
App.Router.map(function () {
this.resource('cow');
this.resource('strats', {path: "/"}, function() {
this.route('strat', {path: "/strat/:strat_id"});
});
});
I visit the strats route. The model hook for that route (StratsRoute) will be hit (in your case a find, returning multiple strats).
I visit the strat/1 route. The model hook for the route (StratsStratRoute) will be hit( find(1))
I visit the strat/2 route. The model hook for the route (StratsStratRoute) will be hit( find(2))
I visit the cow route (which is outside of the strats).
I visit the strat/1 route, the model hook for each route will be called, 1 at a time down to the strat route. The StratsRoute will be called, and until that model has been returned it will wait, once it's returned it will then go to the StratsStratRoute route and wait until that's resolved, once resolved it will continue rendering the page etc.
Interestingly Ember Model has an interesting feature, you can use find or fetch. find will build up a dummy record and return it immediately, and once the ajax is returned it'll update the model. fetch will return a promise, and once the ajax has returned and the record fully built it will resolve the promise.
Note, there is a bit of a race condition in the find approach. Imagine if you do find() in the strats route, then find(1) in the strat route. They will both resolve immediately (with dummy records) and both make calls to the server. The first call will be for all of the records (which might include 1) the second call is for record #1. So we just made 2 calls for record one, that was kind of a waste. In this instance fetch would have been better. Let's think about it with fetch. fetch in the strats route, now we wait til it's resolved. Once it's resolved (aka the server returned all the models) we hit the strat route, it does fetch(1) and instead of it hitting the server, it knows it already has model 1, it came down in the strats route, so it doesn't make an ajax request, and it resolves immediately.
Hopefully this clears up the dust a bit.

Related

What RESTAdapter expects on server responses and what requests should server expect?

I'm using Django REST Framework, not Rails (which seems to have several magical gems to make everything work swiftly with Ember) and I've been having some difficulties trying to figure out how Ember expects responses. I'm using Ember CLI, thus I'm also using Ember data.
The documentation states only the typical GET usage, when I'm simply retrieving an object or an array of objects. Documentation states:
The JSON payload should be an object that contains the record inside a root property
And about conventions:
Attribute names in your JSON payload should be the camelCased versions of the attributes in your Ember.js models.
No problem with that.
1. But how should the API respond when there are errors?
Ok, so documentation also states you could use ajaxError to check jqXHR status for an error and then return a populated DS.Error for the record. However, how should I return different kind of errors. For example, let's say the user session is now invalid and because of that the server couldn't delete a record as requested.
2. How will Ember submit requests?
I'm quite new to REST in general. I think Ember simply use the appropriate verb for the action it wants: GET, POST, PUT, DELETE. I think it's quite clear it will send all the model's field to POST a new one, but how about DELETE? Will Ember send all the record or just the ID to delete an object?
Generally you should be able to see the requests Ember makes by just opening your browser dev tools and seeing the network requests.
Ember data likes the api to respond with an errors hash, something like this:
{"errors":{"title":["can't be blank"]}}
Then as long as you define a function to handle the error case:
Ember.Controller.extend({
actions: {
deleteUser: function() {
var user = this.model;
function success() {
// do something cool?
}
function failure() {
user.rollback();
}
user.destroyRecord().then(success, failure);
}
}
});
then user.errors will be automatically populated and you can do an if user.errors in your template.

Calling findAll during route transition

I'm using Ember 1.4 with EmberData beta 7. The routes in my application are fairly straight forward and looks like this. ScenarioController and StratsControllers are ArrayControllers. StratsStratController is an ObjectController.
App.Router.map(function () {
this.route('scenarios', {path: "/scenarios"});
this.resource('strats', {path: "/"}, function() {
this.route('strat', {path: "/strat/:strat_id"});
});
});
When I first transitioned into the 'strats' route, Ember calls the findAll method, which makes a request to my server for all 'strat' instances as expected. My server returns all data associated with the 'strat' model, side loading all related hasMany records. Then I transitioned to the scenarios route, and Ember calls findAll to fetch all 'scenario' instances, which was also as expected. However, when I transitioned back to the 'strats' route via the browser's back button, I see another GET message from Ember to my server requesting all 'strat' instances again. This surprised me. Why did Ember make another call to findAll for the 'strat' instances when it already has it in DS.store? Is this expected behavior?
I think its because its a live array of data, so ember checks to see if anything has changed in the mean time. You can change the data to a static array using the toArray() method but the way it is currently means that any updates to the database are automatically reflected on the client.
I have just come across this in the docs:
FINDING ALL RECORDS OF A TYPE
1
var posts = this.store.find('post'); // => GET /posts
To get a list of records already loaded into the store, without making another network request, use all instead.
1
var posts = this.store.all('post'); // => no network request
find returns a DS.PromiseArray that fulfills to a DS.RecordArray and all directly returns a DS.RecordArray.
It's important to note that DS.RecordArray is not a JavaScript array. It is an object that implements Ember.Enumerable. This is important because, for example, if you want to retrieve records by index, the [] notation will not work--you'll have to use objectAt(index) instead.

How can I mock an http call executed in module.run Angular app?

I am trying to run the following code (where fusseApp is an angularModule) :
fusseApp.run(function(URI, $http){
$http.get('/check/userLogged').then(function(response){
console.log(response);
});
PS : Actually my http call is executed in a factory and here we just have a call to this factory
The angularModule is mocked with :
angular.module('fusseApp.mainMocks', ['fusseApp', 'fusseApp.loginMocks', 'ngMockE2E', 'ngResource'])
.run(function($httpBackend, $http) {
...
$httpBackend.whenGET('/check/userLogged').respond(200,response);
My problem is that fusseApp.run is executed before the mock module is initialized. So I a have an error :
Uncaught Error: Unexpected request: GET /check/userLogged
No more request expected
This problem seems to have been raised in the jasmine unit-test, but I cannot find a solution. Is it possible to run my $http call after an event that tells me when the application is completely initialized?
My need is to check that the user is authenticated before displaying any data on screen.
maybe this can help...
.run() is always executed if you set ng-app="" in you code, its part of the boostrap process, from there on you can't stop the compile & linking process, but there's many ways to go to prevent a view from being rendered if the user is not logged in:
A) Manual angular boostraping (not so fun but feasible) can be done after authentication is ok, either use jQuery $.ajax or another lib because $http service is out the picture outside NG.
B) But in your .run() you have access to $rootScope and all of the NG services, therefore you can listen for $stateChangeStart and inside the listener perform you auth validation.
This is more or less the best practice for NG authentication, this listener keeps checking every time the user changes state, (which is good so that you can catch sessions expirations), In my code (which I use in prod in various projects) I do exactly this:
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
if (!AuthService.isAuthenticated()) {
$state.transitionTo("login"); // redirect to login page
event.preventDefault();
}
});
AuthService is just a simple service that returns true or false if the user is authenticated.
In my experience the best solution to hide views to unauthorized users and redirect, is to catch state change process like I show before.
Because you can decorate states these properties are accessible in the toState argument (in ui-router at least) So it's possible to do all kinds of fancy stuff like catching user roles and so on...

How to simulate, with FIXTURES, a slow REST backend?

I am trying to simulate a slow backend in a test application using FIXTURES. I am doing the following:
App.SlowIndexRoute = Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
resolve(App.Node.find());
}, 2000);
});
}
});
I was expecting that this would behave similarly to a slow REST backend, namely:
The request is sent
Route is activated, and the template is rendered
Reply arrives from backend
Now the data is updated in the template
Instead, this is roughly what is happening, as far as I can tell:
The request is sent
No rendering of the template is performed, the route is not yet activated.
Once the reply "arrives" (resolve(App.Node.find());) the route is activated
The template is rendered, and since the data is already available, it is also displayed
How can I more accurately simulate a slow REST backend? How can I make sure that the router is activating the view/template before the reply arrives?
DS.FixtureAdapter has a latency property that defaults to 50 milliseconds. You can change this by instantiating the adapter manually when you create your store.
App.Store = DS.Store.extend({
adapter: DS.FixtureAdapter.create({ latency: 1000 });
});
As of Ember 1.0.0-rc.6, it is expected behavior for a route to wait for the model's promise to resolve before transitioning. If you don't want the route to wait, don't return a promise (something with a then method) from your model hook. Return a regular object, one that's already loaded, or a proxy. Then load the model later, in your setupController hook, for example.

How to fire an event to Ember from another framework

We are using version pre4 of ember.
We have a framework (SignalR) working parallel with ember that handles real-time notifications to our application. In the older versions of ember we were able to access the global reference of the router / controller. But with the new version of Ember this is no longer possible. (This is fine)
We have tried different approaches like setting up a global controller in the top route:
setupController: function(){
app.appController = this.controllerFor('app');
}
and sending an event to this controller, which bubbles up to the route like this:
notificator.update = function (context) {
app.appController.send('notificationOccured', context);
});
But this feels like working against the Ember team which just removed the global references.
So now to the big question: is there a better way to access the router or a controller from outside Ember? Preferably send an event to either with a context.
All help is appreciated!
So now to the big question: is there a better way to access the router or a controller from outside Ember? Preferably send an event to either with a context.
Yes. This sounds like a good fit for the ember instrumentation module. Have an appropriate controller subscribe to SignalR events, then trigger them whenever your app handles real-time notification.
First, add a method to ApplicationController for processing updates. If not defined here the event would bubble to the router.
App.ApplicationController = Ember.Controller.extend({
count: 0,
name: 'default',
signalrNotificationOccured: function(context) {
this.incrementProperty('count');
this.set('name', context.name);
}
});
Next, setup your ApplicationController by subscribing to the signalr.notificationOccured event. Use the before callback to log the event and send it's payload to the controller.
App.ApplicationRoute = Ember.Route.extend({
setupController: function (controller, model) {
Ember.Instrumentation.subscribe("signalr.notificationOccured", {
before: function(name, timestamp, payload) {
console.log('Recieved ', name, ' at ' + timestamp + ' with payload: ', payload);
controller.send('signalrNotificationOccured', payload);
},
after: function() {}
});
}
});
Then from your SignalR Application, use Ember.Instrumentation.instrument to send payload to your ApplicationController as follows:
notificator.update = function (context) {
Ember.Instrumentation.instrument("signalr.notificationOccured", context);
});
I posted a working copy with simulated SignalR notifications here: http://jsbin.com/iyexuf/1/edit
Docs on the instrumentation module can be found here, also check out the specs for more examples.
You probably shouldn't be doing this but here's a way to get access to the application's controllers, views, models and router instances. When your application is initialized, controllers, views, models and router are all registered in the application container __container__
APP.__container__.lookup('controller:foo').get('content');
APP.__container__.lookup('view:foo').get('templateName');
APP.__container__.lookup('router:main');
What i think you should do is encapsulate calls to the 3rd party library inside Ember and let Ember manage the whole application. See this attempt to make JQuery UI ember-aware