Mongoose: insert one collection depenting on an other with promises - mongodb

I need to insert records in two collections. The second collection stores the ID of records of the first collection. It is a 1:m (fisrt:second) situation. The trigger is the second collection:
If a record for the second collection needs to be stored
check if there is already a fitting record in the first collection
if not: then save one in the first collection
store the second collection
save the id of the record of the first collection in the second collection
The following example seems to fullfill these steps. But I have the promises only half way.
How can this be done in a better "promised" way?
saveObjects(name: String, objects: Array<IObject>){
var promise = FirstModel.findOne({Name : name}).exec();
promise.then(function(res1){
if (!res1){
var la = new FirstModel();
la.Name = name;
la.save(function(err){
if (err) throw err;
})
}
}).error(function(err){
throw err;
})
objects.forEach(function(obj) {
FirstModel.findOne({Name : name},'_id',function(err, res2){
if (err) throw err;
var vo = new SecondModel();
vo.Name = name;
vo.FistID = res2._id;
vo.save(function(err){
if (err) throw err;
});
});
});
}

I'm gona assume here that you're using bluebird or another promise equivalent set up.
var Promise = require('bluebird');
saveObjects(name: String, objects: Array < IObject > ) {
// Get the first model
return FirstModel
.findOne({
Name: name
})
.exec()
.then(function (res1) {
if (!res1) {
var la = new FirstModel();
la.Name = name;
return la.save() // We return the save operation on the model, save() returns a promise
}
// We just return the model normally so it passes down the chain.
return res1;
})
.then(function (res1) {
// Here we use Promise.all() method on bluebird which accepts an Array
// of promises, we create a promise from the objects array by using Array.map() which
// goes through every object in the array and creates a new array.
return Promise
.all(objects.map(function (obj) {
// We go through each object in objects and create a new
// model and return the save() method of each model, this
// creates a array of promises which are resolved when each
// all model has been saved.
var vo = new SecondModel();
vo.Name = name;
vo.FistID = res1._id;
return vo.save();
}));
})
.then(function (models) {
// here we have all the models we just saved in an array called models,
// do what you want with it.
})
.error(function (err) {
throw err;
})
}
For more info see documentation on Array.map() here and Promise.all() here

Related

Order of save() and find() in NodeJS with MongoDB

I'm trying to create a new record in my MongoDB ("thisPlayer") and save it to my database, then find all records in my database (including the new one) and render them.
I am having trouble understanding why my save() function actually occurs after my find() function. When this code executes, the find() function does not include my new thisPlayer record. However, after the find() runs, the save occurs -- the record is saved to the database AFTER the find() ran.
Thanks in advance!
const playerNumber = async function countPlayers() {
return new Promise((resolve, reject) => {
Player.count(function(err, numOfDocs) {
err ? reject(err) : resolve(numOfDocs);
console.log('I have '+numOfDocs+' documents in my collection');
});
});
}
async function playerProfile() {
var count = await playerNumber();
console.log("count already in db: "+ count);
if (count===0) {
teamCaptain=1;
} else {teamCaptain=0};
count++;
const thisPlayer = new Player({
playerNum: count,
playerName: Name,
});
thisPlayer.save();
Player.find({}, function(err, playaz){
var playerOne;
if (playaz.length > 0) {
playerOne = playaz[0].playerName;
} else {
playerOne = "";
}
res.renderPjax("leavetakings",
{player1: "1: " + playerOne}
);
});
}
playerProfile();
You need to use await
for example.
await Player.find({})

How to check if value already exists in the data received from api before inserting it into db

