Loopback - How to use bulkUpdate method - rest

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

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.

.find() returning nothing even when data exists

I've a mongo database with 3 collections for 3 different kind of users as User,Partner,Admin. Whenever a new user of any type signup I'm searching all three collections to check if username and email exist already. I'm trying to achieve this by calling a function as:
function checkAttribute(attr,val,callback){
User.find({attr: val},function(err,user){
if(err){
console.log(err);
}else{
if(user.length === 0){
Partner.find({attr: val},function(err,partner){
if(err){
console.log(err);
}else{
if(partner.length === 0){
Admin.find({attr: val},function(err,admin){
if(err){
console.log(err);
}else{
if(admin.length === 0){
return callback(null,true);
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
};
Calling function line:
checkAttribute("username",newUser.username,function(error,response){
.......
});
But this is not working as it returns true always even when users with passed username/email exists already. I am unable to find the problem. Any one knows why this is happening?
Thanks in advance.
Since you are passing in the attribute as a variable in the function parameters, the query document
{ attr: val } is an object with the key "attr", not the dynamic attribute you pass in.
To fix this, you need to use computed property names in your query object as
{ [attr]: val }
Also, the function can use async/await pattern to be more readable and for the purpose of finding if a document exist findOne does the job so
well as it returns a document if it exists and null otherwise.
So your function can be refactored as
async function checkAttribute(attr, val, callback) {
try {
const query = { [attr]: val }
const user = await User.findOne(query).exec()
const partner = await Partner.findOne(query).exec()
const admin = await Admin.findOne(query).exec()
const found = (user || partner || admin) ? true: false
return callback(null, found)
} catch (err) {
console.error(err)
return callback(err, null)
}
};
attr: in your queries will search for a db field called attr. If you want to use the function parameter attr, use [attr]: as the key.
Example:
attr = 'username'
User.find({ [attr]: val }, function (err, user) {
if (err) {
console.log(err);
}
})
This is a feature available since ES6 so should work fine. See the docs here for more info

meteor - How to update subscription documents?

I subscribe some collection from server. After I try to delete one document from client side it shows remove failed: Access denied. so I tried to delete it from server side by Meteor.call it works fine but client side has same number of documents.
Below code will explain you better.
ClientJS:
Template.Message.onCreated(function () {
this.autorun(function () {
this.subscription = Meteor.subscribe('mymessage');
}.bind(this));
});
Template.Message.onRendered(function () {
this.autorun(function () {
if (this.subscription.ready()) {
console.log(Message.find().count()); //10
}
}.bind(this));
});
ServerJS:
Meteor.publish('mymessage', function() {
console.log(Message.find().count()); //10
return Message.find();
});
In a click event
ClientJS:
Meteor.call("deletemsg", this._id._str, function(error, result){
if(!error){
console.log(Message.find().count()); // 10, Want to update document here.
}
});
Serverjs
Meteor.methods({
deletemsg: function (delmsg) {
if(Message.remove({"_id":new Mongo.ObjectID(delmsg)})){
console.log(Message.find().count()); //9
return true;
} else {
throw new Meteor.Error("some error message");
}
}
});
Note : I am using existing Mongodb.
Your error is probably related to your configuration of the native rules allow and deny. You should have somewhere on your server a piece of code looking like that (Message being your collection name):
Message.allow({
insert: function (userId, doc) {
//allow rule
},
update: function (userId, doc, fields, modifier) {
//allow rule
},
remove: function (userId, doc) {
//allow rule
}
});
Or an equivalent with deny. It looks like your current user is not allowed to delete (i.e. remove) messages from the collection.
Quick sidenote: you don't need to wrap your subscriptions in an autorun. If you use iron-router, you can use the built in functions to subscribe. In your routes options, you can add something like this:
action: function() {
if(this.isReady()) { this.render(); } else { this.render("loading");}
},
isReady: function() {
var subs = [
Meteor.subscribe("yourPublication")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
You don't need to use _str as _id is already string.
Client JS
Meteor.call("deletemsg", this._id, function(error, result){ //Remove _str from here
if(!error){
console.log(Message.find().count()); // 10, Want to update document here.
}
});
Server JS
When you delete document, you need to pass only id, not object.
Meteor.methods({
deletemsg: function (delmsg) {
if(Message.remove(delmsg)){
console.log(Message.find().count()); //9
return true;
} else {
throw new Meteor.Error("some error message");
}
}
});
Allowing CRUD operations.
if above method doesnt work, try allowing CRUD operations for that collections from server block. Here is documentation.

MONGODB mongoose update embedded document in node.js

I have an embedded image document in my main document and I am able to update it as follows. Everytime it updates, it just overrides the existing image and does not add it to the existing images.. I tried by "{images.push: image}", but that does not work. what is the syntax ? I am not sure if i am able to explain the problem. if not, please let me know if i need to add more info here.
var image = {
img : new_file_name,
orig_img_name : files.source.filename,
caption : fields.message,
upload_date : new Date()
};
RentModel.update({'facebook_id':fb_id, 'prop_location.lat':lat, 'prop_location.lng':lng}, {'images':image}, function(err, docs){
if(err) {
console.log("Error during friends update");
throw err;
}
console.log('image updated to database', new_file_name);
});
try to find() the embedded document first and then add the image to the array.
RentModel.find({'facebook_id':fb_id, 'prop_location.lat':lat, 'prop_location.lng':lng}, function (err, item) {
if (err) //throw ...
if (item) {
if (item.images && item.images.length) {
item.images.push(image);
}
else {
item.images = [image];
}
// save changes
item.save(function (err) {
if (!err) {
// done ...
}
});
}
});