Ember-simple-auth ApplicationRoute model function - ember.js

I have classic setup of Ember-simple-auth, in ApplicationRoute I use
model: function () {
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function(data) {
return data.get('content')[0]
})
});
},
setupController: function(controller, model) {
this.controllerFor('user').set('content', model.user);
}
When user losts authorization, and you open the page. ApplicationRoute::model is fired first, server returns 401 and other execution is stopped.
GET http://localhost:8000/app_dev.php/api/1/users.json 401 (Unauthorized)
Error while loading route: undefined
model should be fired only when authentication is successfull.
I saw that there is sessionAuthenticationSucceeded but I've tried all the ways to listen to it, noone worked. How to listen to this event and get data from server when user is successfully authenticated?
11/06 22:57 UPDATE:enter code here
One solution for this problem that I've managed to achieve, but it seems totally not ember way:
App.ApplicationRoute = Ember.Route.extend(Ember.SimpleAuth.ApplicationRouteMixin, {
skipModelLoading: false,
beforeModel: function() {
this.set('skipModelLoading', !this.get('session').get('isAuthenticated'));
},
model: function () {
if (this.get('skipModelLoading')) {
return;
}
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function(data) {
return data.get('content')[0]
})
});
},
setupController: function(controller, model) {
if (this.get('skipModelLoading')) {
return;
}
this.controllerFor('user').set('content', model.user);
}
});

I assume you're loading the authenticated user in that model method. I'd do it differently and attach that property to the session as shown in this example: https://github.com/simplabs/ember-simple-auth/blob/master/examples/4-authenticated-account.html#L101

I think I found a more ember-way solution for my problem:
App.ApplicationRoute = Ember.Route.extend(Ember.SimpleAuth.ApplicationRouteMixin, {
onSessionIsAuthenticated: function () {
var isAuthenticated = this.get('session').get('isAuthenticated');
if (!isAuthenticated) {
return false;
}
var userController = this.controllerFor('user');
return Ember.RSVP.hash({
user: this.store.find('gsUser').then(function (data) {
userController.set('content', data.get('content')[0]);
})
});
}.observes('session.isAuthenticated').on('init')
});

Related

How to continue even if Ember.js model hook doesn't load all promises?

I'm loading a route. Its model hook loads some models. Some are fetch from ember store and some are promises requested through AJAX:
model: function () {
return Em.RSVP.hash({
//the server data might not be loaded if user is offline (application runs using appcache, but it's nice to have)
someServerData: App.DataService.get(),
users: this.store.find('user')
});
}
The App.DataService.get() is defined as:
get: function () {
return new Ember.RSVP.Promise(function(resolve, reject) {
//ajax request here
});
}
Obviously if the request is rejected, the flow is interrupted and I cannot display the page at all.
Is there a way to overcome this?
Ember.RSVP.hashSettled is exactly meant for this purpose.
From tildeio/rsvp.js Github repository:
hashSettled() work exactly like hash(), except that it fulfill with a hash of the constituent promises' result states. Each state object will either indicate fulfillment or rejection, and provide the corresponding value or reason. The states will take one of the following formats:
{ state: 'fulfilled', value: value }
or
{ state: 'rejected', reason: reason }
Here is an example for using it (working JS Bin example):
App.IndexRoute = Ember.Route.extend({
fallbackValues: {
firstProperty: null,
secondProperty: null
},
model: function() {
var fallbackValues = this.get('fallbackValues');
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.RSVP.hashSettled({
firstProperty: Ember.RSVP.Promise.resolve('Resolved data despite error'),
secondProperty: (function() {
var doomedToBeRejected = $.Deferred();
doomedToBeRejected.reject({
error: 'some error message'
});
return doomedToBeRejected.promise();
})()
}).then(function(result) {
var objectToResolve = {};
Ember.keys(result).forEach(function(key) {
objectToResolve[key] = result[key].state === 'fulfilled' ? result[key].value : fallbackValues[key];
});
resolve(objectToResolve);
}).catch(function(error) {
reject(error);
});
});
}
});
fallbackValues can be useful for managing resolved hash's properties' fallback values without using conditions inside the promise function.
Taking into account that Ember.RSVP.hashSettled is not available in my Ember version. I come up with the following solution:
model: function(params) {
var self = this;
return new Em.RSVP.Promise(function(resolve, reject){
// get data from server
App.DataService.get().then(function(serverData) { //if server responds set it to the promise
resolve({
serverData: serverData,
users: self.store.find('user')
});
}, function(reason){ //if not ignore it, and send the rest of the data
resolve({
users: self.store.find('user')
});
});
});
}

Ember: returning just one record using something other than an id

