javascriptormsails.jswaterlinesails-mongo

SailsJS / Waterline ORM : Update multiple entries using only one query


I'm currently using SailsJS framework, and i'm trying to update an array of elements using only one query in my MongoDB database.

Here is what I'm doing but records are not updated..

Incoming JSON:

{
  "cars": [
    {
      "id": "5cb5cbd5c395a01b4c9d86da",
      "latitude": "-5",
      "longitude": "551"
    },
    {
      "id": "5cb5cbd7c395a01b4c9d86db",
      "latitude": "-4",
      "longitude": "4421",
    }
  ]
}

Controller:

async setLocationOfCars(req, res) {

    try {

        sails.log.info(controllerName + "::" + req.options.action + "() - called");

        const carsLocationArray = req.body.cars;

        let response = CarService.setLocationOfCars(carsLocationArray);
        switch (response.status) {
            case constants.RESOURCE_SUCCESSFULLY_UPDATED :
                return HttpResponseService.json(200, res, constants.RESOURCE_SUCCESSFULLY_UPDATED, response.data);
            default:
                return HttpResponseService.internalServerError(res, response);
        }

    } catch(err) {
        return HttpResponseService.internalServerError(res, err);
    }

}

Service:

async setLocationOfCars(cars) {

    try {

        const arrayOfIdsUpdated = [];
        _.forEach(cars, async function(car){

            let attributesToUpdate = {};
            if (car.hasOwnProperty("latitude")) {
                attributesToUpdate.latitude = car.latitude;
            }
            if (car.hasOwnProperty("longitude")) {
                attributesToUpdate.longitude = car.longitude;
            }

            await Car.updateOne({
                id: car.id
            }).set(attributesToUpdate);

            arrayOfIdsUpdated.push(gateway.id)

        });

        return {
            status: constants.RESOURCE_SUCCESSFULLY_UPDATED,
            data  : arrayOfIdsUpdated
        };

    } catch (err) {
        return {
            status : constants.DATABASE_ERROR,
            name   : err.name ? err.name      : "",
            message: err.message ? err.message: "",
            stack  : err.stack ? err.stack    : "",
            code   : err.code ? err.code      : "",
        };
    }

}

Solution

  • In your Controller

    Make sure you are awaiting the response from your service.

    let response = await CarService.setLocationOfCars(carsLocationArray);
    

    In your "Service"

    I'd probably replace _.forEach with a regular for loop. In your case something like the following, ensuring that gateway.id is actually defined & the value you want to return to the calling code (there's no reference to gateway in your question).

    for (let car of cars) {
      let attributesToUpdate = {};
      if (car.hasOwnProperty("latitude")) {
        attributesToUpdate.latitude = car.latitude;
      }
      if (car.hasOwnProperty("longitude")) {
        attributesToUpdate.longitude = car.longitude;
      }
    
      await Car.updateOne({
        id: car.id
      }).set(attributesToUpdate);
    
    
      // Where does gateway.id come from?
      arrayOfIdsUpdated.push(gateway.id)
    }
    

    That said, this will perform cars.length amount of database queries, not "using only one query".

    But why not _.forEach?

    Using an async callback inside forEach like doesn't look like it will do what you probably want it to do. It's likely that your arrayOfIdsUpdated will be returned to your controller code empty, before any of the calls to updateOne run.

    Take this for example:

    const _ = require('lodash');
    
    function doNothing() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 0)
      });
    }
    
    var foos = [1, 2, 3, 4, 5, 6]
    _.forEach(foos, async (foo) => {
      await doNothing();
      console.log(`Finished #${foo}`);
    });
    
    
    console.log('Done!')
    

    Running it gives the output

    Done!
    Finished #1
    Finished #2
    Finished #3
    Finished #4
    Finished #5
    Finished #6
    

    Note how the "Done!" gets logged to the console before the stuff awaiting a response in the forEach callback.