Skip to content

Wildcard remote hooks for relation methods #737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bluestaralone opened this issue Nov 4, 2014 · 22 comments
Closed

Wildcard remote hooks for relation methods #737

bluestaralone opened this issue Nov 4, 2014 · 22 comments

Comments

@bluestaralone
Copy link

Example:
I make some requests :
POST /Books/{id}/sharedBooks
GET /Books/{id}/sharedBooks/{fk}
DELETE /Books/{id}/sharedBooks/{fk}

As my example you can see the code in SharedBook model like this:

// reset var defined during SV development
    SharedBook.beforeRemote('**', function (ctx, inst, next) {
      console.log('aaaaa');
      next();
    });

But i can't receive any text message from console log, how can i hook to beforeRemote in this case ?
Please help me, thanks much.

@bluestaralone bluestaralone changed the title Did remote method call in relation models when i call request from main model ? Is remote method call in relation models when i call request from main model ? Nov 4, 2014
@projectxmaker
Copy link
Contributor

@bajtos do you have any experience in this case ?

@bajtos bajtos added the question label Nov 4, 2014
@bajtos
Copy link
Member

bajtos commented Nov 4, 2014

GET /Books/{id}/sharedBooks calls Books.__get__sharedBooks__(id, cb) under the hood. You should be able to install a before-remote hook via

 Book.beforeRemote('*.__get__sharedBooks__', function(ctx, inst, next) { ... });

I don't think there is a way for specifying a wildcard hook for relation methods.

@bajtos bajtos changed the title Is remote method call in relation models when i call request from main model ? Wildcard remote hooks for relation methods Nov 4, 2014
@projectxmaker
Copy link
Contributor

thanks, it works.

@MikeSpock
Copy link

Does this mean, that in order to have a consistent API, if I have a remote hook to sharedBooks create, I have to create as many remote hooks as many relation I have to it?
First I create a sharedBooks.beforeRemote, then one hook for each relation, in case someone would like to create a sharedBook through it? - doesn't make too much sense.

Can this be fixed sometime in the future?

@bajtos
Copy link
Member

bajtos commented May 13, 2015

Does this mean, that in order to have a consistent API, if I have a remote hook to sharedBooks create, I have to create as many remote hooks as many relation I have to it?
First I create a sharedBooks.beforeRemote, then one hook for each relation, in case someone would like to create a sharedBook through it?

Yes, that's a correct assessment of the current implementation.

BTW have you tried using the recently added Operation Hooks instead of remoting hooks? I believe Operation Hooks are correctly invoked for relation methods too.

Can this be fixed sometime in the future?

Well, I'd like to see this fixed too, but the solution gets tricky pretty quickly.

Let's say you have "Product hasAndBelongsToMany Categories" and you want to create a remote hook for the "link" method, i.e. Product.__link__categories. What remote hook should be invoked on the Category model in this case?

Another example: "Product hasMany Categories" and making the request "GET /api/products/1/categories/2". Should we invoke remoting hooks for Category.find, Category.findById, or some other method?

I'll reopen this issue to keep the discussion going.

@bajtos bajtos reopened this May 13, 2015
@MikeSpock
Copy link

Operation hooks does seem to work correctly, and correct me if I'm wrong, but I didn't seem to find a way to access accessToken or session data inside observe. My exact usecase is that I want to have createdAt, createdBy, modifiedAt, modifiedBy properties, the createdBy & modifiedBy being belongTo relations to users. For this to work, I need the accessToken.

order.beforeRemote() works fine, and I can get the accessToken through ctx.req.accessToken.
order.observer("before save") works too, bc I can check ctx.isNewInstance and touch modified and created properties accordingly, but I don't have an accessToken in ctx.req, so in the end, I don't know who modifies/creates the model.

And I might set ACLs so you can't access /api/order/{order_id}, only /api/users/{my_user_id}/orders/{order_id}. I think this makes sense, and is the correct use if I don't want other people to access other's orders. Now I don't want to reimplement the same logic when an order is created from different REST endpoints - I don't care which endpoint was called to create an order. It may have been /api/orders /api/users/.../orders api/product/.../orders api/affiliate/.../orders or any other. And I don't want doubled code for each relation only to handle createdBy and modifiedBy.

I'm not very familiar with the link method, so I wouldn't know.

As for find and findById, I never was a big fan of findById, and beyond my emotional issues, I think if a function like this exists, I would say, it should only be a facade to find({where:{id:xxx}). The only usecase I know where it worth making a difference between find and findById is when someone wants to restrict listing everything, but would like to let individual records to be queried. I may be utterly simplifying here, but if that is the case, than it is a rare usecase, and should be handled in the .js as custom logic, and the findById should be handled as a oneliner interpreter for find() with where filter, then a query to /api/products/1/categories/2 would invoke Category.find and Product.find only. It's absolutely possible that I'm missing the point of findById, and if so pls enlighten me :)

@bajtos
Copy link
Member

bajtos commented Jun 1, 2015

@MikeSpock

Operation hooks does seem to work correctly, and correct me if I'm wrong, but I didn't seem to find a way to access accessToken or session data inside observe.

That's correct. I think we don't have a standardized way for passing accessToken to operation hooks and model methods yet. There are two possible solutions:

  1. Use loopback.getCurrentContext().get('accessToken') inside your hook

  2. Since most DAO methods accept an options argument now, you can add a before-remote hook to add the token to this argument and then pick it up via ctx.options in your hook

