Uspert multiple documents with MongoDB/Mongoose - mongodb

Say I have a list of models:
const documents = [{}, {}, {}];
And I want to insert these into the DB, or update them all, but only if a condition is met:
Model.update({isSubscribed: {$ne: false}}, documents, {upsert:true},(err, result) => {
});
The above signature is surely wrong - what I want to do is insert/update the documents, where the condition is met.
There is this Bulk API:
https://docs.mongodb.com/manual/reference/method/Bulk.find.upsert/
but I can't tell if it will work when inserting multiple documents.

Imagine this scenario: We have a list of employees and a form of some sorts to give them all a penalty, at once, not one by one :)
On the backend side, you would have your eg addBulk function. Something like this:
Penalty controller
module.exports = {
addBulk: (req, res) => {
const body = req.body;
for (const item of body) {
Penalty.create(item).exec((err, response) => {
if (err) {
res.serverError(err);
return;
}
});
res.ok('Penalties added successfully');
}
}
Then you'll probably have an API on your frontend that directs to that route and specific function (endpoint):
penaltyApi
import axios from 'axios';
import {baseApiUrl} from '../config';
const penaltyApi = baseApiUrl + 'penalty'
class PenaltyApi {
static addBulk(penalties) {
return axios({
method: 'post',
url: penaltyApi + '/addBulk',
data: penalties
})
}
}
export default PenaltyApi;
...and now let's make a form and some helper functions. I'll be using React for demonstration, but it's all JS by the end of the day, right :)
// Lets first add penalties to our local state:
addPenalty = (event) => {
event.preventDefault();
let penalty = {
amount: this.state.penaltyForm.amount,
unit: this.state.penaltyForm.unit,
date: new Date(),
description: this.state.penaltyForm.description,
employee: this.state.penaltyForm.employee.value
};
this.setState(prevState => ({
penalties: [...prevState.penalties, penalty]
}));
}
Here we are mapping over our formData and returning the value and passing it to our saveBulkEmployees() function
save = () => {
let penaltiesData = Object.assign([], this.state.penalties);
penaltiesData.map(penal => {
penal.employeeId = penal.employee.id;
delete penal.employee;
return penaltiesData;
});
this.saveBulkEmployees(penaltiesData);
}
...and finally, let's save all of them at once to our database using the Bulk API
saveBulkEmployees = (data) => {
PenaltyApi.addBulk(data).then(response => {
this.success();
console.log(response.config.data)
this.resetFormAndPenaltiesList()
}).catch(error => {
console.log('error while adding multiple penalties', error);
throw(error);
})
}
So, the short answer is YES, you can absolutely do that. The longer answer is above :) I hope this was helpful to you. If any questions, please let me know, I'll try to answer them as soon as I can.

Related

Axios/mongodb request, PromiseState stuck on pending, then() part is not called

