Loopback: Can't modify response data in afterRemote

Created on 12 Nov 2015  路  5Comments  路  Source: strongloop/loopback

This is a very strange issue and annoying because it's just one of those that's not supposed to not work!

I have this mixin which is simply supposed to delete the password from the response data just like in the documentation:

Model.afterRemote(options.from, function(ctx, modelInstance, next) {
        if (ctx.result) {
            if (Array.isArray(ctx.result)) {
                ctx.result.forEach(function(result) {
                    delete result.password;
                    result.more = 'Add more!';
                });
            } else {
                delete ctx.result.password;
            }
        }
        console.log('Deleted password', ctx.result);
        next();
    });

The result still includes the password and the more property that I've added doesn't exist in the response data.

Interestingly if I set result.password = null; the console shows the password as null!?
So it won't delete it, it won't add a property but it will let me set it to null.

Some bug? Caching the results? Any help please, I'm pulling out my hair!

Most helpful comment

@EmileSpecs well I think I find the solution, hope it's not too late for you :) Please check this issue:
https://github.com/strongloop/loopback/issues/1162
I tried and it works. The solution is to use unsetAttribute instead of delete, so in your case, it should be result.unsetAttribute('password')
And for adding property, you can use result.newPropertyName = new String('myNewProperty').
For more detailed explanation, please check docs here: https://docs.strongloop.com/display/public/LB/Operation+hooks#Operationhooks-Operationhookcontextobject

All 5 comments

hi @EmileSpecs I am trying to reproduce your problem, can you send me your model's .json and .js file?

Hi @jannyHou , thanks!

Model:

{
  "name": "EmailAccount",
  "base": "PersistedModel",
  "strict": false,
  "idInjection": false,
  "options": {
    "validateUpsert": true
  },
  "mixins": {
    "Timestamp": true,
    "Removepassword": { 
      "from": "find"
    }
  },
  "properties": {
    "archiveConfig": {
      "type": "object"
    },
    "importConfig": {
      "type": "object"
    },
    "tags": {
      "type": "object"
    },
    "user": {
      "type": "string",
      "required": true
    },
    "password": {
      "type": "string"
    },
    "host": {
      "type": "string",
      "required": true
    },
    "archive": {
      "type": "boolean",
      "required": true,
      "default": false
    },
    "import": {
      "type": "boolean",
      "required": true,
      "default": false
    },
    "slug": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "emails": {
      "type": "hasMany",
      "model": "Postbox",
      "foreignKey": ""
    }
  },
  "acls": [],
  "methods": []
}

This is the mixin (removepassword.js):

module.exports = function(Model, options) {
    options.from = options.from || '**';

    Model.afterRemote(options.from, function(ctx, modelInstance, next) {
        if (ctx.result) {
            if (Array.isArray(ctx.result)) {
                ctx.result.forEach(function(result) {
                    delete result.password;
                    //result.password = null;
                });
            } else {
                delete ctx.result.password;
            }
        }
        console.log('Delete password', ctx.result);
        next();
    });
}

And for what it's worth the Model's js:

var fs = require('fs');

module.exports = function(EmailAccount) {

    EmailAccount.observe('before save', function(ctx, next) {
        var path = null;
        var account = ctx.instance || ctx.data;

        if (!account.archive && account.archiveConfig && Object.getOwnPropertyNames(account.archiveConfig).length === 0) delete account.archiveConfig;
        if (!account.import && account.importConfig && Object.getOwnPropertyNames(account.importConfig).length === 0) delete account.importConfig;

        if (account.archive) {
            path = account.archiveConfig && account.archiveConfig.path ? account.archiveConfig.path : null;
            if (!path) return next(new Error('The archive path has not been provided.'));
        }

        if (!ctx.isNewInstance && !account.password) delete account.password;

        if (path) {
            fs.access(path, next);
        } else {
            next();
        }
    });
};

The relation to Postbox I assume isn't relevant? Can probably just leave that out, since the issue arises when I do a simple find without any inclusions etc.

Hi @EmileSpecs, I created the emailaccount model as you provided and copied removepassword.js into server/mixins/. Since I have nothing in front-end, I did everything from the explorer. What I did is creating some sample emailaccounts and then clicking endpoint GET /emailaccount.
Right the mixin doesn't work, but I am not sure whether my process reproduced your problem in a right way, so I need to confirm with you first then I can move to the next step to fix it.
Thanks.

@EmileSpecs well I think I find the solution, hope it's not too late for you :) Please check this issue:
https://github.com/strongloop/loopback/issues/1162
I tried and it works. The solution is to use unsetAttribute instead of delete, so in your case, it should be result.unsetAttribute('password')
And for adding property, you can use result.newPropertyName = new String('myNewProperty').
For more detailed explanation, please check docs here: https://docs.strongloop.com/display/public/LB/Operation+hooks#Operationhooks-Operationhookcontextobject

@jannyHou thanks! I saw that in the documentation but didn't think the resulting array of objects would be instances of the model and thus have that function so I just ignored it.

Also because of the example in the documentation... I suggest that the documentation be updated, since it's obviously not correct.

I'm not sure when the unsetAttribute function wouldn't be be available but I'm now using:

if (typeof result.unsetAttribute === 'function') result.unsetAttribute('password');
else delete result.password;

...just to be sure that the job gets done either way.

Thanks for the help!

Was this page helpful?
0 / 5 - 0 ratings