Creasixtine
Creasixtine

Reputation: 760

Sails' afterCreate(obj): object is not updated

This post has 3 updates and may be long. Please see Original post section.

Update 3 - Full Annonce code

Note that this is my current code. I have updated the url() method and commented everything inside afterCreate(). But the code is still the same. (sorry, it's just copy-and-paste, so commentaries are in french language).

/**
* Annonce.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {
  tableName: 'annonce',
  autoPK: true,
  attributes: {
    qte_commande: {
        type: 'integer',
        defaultsTo: 1,
        required: true
    },
    date_mise_en_ligne: 'date',
    taille_echantillon: {
        type: 'string',
        enum: ['P', 'M', 'G'] // TODO : entité
    },
    qte_echantillons_ini: {
        type: 'integer',
        required: true,
        defaultsTo: 1
    },
    qte_echantillons: {
        type: 'integer'
    },
    titre_annonce: {
        type: 'text',
        required: true
    },
    texte_annonce_md: {
        type: 'text',
        required: true
    },
    slug: {
        type: 'string',
        unique: true
    },
    prix: {
      type: 'float',
      required: true
    },

    /*
     * Associations
     */
    utilisateur: {
      model: 'utilisateur'
    },
    variete: {
      model: 'variete'
    },
    echantillons: {
      collection: 'echantillon',
      via: 'annonce'
    },
    /*
     * Méthode d'instance
     */
    url: function(bool_url_courte) {

      // Si la variété n'est pas accessible, on ne peut pas connaître son URL.
      // Dans ce cas il y a un problème, mais on peut toujours donner l'url courte.
      if (!this.variete) {
          sails.log.warn(new Error('Variété non accessible depuis Annonce.url().'));
          bool_url_courte = true;
      }

      // Si le slug n'existe pas, on le génère à la volée.
      if (!this.slug) {
          var slug = Variete.genSlug(this.titre_annonce);

          // On va remplir le champ slug, qui doit être unique
          sails.log.verbose('Après la création d\'annonce : ' + this.id);
          sails.log.verbose('Le slug vaut : ' + slug);
          // On va donc rechercher une annonce qui comporte
          // déjà un tel slug.
          // Si on ne trouve pas, on pourra faire 
          // attrs.slug = slug;
          // Si on trouve quelque chose, alors il faudra allouer
          // attrs.slug = slug + '_' + attrs.id (ce qui est le seul 
          // moyen facile pour obtenir une chaîne unique).

          // On copie this vers ann_orig car this passe à undefined dans
          // Annonce.findOne().
          var ann_orig = this;
          Annonce.findOne({slug: slug}, function(err1, ann) {
              if (err1) {
                  sails.log.error(err1);
                  // Erreur de communication avec la BDD.
                  // On renvoie vers /erreur
                  return '/erreur';
              } else if (!ann) {
                  // Chouette, c'est la première annonce avec ce titre.
                  sails.log.verbose('C\'est la première annonce de ce nom : ' + ann_orig.titre_annonce);
                  ann_orig.slug = slug;
                  ann_orig.save(function(err2) {
                      if (err2) {
                          sails.log.error('Erreur durant la sauvegarde de annonce.slug : ' + ann_orig.id + ' / ' + ann_orig.slug, err2);
                          // On renvoie quand-même vers /erreur
                          return '/erreur';
                      } else {
                          sails.log.info('Annonce /a/' + ann_orig.id + ' <---> slug = ' + ann_orig.slug);
                      }
                  });
              } else {
                  // Malheur, on a trouvé quelque chose, donc ce 
                  // slug n'est pas unique.
                  sails.log.verbose('Il existe une autre annonce avec ce titre : ' + ann_orig.titre_annonce);
                  ann_orig.slug = slug + '_' + ann_orig.id;
                  ann_orig.save(function(err4) {
                      if (err4) {
                          sails.log.error('Erreur durant la sauvegarde de annonce.slug : ' + ann_orig.id + ' / ' + ann_orig.slug, err4);
                      } else {
                          sails.log.info('Annonce /a/' + ann_orig.id + ' <---> slug = ' + ann_orig.slug);
                      }
                  });
              }
          });
      } // fin génération du slug.

      // Si la variété n'a pas de slug ou si on veut une url courte
      // alors on renvoie /a/:id
      // Sinon l'url est /annonce/variete.slug/annonce.slug
      // 
      // TODO IMPORTANT : Lors du premier appel à cette fonction, il y a 
      // un update en base de donnée fait de manière asynchrone. Du coup,
      // this.slug n'est peut-être pas encore généré. Il le sera la prochaine fois.
      if (!this.slug || !this.variete.slug || bool_url_courte){
        return '/a/' + this.id;
      } else {
        // L'annonce a ce qu'il faut
        return '/annonce/' + this.variete.slug + '/' + this.slug;
      }
    }
  },

  /*
   * beforeCreate() : on met à jour :
   * - qte_echantillons
   * 
   * TODO IMPORTANT : le slug n'existe pas encore. En effet, slug doit être unique,
   * or pour être unique il faut pouvoir s'assurer que titre_annonce est unique.
   * Si ce n'est pas le cas, on rajoute '_' + this.id.
   * Malheureusement, dans beforeCreate() l'id n'est pas encore connu.
   * Dans afterCreate() on ne peut plus mettre à jour l'objet, donc c'est dans
   * la méthode d'instance url() qu'on en fait la mise à jour.
   * 
   * C'est une rustine.
   */
  beforeCreate: function(attrs, next) {
    // Au début, la qté d'échantillons est la même
    // que la qté initiale.
    attrs.qte_echantillons = attrs.qte_echantillons_ini;
    next();
  },
  afterCreate: function(attrs, next) {
    next();
/*
    // On va remplir le champ slug, qui doit être unique
    var slug = attrs.titre_annonce.replace(/\s+/g, '-').toLowerCase();
    sails.log.verbose('Après la création d\'annonce : ' + attrs.id);
    sails.log.verbose('Le slug vaut : ' + slug);
    // On va donc rechercher une annonce qui comporte
    // déjà un tel slug.
    // Si on ne trouve pas, on pourra faire 
    // attrs.slug = slug;
    // Si on trouve quelque chose, alors il faudra allouer
    // attrs.slug = slug + '_' + attrs.id (ce qui est le seul 
    // moyen facile pour obtenir une chaîne unique).
    Annonce.findOne({slug: slug}, function(err1, ann) {
      if (err1) {
        sails.log.error(err1);
        next(err1);
      }
      else if (!ann) {
        // Chouette, c'est la première annonce avec ce titre.
        sails.log.verbose('C\'est la première annonce de ce nom.');
        Annonce.update({id:attrs.id}, {slug:slug}).exec(function(err2, object){
          sails.log.error('Infinite loop ? (1)');
          if (err2) next(err2);
          else {
            sails.log.error('Infinite loop ? (3)');
            next();
          }
        });
        //attrs.slug = slug;
        //next();
      } else {
        // Malheur, on a trouvé quelque chose, donc ce 
        // slug n'est pas unique.
        sails.log.verbose('Il existe une autre annonce avec ce titre.');
        Annonce.update({id:attrs.id}, {slug:  slug + '_' + attrs.id}).exec(function(err4, object){
          sails.log.error('Infinite loop ? (2)');
          if (err4) next(err4);
          else next();
        });
      }
    });
*/    
  },
  beforeUpdate: function (attrs, next) {
    // On ne met pas à jour le slug, qui doit vivre
    // pendant toute la vie de l'annonce.
    next();
  }

};