I am having hard times trying to write data received from a api to db.
I successfully got data and then have to write it to db. The point is to check whether the quote is already exists in my collection.
The problem I am dealing with is that every value gets inserted in my collection, not regarding if it exists or not.
const { MongoClient } = require('mongodb')
const mongoUrl = 'mongodb://localhost/kanye_quotes'
async function connectToDb() {
const client = new MongoClient(mongoUrl, { useNewUrlParser: true })
await client.connect()
db = client.db()
}
async function addQuote(data) {
await connectToDb()
try {
const collection = db.collection('quotes')
let quotes = [];
quotes = await collection.find({}).toArray()
if (quotes = []) { // I added this piece of code because if not check for [], no values will be inserted
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
return
})
}
quotes.forEach(quote => {
if (quote.quote !== data.quote) { // I compare received data with data in collection, it actually works fine(the comparison works as it supposed to)
collection.insertOne(data, (err, result) => {
if (err) {
return
}
console.log(result.insertedId);
})
} else console.log('repeated value found'); // repeated value gets inserted. Why?
})
}
catch (err) {
console.log(err)
}
}
Hi it's probably better to set unique: true indexing on your schema. That way you won't have duplicated values.

Loopback - How to use bulkUpdate method

I'm using Loopback v3 currently and wanted to upsert many records at once in a collection; I found this method bulkUpsert from the documentation (http://apidocs.loopback.io/loopback/#persistedmodel-bulkupdate) but I couldn't figure out how to make it work.
How can I create the updates array from createUpdates() method as mentioned in the documentation? Can anyone help me with a simple example of using this method?
There is an alternative way to do the bulkUpdate method, found in Stackoverflow MongoDB aggregation on Loopback
A mixin can be easily created and reused over the Models. My sample code of bulkUpsert mixin is below:
Model.bulkUpsert = function(body, cb) {
try {
Model.getDataSource().connector.connect(async (err, db) => {
if (err) {
return cb(err);
}
// Define variable to hold the description of the first set of validation errors found
let validationErrors = '';
// Build array of updateOne objects used for MongoDB connector's bulkWrite method
const updateOneArray = [];
// Loop through all body content and stop the loop if a validation error is found
const hasError = body.some(row => {
// Check if it is a valid model instance
const instance = new Model(row);
if (!instance.isValid()) {
// A validation error has been found
validationErrors = JSON.stringify(instance.errors);
// By returning true we stop/break the loop
return true;
}
// Remove ID in the row
const data = JSON.stringify(row);
delete data.id;
// Push into the update array
updateOneArray.push({
updateOne: {
filter: { _id: row.id },
update: { $set: Object.assign({ _id: row.id }, data) },
upsert: true
}
});
// No validation error found
return false;
});
// Check if a validation error was found while looping through the body content
if (hasError) {
return cb(new Error(validationErrors));
}
// No validation data error was found
// Get database collection for model
const collection = db.collection(Model.name);
// Execute Bulk operation
return collection.bulkWrite(updateOneArray, {}, (err, res) => {
// Check if the process failed
if (err) {
console.err('The bulk upsert finished unsuccessfully', err);
return cb(err);
}
// Check if there were errors updating any record
if (res.hasWriteErrors()) {
console.error(`The bulk upsert had ${res.getWriteErrorCount()} errors`, res.getWriteErrors());
}
// Finished successfully, return result
return cb(null, {
received: body.length,
handled: res.upsertedCount + res.insertedCount + res.matchedCount
});
});
});
}
catch (err) {
console.error('A critical error occurred while doing bulk upsert', err);
return cb(err);
}
return null;
};
Ref: Mongodb query documentation

sails.js the return object from service is undefined when using a find query

