Skip to content
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

Loopback relations validate #114

Closed
wesleymilan opened this issue Jul 26, 2015 · 8 comments
Closed

Loopback relations validate #114

wesleymilan opened this issue Jul 26, 2015 · 8 comments

Comments

@wesleymilan
Copy link

Loopback does not validate relations by default, it should have some parameter on model.json to activate a default forward key validation, just in case of use NoSQL or multiples databases.

In my case I have a MySQL database builded to my legacy application, and I need to migrate gradually to my public API app, but even when it's done, I wan't to keep some integrity between payment informations in my SQL database, and high traffic data in my NoSQL database, so this solution could keep my code cleaned and fast to implement.

Thank you

@aalexgabi
Copy link

Any link on how to validate it manually if this cannot be done automatically? I found this http://stackoverflow.com/questions/26785576/loopback-validation-on-properties-whos-types-are-other-models-version-2-xx but I'm looking for something that only validates the presence of the other model.

@aalexgabi
Copy link

I've found a simpler way: strongloop/loopback#505, it only checks the presence of an id but not that the object actually exists.

@jesusbv
Copy link

jesusbv commented Oct 3, 2016

@wesleymilan and @aalexgabi
I'm having same issue (loopback-connector in my case, should work fine),
and I got a workaround (while I try to figure out a solution in this package, as @aalexgabi asked for a manual solution) hope it helps for your case:

let's say we have User model and Role model and
we have a through model 'User-Role' so, for every new /UserRole
we would like to validate that { userId: whavs, roleId: whatever } exists
or are valid rows in their tables.

in a mixins file we could have this:

'use strict'

let _ = require('lodash');

let checkIntegrityThroughModel = function(ctx, next) {

  if (ctx.instance) {
    let relations = ctx.Model.definition.settings.relations;
    // get models from relations and findById w/ val from foreignKey
    // that matches ctx.instance key/val
    let throughInfo = _.map(relations, relation => {
      return { modelName: relation.model, fk: relation.foreignKey };
    });

    // ES6 destructuring
    var [
      { modelName: modelNameA, fk: fkA },
      { modelName: modelNameB, fk: fkB }
    ] = throughInfo;

    let throughModel = ctx.Model;
    let modelA = throughModel.app.models[modelNameA];
    let modelB = throughModel.app.models[modelNameB];
    let valueAId = ctx.instance[fkA];
    let valueBId = ctx.instance[fkB];

    modelA.findById(valueAId).then(function(instanceA) {
      if (instanceA === null) {
        return next('No ' + modelNameA + ' with "' + valueAId + '" id');
      }

      modelB.findById(valueBId).then(function(instanceB) {
        if (instanceB === null) {
          return next('No ' + modelNameB + ' with "' + valueBId + '" id');
        }
        next();
      });
    }).catch(function(err) {
      next(err);
    });
  }
};

module.exports = function(Model, options) {
  /**
    * @api {POST} /ThroughModel/ Create ModelA-ModelB information
    * @apiDescription Before Create a row in the through model, we check
    * both ids exist => integrity
    * This is both fk must be existing rows as pk in their tables
    *
    * @apiName beforeCreate
    * @apiGroup Model
    *
    * @apiParam {String} modelAId ModelA unique ID.
    * @apiParam {String} modelBId ModelB unique ID.
    *
    * @apiParamExample {json} Request-Example (/User-Role):
    *     {
    *       "userId": "490dd640-862e-11e6-9644-b5c34ca53299",
    *       "roleId": "basic",
    *     }
    */
  Model.observe('before save', checkIntegrityThroughModel);
};

This example is for a through model with 2 keys, but this is easily extensible to
a through model with more than 2 keys.

and in the model definition Model.json (we should include this in every model we want the validation)

  "mixins": {
     "NameOfMixinsFile": { /* options */ }
  }

I know it's not the best as we expect to be validated by the connector but in the meantime.
Please, let me know your feedback or anything you come up with.

@nicolaspernoud
Copy link

nicolaspernoud commented May 3, 2017

Elaborating on jesusbv proposal : that is possible by creating and using mixins (sorry for the comments in French)

/common/mixins/BelongsToIntegrityCheck.js

/*Ce mixin permet de vérifier les contraintes d'intégrité (l'objet enfant possède bien un parent)
en vérifiant que les clés étrangères des objets parents existes bien lors de la création d'un objet enfant*/

'use strict'

let _ = require('lodash');