Update 2 - using Annonce.update() method

You shall see below the code I currently have to update the object inside the afterCreate() section. Note that this doesn't work. The request hangs and I see no Infinite loop ? (*). After checking, I see that the process comes into the // We didn't find any, so .slug is simple line.

Know what? I guess that Model.afterCreate() it is even too late or too early to update the object.

To summerize, my problem is actually simple: I wanted to have a unique slug, and adding the Annonce.id to it was a good way to manage to. This slug is used to generate an url with an instance method named ann.url() (see this discussion).

Now, if I don't misunderstand, in beforeCreate() there is no this.id nor attrs.id inside. This is why I need to make it after pushing the object in database.

I wouldn't like to add a random hash/number to the slug because it would generate an ugly url, and it wouldn't ensure me to get a unique slug (note I could enter into a recursive searching and that could be OK).

Maybe afterCreate() is the wrong place to do this, but the documentation doesn't clearly secify it (once again I am not alone with that true issue due to the implementation or the lack in documentation).

I have thought of updating the slug inside my ann.url() method. This obviously works. But I will choose to use this workaround if I cannot do anything else: this method is called everywhere inside my templates, so it's part of the 20% code that is most often used (this project needs relatively high performance). So I don't want to add one more conditional block in this method.

afterCreate: function(attrs, next) {
  // slug must be unique
  var slug = attrs.titre_annonce.replace(/\s+/g, '-').toLowerCase();
  // Let's search for a classified that would have such a slug
  // If we don't find, we can do 
  // this.slug = slug;
  // If we find sthg, we must get a unique string.
  // this.slug = slug + '_' + this.id
  // This would fit.
  // findOne() <--- because we only want to see if there's sthg
  Annonce.findOne({slug: slug}, function(err1, ann) {
    if (err1) {
      sails.log.error(err1);
      next(err1);
    }
    else if (!ann) {
      // We didn't find any, so .slug is simple
      Annonce.update({id:attrs.id}, {slug: slug}).exec(function(err2, object){
        sails.log.error('Infinite loop ? (1)');
        if (err2) next(err2);
        else {
          sails.log.error('Infinite loop ? (3)');
          next();
        }
      });
    } else {
      // We found sthg, so slug is not unique.
      Annonce.update({id:attrs.id}, {slug: slug + '_' + attrs.id}).exec(function(err3, object){
        sails.log.error('Infinite loop ? (2)');
        if (err3) {
          sails.log.error('SAVE ERROR');
          next(err3);
        } else next()
      });
    }
  });

},