Let's say that I want to have URLs like /users/JoshSmith for maximum readability/shareability.
I set up my Router:
this.resource('user', path: '/users/:username')
And my route:
var UserRoute = Ember.Route.extend({
model: function(params) {
debugger
return this.store.find('user', { username: params.username });
}
});
But this findQuery function actually returns an array, since it's calling /users?username= instead of calling /users/:username like I would normally do.
I'm a little lost as to how I should be handling this; I'm assuming there's a convention out there, I just can't find it.
As suggested here: http://discuss.emberjs.com/t/find-by-different-property/2479
Just override serialize on your route.
var UserRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('user', { username: params.username });
},
serialize: function(model) {
return { username: model.get('username') };
}
});
This replaces the default which looks like this:
serialize: function(model) {
// this will make the URL `/posts/12`
return { post_id: model.id };
}
Source: http://emberjs.com/api/classes/Ember.Route.html#method_serialize
I had this same problem as well, findQuery always returns a record array. The way I got around this was to simply change my model hook in the router to
model: function(params) {
return this.store.find('user', { username: params.username }).then(function(users) {
return users.get('firstObject');
});
}

Ember auth transition.retry() after login doesn't transit while testing

I am having some issues with testing my login and related features of my app. The app works perfectly, but the test fails. For testing, I use a Qunit with karma
I have created few authenticated routes(say accounts) which one can only visit after logging in. If a user goes to accounts route without logging, he is redirected to login page and after successful login, redirected back to the accounts page.
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!App.AuthManager.isAuthenticated()) {
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
var loginController;
loginController = this.controllerFor('login');
loginController.set("attemptedTransition", transition);
this.transitionTo("login");
},
events: {
error: function(reason, transition) {
this.redirectToLogin(transition);
}
}
});
App.LoginController = Ember.ObjectController.extend({
attemptedTransition: null,
loginUser: function() {
var attemptedTran, form_data, router, that;
router = this.get("target");
form_data = this.getProperties("email", "password");
attemptedTran = this.get('attemptedTransition');
that = this;
return $.post("/sessions", {
'session': form_data
}, (function(results) {
return Ember.run(function() {
App.AuthManager.authenticate(results.api_key.access_token, results.api_key.user_id);
if (attemptedTran) {
attemptedTran.retry();
return that.set('attemptedTransition', null);
} else {
return router.transitionTo("index");
}
});
}), "json");
}
});
App.AccountsRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.find('account');
}
});
I am trying to test this using
test("account index", function() {
expect(3); // Ensure that we will perform one assertion
visit("/accounts").andThen(function() {
equal(currentRouteName(),"login",'Accounts is an authenticated Route. so redirected to login page');
fillIn('input[type=text]', "j#j.com");
fillIn('input[type=password]', "jjjjjj");
click("button:submit").andThen(function(){
equal(currentRouteName(),"accounts",'After login redirected back to account page.');
})
});
But this test fails after logging in and doesn't redirect back to the accounts page.
Any help??
It looks like you're setting previousTransition then getting attemptedTransition. attemptedTransition should always be null according to the logic above.

Incrementally adding models to controller

I have a standard issue route like:
App.MessagesRoute = Ember.Route.extend({
model: function() {
return App.Message.find();
}
});
Which works great for the existing set of messages that i get from a REST endpoint. But I also get new messages via a websocket. How in the RC2 routing architecture (and non-Ember Data store) how do I plumb new message like these into Ember cleanly?
I solved this using the Ember.Instrumentation framework element that's talked about in How to fire an event to Ember from another framework.
Here's my complete code:
...
// this is fired from my websocket message reception code.
Ember.Instrumentation.instrument('onMessage', message);
...
App.MessagesRoute = Ember.Route.extend({
setupController: function(controller, model) {
Ember.Instrumentation.subscribe('onMessage', {
before: function(name, timestamp, message) {
controller.send('onMessage', message);
},
after: function() {}
});
},
model: function() {
return App.Message.find();
}
});
App.MessagesController = Ember.ArrayController.extend({
onMessage: function(message) {
this.unshiftObject(message);
}
});

Computed property in handlebar #if not updating

I am trying to use the following template:
<script type="text/x-handlebars" data-template-name="login">
{{#if logged_in}}
Logged in
{{else}}
Not logged in
{{/if}}
</script>
with the model:
App.Login = DS.Model.extend({
access_token: DS.attr('string'),
logged_in: function() {
return (this.get('access_token') != null);
}.property('access_token')
});
to display the user's logged-in state.
The access_token is being set via an async callback in the Route's setupController:
App.LoginRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('content', model);
// call async login method
window.setInterval(function test() {
model.set('access_token', 'MY_ACCESS_TOKEN');
console.log(model.get('access_token'));
}, 5000);
},
model: function() {
return App.Login.find();
}
});
The problem is logged_in never seems to change (even though the model.set line is executed and 'access_token' is updated). Am I doing something wrong or should I be filing a bug?
Full code: http://jsfiddle.net/Q8eHq/
You are setting the model to App.Login.find() which returns an enumerable, not a single object. One way to do it, is to set the model to a single object:
App.LoginRoute = Ember.Route.extend({
model: function() {
return App.Login.find(1);
}
});
Or if you are going to use a dynamic route (e.g. users/login/9):
App.LoginRoute = Ember.Route.extend({
model: function(params) {
return App.Login.find(params.id);
}
});