let checkBelongsToIntegrity = function (ctx, next) {
  if (ctx.instance) {
    let relations = ctx.Model.definition.settings.relations;
    let relationsArray = _.map(relations, rel => {
      return { modelName: rel.model, fk: rel.foreignKey, type: rel.type };
    });

    /* On utilise Lodash pour transformer l'objet des relations en Tableau de la forme
      [
        { modelName: 'achat', fk: 'achat_id', type: 'belongsTo' },
        { modelName: 'FED_AGENT', fk: 'agent_rfagent', type: 'belongsTo' }
      ]
    */

    let thisModel = ctx.Model;
    // Le message qui sera renvoyé en cas d'échec de vérification des contraintes d'intégrité
    let message = "";
    // Le tableau des promises correspondant aux requêtes vérifiants les contraintes
    let promiseArray = [];

    relationsArray.forEach(function (relation) {
      if (relation.type == 'belongsTo') {
        let parentModelName = relation.modelName;
        let parentModel = thisModel.app.models[parentModelName];
        let parentId = ctx.instance[relation.fk];

        // On cherche le modèle parent qui correspond à l'id demandé pour le modèle enfant...
        promiseArray.push(parentModel.findById(parentId).then(function (parentInstance) {
          if (parentInstance === null) {
            message += 'No ' + parentModelName + ' with "' + parentId + '" id. ';
          }
        }));

      }
    }
    );

    /* Une fois que toutes les promesses ont été déterminées et conduisent vers un message en cas de non respect de la contrainte d'intégrité,
    on les regroupe dans une promesse commune résolue quand toutes sont résolues et qui renvoit le message en cas de non respect de contrainte */
    Promise.all(promiseArray)
      .then(
      function () {
        next(message);
      }
      , console.error)
      .catch(function (err) {
        next(err);
      });
  }
}

module.exports = function (Model, options) {
  Model.observe('before save', checkBelongsToIntegrity);
};

/common/mixins/HasManyIntegrityCheck.js

/*Ce mixin permet de vérifier les contraintes d'intégrité (si l'objet parent possède des enfants, on ne peut pas le supprimer)
en faisant une requête sur les enfants qui sont rattachés à ce parent*/

'use strict'

let _ = require('lodash');

let checkHasManyIntegrity = function (ctx, next) {
  if (ctx.where) {
    let relations = ctx.Model.definition.settings.relations;
    let relationsArray = _.map(relations, rel => {
      return { modelName: rel.model, fk: rel.foreignKey, type: rel.type };
    });

    /* On utilise Lodash pour transformer l'objet des relations en Tableau de la forme
      [
        { modelName: 'achat', fk: 'achat_id', type: 'belongsTo' },
        { modelName: 'FED_AGENT', fk: 'agent_rfagent', type: 'belongsTo' }
      ]
    */

    let thisModel = ctx.Model;
    // Le message qui sera renvoyé en cas d'échec de vérification des contraintes d'intégrité
    let message = "";
    // Le tableau des promises correspondant aux requêtes vérifiants les contraintes
    let promiseArray = [];

    relationsArray.forEach(function (relation) {
      if (relation.type == 'hasMany') {
        let childrenModelName = relation.modelName;
        let childrenModel = thisModel.app.models[childrenModelName];
        let parentId = ctx.where.id;
        let whereObject = {};
        whereObject[relation.fk] = ctx.where.id;
        // On cherche les enfants éventuels
        promiseArray.push(childrenModel.find({
          where: whereObject
        }).then(function (data) {
          if (data.length > 0) { // Si il y a des enfants, on renvoit une erreur
            message += 'This "' + thisModel.modelName + '" has "' + childrenModelName + '", and can\'t be deleted. ';
          }
        }));

      }
    }
    );

    /* Une fois que toutes les promesses ont été déterminées et conduisent vers un message en cas de non respect de la contrainte d'intégrité,
    on les regroupe dans une promesse commune résolue quand toutes sont résolues et qui renvoit le message en cas de non respect de contrainte */
    Promise.all(promiseArray)
      .then(
      function () {
        next(message);
      }
      , console.error)
      .catch(function (err) {
        next(err);
      });
  }
}

module.exports = function (Model, options) {
  Model.observe('before delete', checkHasManyIntegrity);
};

And in your models :
Parent Model :

  "mixins": {
    "HasManyIntegrityCheck": {}
  },

Children Model :

  "mixins": {
    "BelongsToIntegrityCheck": {}
  },

@stale stale bot added the stale label Sep 9, 2017
@stale
Copy link

stale bot commented Sep 9, 2017

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@emilio-martinez
Copy link

I think this should be kept alive.

@stale stale bot removed the stale label Sep 16, 2017
@stale
Copy link

stale bot commented Nov 15, 2017

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 15, 2017
@stale
Copy link

stale bot commented Nov 29, 2017

This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.

@stale stale bot closed this as completed Nov 29, 2017
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

9 participants