Loopback: How to override remote methods for relations?

Created on 22 Aug 2014  Â·  27Comments  Â·  Source: strongloop/loopback

doc

Most helpful comment

Tried to register a remote method on the url of the relation method and it still calls the relation method.
Tried to register it after the "attached" event was fired.
Tried to override the method on the model itself (__create__relation)

Nothing seems to work. Wtf :+1: :)

All 27 comments

when setting up the relation in code, you could try:

var findById = function(id, cb) {
  ...
};

reverse.shared = true; // remoting

Category.hasMany(Product, { scopeMethods: {
  findById: findById
} });

Here's a more elaborate example, which I'm using to allow a model to be found by id or slug:

var relationName = 'products';
var propertyName = 'slug';

var relation = Model.relations[relationName];
var scope = Model.scopes[relationName];

var idName = Model.dataSource.idName(relation.modelTo.modelName);

var wrapMethod = function(methodName) {
    return scope.methods[methodName] = (function(method) {
        return function() {
            var self = this;
            var args = _.toArray(arguments);
            if (_.isObjectID(args[0])) {
                method.apply(self, args);
            } else {
                var findById = methodName === 'findById';
                var cb = _.last(args);
                var cond = { where: {}, limit: 1, fields: {} };
                cond.fields[propertyName] = true;
                cond.fields[relation.keyTo] = true;
                cond.fields[idName] = true;
                if (findById) delete cond.fields; // ignore
                var id = cond.where[propertyName] = args.shift();
                this[relationName](cond, function(err, items) {
                    var item = items[0] || null;
                    if (findById) return cb(err, item);
                    if (item) id = item[idName];
                    method.apply(self, [id].concat(args));
                });
            }
        };
    }(scope.methods[methodName]));
};

Model.prototype['__findById__' + relationName] = wrapMethod('findById');
Model.prototype['__updateById__' + relationName] = wrapMethod('updateById');
Model.prototype['__destroyById__' + relationName] = wrapMethod('destroy');
Model.prototype['__exists__' + relationName] = wrapMethod('exists');

But how can i change remote parameters like accepts, returns and similar?

Have you tried the following?

Model.prototype['__findById__' + relationName].accepts = { ... };
Model.prototype['__findById__' + relationName].returns = { ... };

I tried this:

  Exercise.prototype.__create__resources = function (req, res, cb) {
    var self = this;
    self.resources.create({id: new ObjectID()}, function(err, resource) {
      if (err) cb(err);
      resource.upload(req, res, function(e, d) { self.save(); cb(e, d); });
    });
  };
  Exercise.prototype.__create__resources.accepts = [
    {arg: 'req',  type: 'object',  'http': {source: 'req'}},
    {arg: 'res',  type: 'object',  'http': {source: 'res'}}
  ];
  Exercise.prototype.__create__resources.returns =
    {arg: 'resp', type: loopback.getModel("resource"), root: true};
  Exercise.prototype.__create__resources.description =
    "Uploads exercise resource";

I can override function this way, but no accepts and returns. Any other idea?

Well i found a way, if i also define shared and http properties, it works, but now i have two entries in loopback explorer, so this is just an ugly hack.

UPDATE: This only works sometimes, so don't use it.

@offlinehacker since you are assigning a completely new method, you have to set it to shared, so that's correct. Can you elaborate on the 'sometimes' part?

I will investigate this and see if I can provide a helper function to do this consistently.

OK, looks like strong-remoting allows registering functions by name, and then resolves (a.k.a. binds) them for remoting later. Internally, this list is kept as an array, so that might explain why you're seeing the double entries in explorer.

I would suggest the following for now - can you try this?:

var sharedMethod = Exercise.sharedClass.find('__create__resources', false); // false: non-static
sharedMethod.accepts = [ … ];
sharedMethod.returns = { … };

Exercise.prototype.__create__resources = function (req, res, cb) {
   ... 
};

// Perhaps this is needed:
Exercise.setup();

Hello @offlinehacker
Is this issue still relevant or it is been resolved?

@fabien Thanks a lot for the clarification.

Closing due to inactivity. If you are still running into problems, feel free to leave a comment and I will reopen the issue.

I do need to override User delete method, instead of deleting I want to update "deleted" field. And I want to keep the same path : DELETE /api/User

How can i achieve this in 2016 :) ?

@amaurybrisou Got the same question here !

+1 ..trying to override /api/user/{id}/items

+1 I need to override a hasAndBelongsToMany relation method, /api/entity/{id}/relation/rel/{fk}... overriding the __link__relation function does not work, and I don't quite understand the above suggestions. Is there any documentation about this?

Re-opening issue. ..

+1

+1

Tried to register a remote method on the url of the relation method and it still calls the relation method.
Tried to register it after the "attached" event was fired.
Tried to override the method on the model itself (__create__relation)

Nothing seems to work. Wtf :+1: :)

+1

My work around:

  1. Disable that remote method.
  2. Create a new remote method of your own, name it as the one you want to override.

@dktan if you disable remote method /api/user/{id}/items, create your own. How did you define remote method to use the same url pattern? (I mean show "api/user/{id}/items" in explorer and get the "id" in code). I search how to implement in docs, but with no luck.

@windyinwind I did not try with User model, but with my current models, make sure your method signature is:

MyModel.prototype.customMethod = function (filter, cb) {
  var self = this;
  // You can get id by accessing self.id
  ...
}

With the "prototype" method declaration, you could have an instance matching with the "id" via self that the loopback-explorer exposes.

@nghiaht @kennethlynne @windyinwind @dktan @loay @superkhau , I am able to override the inbuilt remote methods of the relation as following:

  • [ ] - add a model event listener for 'booted' (or 'started', if event is emitted in server.js, when app.listen() is called)
  • [ ] - attach the customized method only after the application has booted or started.
const app = require('../../');
module.exports = function(Customer) {
  app.on('started', function() {
    const originalFindOrders = Customer.prototype['__findById__orders'];
    Customer.prototype['__findById__orders'] = function() {
      //custom logic here
      originalFindOrders.apply(this, arguments);
    };
  });
};

@superkhau @bajtos , it would be helpful to add documentation on overriding inbuilt remote methods. I will do it this sprint.

Connect to #443

Actually, closing in favour of #443

Hi. I have some situation here. I'd like the existence of the "documented" parameter to bypass swagger without lose the remoting functionalities. Well, I realised that I can run a boot script, selecting which endpoint deploy on swagger and which not, but, the issue is with related models, I cannot apply the documented property. I think is related with this issue, I'd like to override the by default "documented" attribute set as shared method. Is there a way to do that with related models?

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

crandmck picture crandmck  Â·  3Comments

htmlauthor picture htmlauthor  Â·  3Comments

nmklong picture nmklong  Â·  3Comments

cajoy picture cajoy  Â·  4Comments

ian-lewis-cs picture ian-lewis-cs  Â·  4Comments