Update 1 - using Annonce.find() method

I have updated my Model.afterCreate() to make fetch the newly created record in database.

afterCreate: function(attrs, next) {
  // slug must be unique
  var slug = attrs.titre_annonce.replace(/\s+/g, '-').toLowerCase();
  // Let's search for a classified that would have such a slug
  // If we don't find, we can do 
  // this.slug = slug;
  // If we find sthg, we must get a unique string.
  // this.slug = slug + '_' + this.id
  // This would fit.
  // findOne() <--- because we only want to see if there's sthg
  Annonce.findOne({slug: slug}, function(err, ann) {
    if (err) {
      sails.log.error(err);
      next(err);
    }
    else if (!ann) {
      // We didn't find any, so .slug is simple
      Annonce.findOne({id:attrs.id}, function(error, object){
        sails.log.error('Infinite loop ? (1)');
        if (error) next(error);
        else {
          sails.log.error('Infinite loop ? (3)');
          object.slug = slug;
          object.save(function (err) {
            sails.log.error('Infinite loop ? (4)');
            next();
          });
        }
      });
    } else {
      // We found sthg, so slug is not unique.
      Annonce.findOne({id:attrs.id}, function(error, object){
        sails.log.error('Infinite loop ? (2)');
        if (error) {
          sails.log.error('SAVE ERROR');
          next(error);
        } else {
          object.slug = slug + '_' + attrs.id;
          sails.log.error('SAVE OK');
          object.save(function (err) {
            next();
          });
        }
      });
    }
  });

},

I get this:

error: Infinite loop ? (1)
error: Infinite loop ? (3)

I don't get any SAVE ERROR nor SAVE OK and the request stucks, I think because we don't enter into save() and no next() is called.

So what's the problem?


Original post

After creating a record in one of my entities, I have to add a new field. I have to do it after because I need the record's ID.

I need to save this and I don't know how to do.

afterCreate: function(obj, next) {

// slug field must be unique
var slug = obj.titre_annonce.replace(/\s+/g, '-').toLowerCase();
// Let's search for a classified that would have such a slug
// If we don't find, we can do 
// this.slug = slug;
// If we find sthg, we must get a unique string.
// this.slug = slug + '_' + this.id
// This would fit.
// findOne() <--- because we only want to see if there's sthg
Annonce.findOne({slug: slug}, function(err, ann) {
   if (err) {
      sails.log.error(err);
      next(err);
    }
    else if (!ann) {
      // We didn't find any, so .slug is simple
      obj.slug = slug;
      obj.save(function(err){
        next();
      });
    } else {
      // We found sthg, so slug is not unique.
      obj.slug = slug + '_' + obj.id;
      obj.save(function (err) {
        next();
      });
    }
  });

Error: obj doesn't have a save() method. But not using .save() ends with no database update.

What do you think?

Upvotes: 1

Views: 2255

Answers (1)

Meeker
Meeker

Reputation: 5979

In your example, "obj" would not have the save method attached. You would need to re-instantiate the object if you want the save method. Assuming "obj" came from "Model"

example:

afterCreate : function(obj,next){
    Model.findOne(obj.id, function(err, objectWithSaveMethod){
    .....        
   } 
 }

Or instead of obj.save() you could

Model.update({slug:slug}, {id:obj.id}, function(err, object){
    if(err) next(err);
    next();
})

Upvotes: 2

Related Questions