3

I've got a controller method that takes a string argument so I can test if a user has a capability. The user has many roles and the roles has an array of permissions attached that we need to check if it contains the capability. I know this is overly verbose, but for the sake of understanding, I've left it so. Will refactor later...

App.WorkspaceIndexController = Ember.Controller.extend({
    userCan: function(capability) {
      var userHasCapability = false;
      var userPromise = this.get('session.user');

      var rolesPromise = userPromise.then( function(user) {
        return user.get('roles');
      });

      var filteredRolesPromise = rolesPromise.then(function (roles) {
        return roles.filter(function (role) {
          return role.get('workspace') === self.get('workspace');
        });
      });

      filteredRolesPromise.then(function (filteredRoles) {
        return filteredRoles.forEach(function (role) {
          userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'), capability);
        });
      });

      return userHasCapability;

    },
    ...
});

The problem I'm having, is that I need the method to return a boolean if the user has the permission. This returns false every time. Am I setting the userHasCapability property improperly, or is there something else I should be doing to return the value?

ToddSmithSalter
  • 715
  • 6
  • 20

1 Answers1

3

Primitive types such as bool, int, string etc are copied, not referenced. This means you return userHasCapability, and the value false is returned immediately, setting userHasCapability within the scope of that promise, doesn't mean it will be returned. In fact it won't.

Additionally the real response will need to be in the form of a promise, and whomever calls it will need to use it in that form as well.

Here's the order of operations, assuming foo calls userCan.

App.WorkspaceIndexController = Ember.Controller.extend({
    foo: function(){
      var j = this.userCan('eat worms');  // 1. method is called, 6. j is set to false, we fall out of foo
    },
    userCan: function(capability) {
      var userHasCapability = false;
      var userPromise = this.get('session.user');

      var rolesPromise = userPromise.then( function(user) { // 2. promise built
        return user.get('roles'); // 7. this promise happens is resolved
      });

      var filteredRolesPromise = rolesPromise.then(function (roles) { // 3. another promise built
        return roles.filter(function (role) { //8 return the filter cause 7 resolved
          return role.get('workspace') === self.get('workspace');
        });
      });

      filteredRolesPromise.then(function (filteredRoles) { // 4. another promise built
        return filteredRoles.forEach(function (role) {  //9. 8 resolved so do this now, even though no one references userHasCapability anymore
          userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'),  capability);
        });
      });

      return userHasCapability; // 5. false is returned

    },
    ...
});

The fact that roles is a promise means anyone that tries to use it needs to expect a promise as a result (or don't use async, and don't use promises)

App.WorkspaceIndexController = Ember.Controller.extend({
    foo: function(){
      this.userCan('eat worms').then(function(result){
        console.log(result);
      });
    },
    userCan: function(capability) {
      var userHasCapability = false;
      var userPromise = this.get('session.user');

      var rolesPromise = userPromise.then( function(user) { // 2. promise built
        return user.get('roles'); // 7. this promise happens is resolved
      });

      var filteredRolesPromise = rolesPromise.then(function (roles) { // 3. another promise built
        return roles.filter(function (role) { //8 return the filter cause 7 resolved
          return role.get('workspace') === self.get('workspace');
        });
      });

      return filteredRolesPromise.then(function (filteredRoles) { // 4. another promise built
        filteredRoles.forEach(function (role) {  //9. 8 resolved so do this now, even though no one references userHasCapability anymore
          userHasCapability = _.contains(_.flatten(role.get('permissions'), 'name'),  capability);
        });
        return userHasCapability;
      });
    }
});
Kingpin2k
  • 47,277
  • 10
  • 78
  • 96
  • That is pretty awesome. Sure helps me understand how the promise chain is built. Are embedded records promises? Roles is always embedded inside the user model. I'm not sure why is returns a promise every time. Shouldn't the embedded records already be there? If not, how can I go about getting these records without using promises. – ToddSmithSalter Sep 12 '14 at 02:22
  • embedded records doesn't define whether or not something is a promise, really the `async:true` is the thing that defines that. – Kingpin2k Sep 12 '14 at 05:57
  • Is your attributes still defined like this: `roles: DS.hasMany('role'),`? – Kingpin2k Sep 12 '14 at 05:58
  • Yes it is. I suppose async is true by default? I'm on ember-data beta 9. So If I set it to {async: false}, that should do it? – ToddSmithSalter Sep 12 '14 at 14:41
  • After tinkering a bit, I'm not seeing how I can get the roles synchronously. I always have to get the user, which returns a promise, then get the roles. In that sense, won't userCan() always be thenable? – ToddSmithSalter Sep 12 '14 at 16:56
  • When/how are you fetching the user? – Kingpin2k Sep 12 '14 at 17:33
  • The user is added to the session object in an initializer, which I'm positive you're familiar with. I can `this.get('session.user')` from routes and controllers. – ToddSmithSalter Sep 12 '14 at 17:41
  • Does the initializer block the app from starting until it sets the user, or does it set the user promise immediately and allow the app to continue starting? – Kingpin2k Sep 12 '14 at 17:56
  • If so, you should block your app from starting, and set the user record (instead of the promise), then allow your app to start (warning, this will delay the app, and you need to have a contingency that will be prepared for an error response, or your app will never start) Here's an example: http://emberjs.jsbin.com/OxIDiVU/1072/edit – Kingpin2k Sep 12 '14 at 18:07
  • Alright, I think that's done the trick. I was using Session.reopen() on SimpleAuth. Here's my working method that I can use in an if block: https://gist.github.com/ToddSmithSalter/f3c2b3c517ce3fe7ea39 – ToddSmithSalter Sep 12 '14 at 21:10