I'm trying to update my mongodb database in javascript by accessing some documents from the database, changing a specific document and then performing a patch request via axios.
When I get to the patch request I'm able to update the database however the promise is stuck on pending and thus, the then() part of the code is not run.
This is the main structure of the code:
In the first part the documents are requested from the database via axios.get:
function updateDocument(someinputdata){
g = axios.all([axios.get('/getData1),axios.get('/getData2)])
.then(response => {
Data1 = response[0].data;
Data2 = response[1].data;
adjustData(Data1,Data2);
});
}
In the second part a specific document is changed and a patch request is called:
function adjustData(Data1,Data2){
...getting specific document and change value from specific field...
var newRec = {
title: "dummyTitle",
rate: newRateValue
};
promise = axios({
url: '/patch/The Real Title',
method: 'PATCH',
data: newRec,
headers: { "Content-Type": "application/json" }
})
.then(() => {
console.log('I want this text to display but it doesn't')
});
}
If I console.log(promise):
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
On the server side I have this:
router.patch('/patch/:title', (req,res) => {
const updatedPost = Model.updateOne(
{ "title": req.params.title},
{ $set: { "rate" : req.body.rate}},
(err, result) => {
if(err) {
console.log(err);
throw err;
}
})
.then(
console.log('This text is displayed');
)
})
I want to use the first then() part to update some HTML
Why is the patch request stuck on pending (so not fulfilled or rejected)?
I've figured out what my problem was.
I needed to add
res.json({msg: "Your data has been saved"});
to the code on the server side.

Ionic 2 MEAN Application doesn't return updated data on get request

I've been having this weird issue with an application I'm building. Essentially a function is invoked I want to read in a user's current game statistics -Wins, losses, draws etc - I do this using a service which creates an observable and consumes data from my rest api. On first call of this method the data read in is the most current up to date version but after this point I update the document for the user in the database and then when I execute the function again it reads in the original document before the update. However when I check the database the document has in face been updated.
Here is my provider function for consuming the data.
getUser(id) {
if (this.data) {
return Promise.resolve(this.data);
}
return new Promise(resolve => {
this.http.get('https://pitchlife-hearts.herokuapp.com/api/users/' + id)
.map(res => res.json())
.subscribe(data => {
this.data = data;
resolve(this.data);
});
});
}
Here is the call I make in my function.
play(challenger, opponent) {
this.userService.getUser(_id).then((data) => {
this.challenger_account = {
_id: data._id,
points: data.maroon_points,
wins: data.wins,
draws: data.draws,
losses: data.losses
};
Here is my update call.
this.userService.updateUser(this.challenger_account);
Here is my api endpoint call as well although this does work every time I update the data.
app.post('/api/users/update', function (req, res) {
// Update a user
var options = {};
User.update({_id : req.body._id }, {
maroon_points: req.body.points,
wins: req.body.wins,
draws: req.body.draws,
losses: req.body.losses
}, options,
function (err, user) {
if (err)
res.send(err);
res.json(user);
});
});
Any help with this would be hugely appreciated as this is driving me crazy.
When are you updating the this.data property that the getUser(id) { ... } method uses?
Because the first time the getUser(id) {...} method is executed, this.data is null and because of that the http request is made. But after that, the value of this.data is always returned, but if you don't update it manually, it'll be always the first value it was set to.

Take advantage of blueprints / waterline findWhere inside custom controller

I have a bear model and I'm using it with blueprint REST.
// api/models/Bear.js
module.exports = {
attributes: {
name: {
type: 'string',
required: true
}
}
};
I'd like to perform some calculations to bears based on exactly the same criterias as the standard findWhere. Indeed I'd like to be able to request
GET /bear/details
exactly just like I request
GET /bear
So I could find bear details with :
complex query like ?where={}
fields like ?name=
but also sending json in body like {name: ''}
or maybe even using ?limit= etc.
The controller looks like this :
// api/controllers/BearController.js
module.exports = {
getDetails: function (req, res) {
Bear.find().exec(function (err, bears){
if (err) return res.serverError(err);
var bearsDetails = _.map(bears, function(bear) {
return {
id: bear.id,
nameLength: bear.name.length,
reversedName: bear.split('').reverse().join('')
};
});
return res.json(bearsDetails);
});
}
};
And I have a custom route that looks like this
// config/routes.js
module.exports.routes = {
'get /bear/details': 'BearController.getDetails'
}
=> How to automaticaly filter models exactly like in a findWhere request, in a custom controller, without reinventing the wheel ?
Apparently I figured it out myself digging into sails' find() source code. One can use actionUtil's parseCriteria(req). I personaly wrapped it into a service for cleanliness purpose.
Roughly :
api/services/ActionUtilService.js
module.exports = require('../../node_modules/sails/lib/hooks/blueprints/actionUtil');
api/controllers/BearController.js
module.exports = {
getDetails: function (req, res) {
let criteria = ActionUtilService.parseCriteria(req);
Bear.find(criteria).exec(function (err, bears){
if (err) return res.serverError(err);
var bearsDetails = _.map(bears, function(bear) {
return {
id: bear.id,
nameLength: bear.name.length,
reversedName: bear.split('').reverse().join('')
};
});
return res.json(bearsDetails);
});
}
};
For cleanliness I've wrapped it into

meteor how to manage async updates in a loop

I have this loop:
properties.forEach(function(property) {
console.log("property: " + property);
var upsertValues = {};
upsertValues["ID"] = property.ID;
Properties.upsert(upsertValues,
{$set: property},
function(err, nbr) {
if(err)
console.log(err);
else
console.log("upsert successful" + nbr);
});
});
setTimeout(function () {
Fiber(function() {
Meteor.call("removeOldProperties", modification_date);
}).run();
}, 30000)
})
Basically, it updates a bench of documents and at the end, it removes all the once who have not been updated.
I had to use a TimeOut because without that, I removes the documents before their update, as all the Meteor.upsert statements are async.
Is there a better way to do it (without having to use this timeout) ?
Thanks,
Couple thoughts:
upserts are fast, no need for a callback
Fiber is for the server
I don't understand how your upsertValues was a valid query. Is this referring to the document _id? If so, convention is to keep using the name _id, if not, I'd use a more descriptive name. Was this code functioning??
What remains:
var upsertsCompleted = 0;
properties.forEach(function(property) {
Meteor.call("upsertProperties", property, function() {
if (++upsertsCompleted === properties.length) {
Meteor.call("removeOldProperties", modification_date);
}
}
Meteor.methods({
upsertProperties: function (property) {
return Properties.upsert(property.ID, {$set: property});
}
});

How to Use NodeJs without Mongoose

I am getting really good at NodeJs and AngularJs. After building a sizable application, I have come to the conclusion that Mongoose is not for me. I want to use pure MongoDb.
I have done a pretty good job refactoring my server code. I pulled all my functions out of my routes and into controllers. Currently however, my controllers are using Mongoose and they are connected to Mongo via the schema.
Current code:
var express = require('express'),
errMsg = require('../../../utilities/errorMsg'),
MyMongooseSchema = require('./models/myMongooseModel');
module.exports = {
all: function (req, res) {
MyMongooseSchema.find({}).exec(function (err, results) {
if (err) {
....
});
}
res.send(results);
})
}
Again I do not want this. I want the dynamism of MongoDb. I tried this to no avail.
db.js:
function MongoDbConnect(dbName){
var db = 'mongodb://localhost/' + dbName;
MongoClient.connect(db, function (err, db) {
if(err){
console.log('Error connecting to database')
} else {
return db; }
});}
exports.MongoDbConnect = MongoDbConnect;
controller: [that is called from respective router]
var database = require('../../../config/db');
module.exports = {
all: function (req, res) {
var db = database.MongoDbConnect('myDbName');
db.collection('MyCollectionName').find({}, function (err, results) {
if (err) {
return res.status(400).send({
message: errMsg.getErrorMessage(err)
});
}
res.send(results);
})
}}
Obviously, this is not working.
What would be the appropriate way to create a well refactored database connection that any controller can call on, with whatever database the controller needs?
Thank you.
Hey everybody, thanks sooooo much for your help !
var mongoskin = require('mongoskin');
var db = mongoskin.db('mongodb://localhost:27017/myDatabase?auto_reconnect', {safe:true});
db.collection('Menu').find({}).toArray(function(error, menu){
if (error) return next(error);
res.send(menu || [])
})