I created a service called AppService.
Its function getUserPostionOptions is supposed to return an object:
getUserPostionOptions: function (user) {
// PositionOptions.findOne({id:'53f218deed17760200778cfe'}).exec(function (err, positionOptions) {
var positionDirectionsOptions = [1,2,3];
var positionLengthsOptions = [4,5,6];
var object = {
directions:positionDirectionsOptions,
lengths:positionLengthsOptions
};
return object;
// });
}
This works, in my controller positionOptions gets populated correctly:
var positionOptions = AppService.getUserPostionOptions(user);
However, when I uncomment the find query the item is found but the object returns undefined.
Thank in advance for your help
SailsJs ORM (and almost NodeJs database querying methods) uses non-blocking mechanism via callback function. So you have to change your code into:
getUserPostionOptions: function (user, callback) {
PositionOptions.findOne({id:'53f218deed17760200778cfe'}).exec(function (err, positionOptions) {
var positionDirectionsOptions = [1,2,3];
var positionLengthsOptions = [4,5,6];
var object = {
directions:positionDirectionsOptions,
lengths:positionLengthsOptions
};
callback(null, object); // null indicates that your method has no error
});
}
Then just use it:
AppService.getUserPostionOptions(user, function(err, options) {
if (!err) {
sails.log.info("Here is your received data:");
sails.log.info(options);
}
});

SailsJS how to write non-async Model.CRUD functions?

I've a Parent and Child models in my app.
Parent.create receives parent_name and an array of children that I want to add to the Parent model, the following flow describes the function:
1) Create parent object
2) Create all children
3) Save parent with updated children array
The problem is that Parent.create is probably async, and the 'created_children' array when saved to parent is empty (because it doesn't wait until the Parent.create finishes.
How can I make Model.create dependent (or synchronic)?
See the code below (I commented the buggy part //BUG: EMPTY ARRAY!!!!!!!!!!):
create: function(req, res, next) {
var childrenInput = req.param('children');
var parentObj = {
name: req.param('parent_name')
};
Parent.create(parentObj, function parentCreated(err, parent) {
if (err) {
return res.redirect('/parent/new');
}
// assign children
var created_children = new Array();
for(var i=0; i < childrenInput.length; i++) {
var childObj = {
name: parentObj.childrenInput[i],
parent_id: parent.id
};
// create child
Child.create(childObj, function childCreated(err, child) {
if (err) {
for(var j=0; j < created_children.length; j++) {
Child.destroy(created_children[j].id, function childDestroyed(err) {
if (err)
{
// BIG ERROR
return next(err);
}
});
}
return res.redirect('/parent/new');
}
// add created child
created_children.push(child.id);
}) // end of Child.create;
} // end of for;
// save created children to parent
parent.children = created_children.slice();
parent.save(function(err, c) {
if (err)
{
// TODO: FUNCTION TO DESTROY ALL CHILDREN
return next(err);
}
});
return res.redirect('/parent/show/' + parent.id);
});
},
Parent model
module.exports = {
schema: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
children: {
type: 'array',
defaultsTo: []
}
}
};
Performing asynchronous operations on an array can be a real pain. I'd suggest using a module like async, which provides synchronous-like functionality for asynchronous code. You could then rewrite your code as:
Parent.create(parentObj, function parentCreated(err, parent) {
if (err) {
return res.redirect('/parent/new');
}
// You only really need this for error handling...
var created_children_ids = new Array();
// Create an array of child instances from the array of child data
async.map(
// Array to iterate over
childrenInput,
// Iterator function
function(childObj, callback) {
Child.create(childObj, function childCreated(err, child) {
if (err) {return callback(err);}
created_children_ids.push(child.id);
// 'null' indicates no error
return callback(null, child);
});
},
// Callback for when loop is finished.
// If any run of the iterator function resulted in the
// callback being called with an error, it will immediately
// exit the loop and call this function. Otherwise the function
// is called when the loop is finished, and "results" contains
// the result of the mapping operation
function (err, results) {
if (err) {return destroyChildren();}
// Save the children to the parent
parent.children = results;
parent.save(function(err, c) {
if (err) {return destroyChildren();}
return res.redirect('/parent/show/' + parent.id);
});
function destroyChildren(err) {
Child.destroy({id: created_children_ids}).exec(function() {
// Respond with an error
return res.serverError(err);
});
}
}
);
});
Note that if you're using Sails v0.10, you can use actual associations to bind the parent and child records, and use parent.children.add(childObj) (which is a synchronous operation) in a regular loop prior to calling parent.save(). Calling .add with an object will cause that model to be created during the save operation.