Loopback: Remote Hook - beforeRemote - upsert not getting triggered

Created on 13 Jan 2016  路  18Comments  路  Source: strongloop/loopback

As per Remote Hooks and PersistedModel REST API documentation, we can use 'upsert' Remote hook.

I have a mixin

module.exports = function(Model, options){
    Model.beforeRemote('upsert',function(ctx,next){
        console.log('upsert called');
        next();
    });
};

but this is not getting triggered when I add a new model instance. Please help.

triaging

Most helpful comment

@IvanAlegre... I found the solution!

Instead of just updateAttributes, use prototype.updateAttributes.

All 18 comments

hey @pktippa the main problem is that you miss an argument modelInstance in your callback function, since upsert is an instance method, the syntax should be:

Model.beforeRemote('upsert', function(ctx, modelInstance, next){
       // Your Code
});

Please check our remote hook docs for more details. In the signature part, it explains the syntax for instance method.


I also created a repo to reproduce your issue:
Model test has a RemoteHook for upsert:

Test.beforeRemote('upsert',function(ctx, modelInstance, next){
        console.log('upsert called');
        next();
});

And it works with endpoint PUT /tests

hey @jannyHou thanks for the response.
I observed 3 things here.
a. As per API docs Upsert goes under static methods but not instance methods.
b. The 'upsert' hook getting triggered when we do a PUT but not for POST why?
c. The 'upsert' hook not getting triggered for PUT/{id}

hey @pktippa sorry for misguiding you yesterday, upsert is a static method not instance, and the point is upsert is a build-in method instead of remote method. That's why you will get problem when you use a remote hook.
So you can either do the trigger with a Model hook or Operation hook

btw, POST matches create not upsert.

Feel free to reopen it if you have other ideas! And you are welcomed to post question on our google group!

@pktippa you might have to use the alias updateOrCreate. I'm not quite sure why it doesn't work but it should.

Also, upsert was implemented as a static method because of the way prototype methods are called by strong-remoting. Any operation performed on this example endpoint /api/Examples/{id}/ will first call findById(...) and then the remote method you originally wished to call. If you try to call PUT /api/Examples/{id}/ for a model that doesn't exist, the findById call with throw an error before updateAttributes is even called.

I think this is a major design flaw and really introduces an inconsistency with the REST endpoints. We are currently working on improving the behavior.

^ EDIT:

the findById call with throw an error before upsert is even called

changed to:

the findById call with throw an error before updateAttributes is even called

@pktippa

a. As per API docs Upsert goes under static methods but not instance methods.
b. The 'upsert' hook getting triggered when we do a PUT but not for POST why?
c. The 'upsert' hook not getting triggered for PUT/{id}

a) It is a static method because of my above explanation (the design flaw)
b) The create method is mapped to POST and updateOrCreate (alias is upsert) is mapped to PUT
c) The 'updateAttributes` method is mapped to PUT MyModels/{id}

There are no bugs here, the functionality is as expected, but I agree, it is all rather inconsistent and thus confusing. I believe I created an issue somewhere to improve the design.

@richardpringle Thanks for the explanation.

Is it possible that updateAttributes remote hook is not being fired when making a PUT to Test/{id}?

Test.beforeRemote('updateAttributes',function(ctx, modelInstance, next){
        console.log('PUT called');
        next();
});

The code from above does not print anything. At the moment i'm doing this:

Test.beforeRemote('**',function(ctx, modelInstance, next){
        if (ctx.method.name === 'updateAttributes') console.log('PUT called');
        next();
});

And this works as expected

@IvanAlegre, I think you need to use updateAttributes in place of upsert. Have you tried that?

@richardpringle sorry, I had a typo, I have updateAttributes in the remote hook

@IvanAlegre

Can you do me a huge favor and open a new issue in strong-remoting/issues?

Mention me in a comment and I will label it as a bug.

@IvanAlegre... I found the solution!

Instead of just updateAttributes, use prototype.updateAttributes.

Thank you so much @richardpringle !!

put works fine but what about patch?

Model.beforeRemote('prototype.updateAttributes', function(ctx, modelInstance, next){
console.log(modelInstance);
next();
})

For me the modelInstance is always empty object {}. However ctx.instance is the instance snapshot before the update. What modelInstance is supposed to be?

Edit. the update goes through because I can see the record in db gets updated. So I don't think modelInstance being empty is the correct behavior. If I want to get the updated snapshot of the model instance in beforeremote how can I do it?

prototype.patchAttributes for method PATCH
for the others I found it when set _methodname_ to _**_ and console.log context object (ctx)

ModelName.beforeRemote( "**", function( ctx, unused, next) {
    console.log('ctx.methodString', ctx.methodString)
    next();
});

Use the string that append ModelName
(I've tried on LoopBack 3.0)

I also always get an empty object for beforeRemote, thats really strange :/

EDIT:

figured out why. Actually the beforeRemote function looks like this:

ModelCtor.beforeRemote = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); };

This is why the beforeRemote hook will only get the information if the context already has a result at the time the remote hook triggers. This is not always the case (at least for me its never the case). Instead I am reading the information of what was sent by the user from ctx.req.body, but I am not sure if that actually passes validation before triggering the hook.

for PATCH aka upsert, you should use 'patchOrCreate'

e.g.

Model.beforeRemote('patchOrCreate', function(ctx, modelInstance, next){
       // Your Code
});
Was this page helpful?
0 / 5 - 0 ratings