// IIRC, remote hooks are not inherited, you have to set it up on every model instance
// The easiest solution is to override setup() of LoopBack core Model.
var _setup = loopback.Model.setup;
loopback.Model.setup = function() {
  _setup.apply(this, arguments);
  this.beforeRemote('**', function(ctx, unused, cb) {
    ctx.args.options = ctx.args.options || {};
    ctx.args.options.accessToken = ctx.req.accessToken;
    cb();
  });
};

// now in your common/models/order.js
Order.observe('before save', function(ctx, next) {
  var token = ctx.options && ctx.options.accessToken;
  if (!token) return next(new Error('Authorization is required.'));
  // make your changes
  next();
});

Please note that I haven't tested the code, it may need few tweaks to get it working

I am cc-ing @raymondfeng and @ritch, they may be able to provide additional information on how to access the current accessToken and/or the current user from an operation hook and/or a model method.

@ritch
Copy link
Member

ritch commented Jun 1, 2015

@bajtos I have had the same exact thoughts recently. We should promote this somehow. Perhaps as a first class feature in remoting or core.

As far as this issue is concerned +1 to @bajtos response above.

@tekand
Copy link

tekand commented Nov 21, 2015

Hi,

I have a quite similar problem. Lets say I have Product hasAndBelongsToMany Categories. What operation hook should I define if I like to trigger additional actions (send a push notification for example) if a new relation is made between these two models (PUT /Products/{id}/categories/rel/{fk} or PUT /Categories/{id}/products/rel/{fk})

Thanks,

David

@n2sandhu
Copy link

n2sandhu commented Feb 3, 2016

Hey @tekand , just wondering have you found the solution to the problem above?? I am looking for a similar solution

@tekand
Copy link

tekand commented Feb 3, 2016

@n2sandhu nope, still unsolved. :(

@moklick
Copy link

moklick commented Jun 9, 2016

I am also looking for a way to observe when a new relation is created. Can't find anything in the docs.

@jwebcat
Copy link

jwebcat commented Jun 26, 2016

I would like to do the same, a way to observe when a link is created.

@pulkitsinghal
Copy link

pulkitsinghal commented Aug 18, 2016

@bajtos and/or @ritch - would you mind either answering @tekand's question here or moving the discussion by @tekand, @n2sandhu, @moklick and @jwebcat into another issue labeled as discussion or question if you don't think this is the right thread?

Nov 2015 to Jun 2016 is an awful long time ;)

@bajtos
Copy link
Member

bajtos commented Aug 24, 2016

Thanks for the poke, @pulkitsinghal.

@tekand @moklick @jwebcat please open a new issue to discuss this question. Let's keep the discussion here focused on remote hooks for relation methods.

@timlind
Copy link

timlind commented Apr 6, 2017

When I put a remote hook on Favorite.afterRemote('find') I expect it will execute when I access /profiles/1/favorites.

Can we introduce this as a feature @bajtos?

@bajtos
Copy link
Member

bajtos commented Apr 18, 2017

When I put a remote hook on Favorite.afterRemote('find') I expect it will execute when I access /profiles/1/favorites.

Yes, that's what we all would expect.

Unfortunately, the way how relation methods are exposed via strong-remoting/REST makes it very difficult to implement such desired behaviour.

Can we introduce this as a feature

Sure, you are welcome to give it a try! See http://loopback.io/doc/en/contrib/index.html to get started.

Just be warned, this feature is tricky and complex to implement (at least I think so).

@stale stale bot added the stale label Aug 23, 2017
@stale stale bot closed this as completed Sep 6, 2017
@muhawo
Copy link

muhawo commented Feb 14, 2018

@bajtos where can find doc about relation model remote hooks , I did not find the format '*.get__sharedBooks'

@muhawo
Copy link

muhawo commented Feb 14, 2018

@danazkari
Copy link

@bajtos hey! Sorry to bubble up this issue again... but I read your comment #737 (comment), and noticed that was back on 2014, sooo I was wondering if this is still the case? I tried it and didn't work at all with LB 3.

For now, I'm making custom endpoints so this is not super urgent but something I'd like to know how to do.

Thanks!!!

@bajtos
Copy link
Member

bajtos commented Jun 14, 2018

I read your comment #737 (comment), and noticed that was back on 2014, sooo I was wondering if this is still the case? I tried it and didn't work at all with LB 3.

Yes, this issue is fortunately still not addressed. Considering our focus on LB 4, I don't expect this issue to be fixed in LB 3.x ever 🙁

@alextsoi
Copy link

alextsoi commented Jun 16, 2018

My case is:
Endpoint: /Accounts/{id}/items
Goal: Do some data preprocessing before creating items
Direction: use beforeRemote function to model

  1. First I tried Accounts.beforeRemote('**', ... to dump the context, I found out loopback will display my relation method as Account.prototype.__create__items
  2. I did a quick check on loopback source code (https://github.com/strongloop/loopback/blob/0feda03d5b9534d4ce785e1de18c6788300c77af/lib/model.js) Improve the ACL matching algorithm #214, found out that the code will build the function name "className + '.' + name"
  3. I change beforeRemote(functionName), i.e. functionName as "*.__create__items" OR "prototype.__create__items", it works.

Account.beforeRemote('prototype.__create__items', function(ctx, modelInstance, next){
//YOUR LOGIC HERE
next();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests