web-dev-qa-db-fra.com

Modèle Sails.js - insérer ou mettre à jour des enregistrements

J'ai essayé Sails.js et j'écris une application qui importe des données à partir d'une API tierce et qui enregistre dans une table MySQL. En gros, j'essaie de synchroniser les données avec mon application pour une analyse plus poussée, de mettre à jour mes enregistrements ou de créer de nouveaux enregistrements si nécessaire.

J'ai parcouru l'API de Sails et je vois des méthodes pour trouver, créer et mettre à jour des enregistrements, mais aucune méthode intégrée pour insérer/mettre à jour des enregistrements en fonction de la situation. Ai-je oublié quelque chose ou devrai-je le mettre en œuvre moi-même?

Si je dois implémenter cela moi-même, quelqu'un connaît-il un bon modèle de conception pour l'insertion/la mise à jour?

Voici à quoi cela pourrait ressembler…

_.each(importedRecords, function(record){
  MyModel.find({id: record.id}).exec(function findCB(err, found){
    if(found.length){
      MyModel.update(record.id, task).exec(function(err, updated){
        if(err) { //returns if an error has occured, ie id doesn't exist.
          console.log(err);
        } else {
          console.log('Updated MyModel record '+updated[0].name);
        }
      });
    }else{
       MyModel.create(record).exec(function(err, created){
         if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
           console.log(err);
         } else {
           console.log('Created client record '+created.name);
         }
       });
     }
   });
 });

Est-ce que je me dirige dans la bonne direction ou existe-t-il une solution plus élégante?

De plus, je traite beaucoup de modèles différents dans cette application, ce qui impliquerait de recréer ce bloc de code dans chacun de mes modèles. Est-il possible d'étendre l'objet de base Model afin d'ajouter cette fonctionnalité à tous les modèles?.

Merci John

9
Critical Mash

J'ai réécrit le code Critical Mash, donc moins de code et plus générique. Vous pouvez maintenant appeler updateOrCreate de la même façon que vous appelez findOrCreate. Et ça ressemble à ça:

module.exports.models = {
    updateOrCreate: function(criteria, values){
        var self = this; // reference for use by callbacks
        // If no values were specified, use criteria
        if (!values) values = criteria.where ? criteria.where : criteria;

        return this.findOne(criteria).then(function (result){
          if(result){
            return self.update(criteria, values);
          }else{
            return self.create(values);
          }
        });
    }
};

Ainsi, vous pouvez écrire les critères de la même manière. nul besoin de travailler sur la clé, et le code est tellement plus simple.

17
Stas Arshanski

Sails 0.10 a findOrCreate(criteria, attributes, callback), voir Sails Docs .

criteria est le critère de recherche du bit "trouver" (même syntaxe que find ()).

attributes est la donnée utilisée si elle n’est pas trouvée pour le bit "create" (même syntaxe que create ()).

Voici un exemple:

MyModel.findOrCreate({name:'Walter'},{name:'Jessie'}, function (err, record){ console.log('What\'s cookin\' '+record.name+'?');

Notez également qu'il existe d'autres méthodes de requête composites documentées dans le référentiel Waterline (voir les exemples dans tests ) et dans la documentation Waterline :

Chacune des méthodes de base suivantes est disponible par défaut sur une instance de Collection:

  • findOne
  • trouver
  • créer
  • mettre à jour
  • détruire
  • compter

En outre, vous disposez également des méthodes d'assistance suivantes:

  • créerChaque
  • findOrCreateEach * <- Ressemble à ce dont vous avez besoin (astuce: utilisez des tableaux de critères/attributs) *
  • findOrCreate
  • findOneLike
  • trouver comme
  • commence avec
  • se termine par
  • contient

En fonction de vos attributs de collection, vous disposez également de détecteurs dynamiques. Donc, étant donné un attribut de nom, les requêtes suivantes seront disponibles:

  • findOneByName
  • findOneByNameIn
  • findOneByNameLike
  • findByName
  • findByNameIn
  • findByNameLike
  • countByName
  • countByNameIn
  • countByNameLike
  • nameStartsWith
  • nameEndsWith
  • nomContient

Pour ce qui est d’ignorer quelque chose, eh bien, c’est là mais ce n’est pas encore dans la documentation principale de Sails, donc la réponse est oui et non, alors ne vous inquiétez pas :)

10
Kenneth Benjamin

Votre solution est la bonne. Il n'y a pas d'autre moyen de faire cela avec waterline (l'ORM de sails.js). Mais plusieurs bases de données ont des fonctions pour ce cas:

MySQL

REPLACE INTO table SET id = 42, foo = 'bar'; (avec une clé primaire ou unique. Très merdique si vous utilisez auto_increment ;-)

Dans Waterline, vous pouvez utiliser le modèle. query () - Function pour exécuter du code SQL direct (voir: http://sailsjs.org/#/documentation/reference/waterline/models/query.html )

MongoDB

db.collection.update(
  <query>,
  <update>,
  { upsert: true }
)

L'indicateur upsert signifie: Si vous ne pouvez pas le mettre à jour car vous n'avez rien trouvé dans la requête, créez cet élément!

Dans Waterline, vous pouvez utiliser la fonction Model. native () pour exécuter des commandes directes mongoDB (voir: http://sailsjs.org/#/documentation/reference/waterline/models/native. html )

Conclusion

Vous avez besoin d'une exécution rapide (et bien sûr si vous avez beaucoup de requêtes), je vous conseillerais d'utiliser les fonctions natives/sql. Mais en général, je suis vraiment fan de la flexibilité d'un système ORM et chaque fois que vous utilisez des fonctions spécifiques à une base de données, il est difficile à gérer.

3
mdunisch

Merci user3351722, je préfère également utiliser le système ORM. Je viens d'essayer d'implémenter la solution ci-dessus en tant que méthode de modèle générale. (Basé sur Hériter des attributs et des fonctions de cycle de vie des modèles Sails.js ).

J'ai modifié config/models.js et ajouté une nouvelle fonction insertOrUpdate qui prend le nom de la colonne d'index, les données que je veux insérer ou mettre à jour et une fonction de rappel.

module.exports.models = {
  insertOrUpdate: function(key, record, CB){
    var self = this; // reference for use by callbacks
    var where = {};
    where[key] = record[key]; // keys differ by model
    this.find(where).exec(function findCB(err, found){
      if(err){
        CB(err, false);
      }
      // did we find an existing record?
      if(found && found.length){
        self.update(record[key], record).exec(function(err, updated){
          if(err) { //returns if an error has occured, ie id doesn't exist.
            CB(err, false);
          } else {
            CB(false, found[0]);
          }
        });
      }else{
        self.create(record).exec(function(err, created){
          if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
            CB(err, false);
          } else {
            CB(false, created);
          }
        });
      }
    });
  }
};

Cela ne fonctionnerait qu'avec des tables/collections qui ont un index. Je ne sais pas comment faire l'introspection du nom de clé d'un modèle dans waterline, je passe donc dans le nom du champ sous forme de chaîne.

Voici comment utiliser la méthode dans un contrôleur…

_.each(clients, function(client){
  Client.insertOrUpdate('client_id', client, function(err, updated){
    if(err) { //returns if an error has occured, ie invoice_id doesn't exist.
      sails.log(err);
    } else {
      sails.log('insertOrUpdate client record ', updated.organization); //+updated[0].name
    }
  });
});

J'ai essayé cette méthode avec trois modèles différents et jusqu'à présent, tout va bien. Ce sont toutes des tables MySQL et les modèles ont tous un index défini. Votre kilométrage peut être très différent si vous utilisez un datastore différent.

Si quelqu'un voit un moyen d'améliorer cela, merci de nous le faire savoir.

2
Critical Mash

Voici comment je le fais: j'étend le fichier config/models.js pour inclure la fonctionnalité et il vérifie si l'adaptateur dispose des méthodes correctes. Vous pouvez l'appeler comme une promesse ou normalement.

    var normalize = require('sails/node_modules/waterline/lib/waterline/utils/normalize');
    var hasOwnProperty = require('sails/node_modules/waterline/lib/waterline/utils/helpers').object.hasOwnProperty;
    var defer = require('sails/node_modules/waterline/lib/waterline/utils/defer');
    var noop = function() {};

module.exports.models = {

    /**
     * [updateOrCreate description]
     * @param  {[type]}   criteria [description]
     * @param  {[type]}   values   [description]
     * @param  {Function} cb       [description]
     * @return {[type]}            [description]
    */

    updateOrCreate: function (criteria, values, cb) {
        var self = this; 
        var deferred;

        // Normalize Arguments
        if(typeof cb !== 'function') {
           deferred = defer();
        }
        cb = cb || noop;

        criteria = normalize.criteria(criteria);

        if (criteria === false) {
            if(deferred) {
                deferred.resolve(null);
            }
            return cb(null, []);
        }
        else if(!criteria) {
            if(deferred) {
                deferred.reject(new Error('No criteria or id specified!'));
            }
            return cb(new Error('No criteria or id specified!'));
        }

        // Build Default Error Message
        var errFind = 'No find() method defined in adapter!';
        var errUpdate = 'No update() method defined in adapter!';
        var errCreate = 'No create() method defined in adapter!';

        // Find the connection to run this on
        if(!hasOwnProperty(self.adapter.dictionary, 'find')){
            if(deferred) {
                deferred.reject(errFind);
            }
            return cb(new Error(errFind));
        }
        if(!hasOwnProperty(self.adapter.dictionary, 'update')){ 
            if(deferred) {
                deferred.reject(errUpdate);
            }
            return cb(new Error(errUpdate));
        }
        if(!hasOwnProperty(self.adapter.dictionary, 'create')) {
            if(deferred) {
                deferred.reject(errCreate);
            }
            return cb(new Error(errCreate));
        }

        var connNameFind = self.adapter.dictionary.find;
        var adapterFind = self.adapter.connections[connNameFind]._adapter;

        var connNameUpdate = self.adapter.dictionary.update;
        var adapterUpdate = self.adapter.connections[connNameUpdate]._adapter;

        var connNameCreate = self.adapter.dictionary.create;
        var adapterCreate = self.adapter.connections[connNameCreate]._adapter;

        adapterFind.find(connNameFind, self.adapter.collection, criteria, normalize.callback(function before (err, results){

            if (err) {
                if(deferred) {
                    deferred.reject(err);
                }
                return cb(err);
            }

            if(results && results.length > 0){
                adapterUpdate.update(connNameUpdate, self.adapter.collection, criteria, values, normalize.callback(function afterwards (err, updatedRecords) {
                    if (err) {
                        if(deferred) {
                            deferred.reject(err);
                        }
                        return cb(err);
                    }
                    deferred.resolve(updatedRecords[0]);
                    return cb(null, updatedRecords[0]);
                }));
            }else{
                adapterCreate.create(connNameCreate, self.adapter.collection, values, normalize.callback(function afterwards (err, createdRecord) {
                    if (err) {
                        if(deferred) {
                            deferred.reject(err);
                        }
                        return cb(err);
                    }
                    deferred.resolve(createdRecord);
                    return cb(null, createdRecord);
                }));
            }
        }));

        if(deferred) {
            return deferred.promise;
        }
    }
}
0
scott