NodeJS - Removing a particular item from an Array - arrays

I'm getting list of all collections from mongodb, in form of an array
mongoose.connection.db.listCollections().toArray(function (err, names) {
if (err) {
console.log(err);
}
console.log(names);
output:
[ { name: 'system.indexes' },
{ name: 'books' },
...
I want to remove that system.indexes from the array. I tried playing around with some functions like:
Splice
Pop, Shift
Underscore's withoutfunction
Well, Honestly I don't even know if they were intended for this.

To remove the objects where name is equal to system.indexes do it as follow:
mongoose.connection.db.listCollections().toArray(function (err, names) {
if (err) {
console.log(err);
}else{
var i;
for(i=names.length - 1; i >= 0; i-=1){
if(names[i].name !== 'system.indexes'){
names.splice(i,1);
}
}
// names now contain all items without the system.indexes
}

Related

How to get all subcollection documents with subcollection name as a date? [duplicate]

Say I have this minimal database stored in Cloud Firestore. How could I retrieve the names of subCollection1 and subCollection2?
rootCollection {
aDocument: {
someField: { value: 1 },
anotherField: { value: 2 }
subCollection1: ...,
subCollection2: ...,
}
}
I would expect to be able to just read the ids off of aDocument, but only the fields show up when I get() the document.
rootRef.doc('aDocument').get()
.then(doc =>
// only logs [ "someField", "anotherField" ], no collections
console.log( Object.keys(doc.data()) )
)
It is not currently supported to get a list of (sub)collections from Firestore in the client SDKs (Web, iOS, Android).
In server-side SDKs this functionality does exist. For example, in Node.js you'll be after the ListCollectionIds method:
var firestore = require('firestore.v1beta1');
var client = firestore.v1beta1({
// optional auth parameters.
});
// Iterate over all elements.
var formattedParent = client.anyPathPath("[PROJECT]", "[DATABASE]", "[DOCUMENT]", "[ANY_PATH]");
client.listCollectionIds({parent: formattedParent}).then(function(responses) {
var resources = responses[0];
for (var i = 0; i < resources.length; ++i) {
// doThingsWith(resources[i])
}
})
.catch(function(err) {
console.error(err);
});
It seems like they have added a method called getCollections() to Node.js:
firestore.doc(`/myCollection/myDocument`).getCollections().then(collections => {
for (let collection of collections) {
console.log(`Found collection with id: ${collection.id}`);
}
});
This example prints out all subcollections of the document at /myCollection/myDocument
Isn't this detailed in the documentation?
/**
* Delete a collection, in batches of batchSize. Note that this does
* not recursively delete subcollections of documents in the collection
*/
function deleteCollection(db, collectionRef, batchSize) {
var query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise(function(resolve, reject) {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
}
function deleteQueryBatch(db, query, batchSize, resolve, reject) {
query.get()
.then((snapshot) => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = db.batch();
snapshot.docs.forEach(function(doc) {
batch.delete(doc.ref);
});
return batch.commit().then(function() {
return snapshot.size;
});
}).then(function(numDeleted) {
if (numDeleted <= batchSize) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(function() {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
This answer is in the docs
Sadly the docs aren't clear what you import.
Based on the docs, my code ended up looking like this:
import admin, { firestore } from 'firebase-admin'
let collections: string[] = null
const adminRef: firestore.DocumentReference<any> = admin.firestore().doc(path)
const collectionRefs: firestore.CollectionReference[] = await adminRef.listCollections()
collections = collectionRefs.map((collectionRef: firestore.CollectionReference) => collectionRef.id)
This is of course Node.js server side code. As per the docs, this cannot be done on the client.

MongoDB: how can I find and merge array

I'm trying to find a specific ID in my document and then merge an array to the existing one, for example if I have this array stored in db.friends:
["12","13","14"]
and I send this array: ["12","16","18"], db.friends should contain: ["12","13","14","16","18"]
I'm using underscore library, but I'm not sure I have to (maybe "aggregate" in mongoose?)
Here is what I did, can you tell me where am I wrong?
function saveFollowers(req, res) {
var friends = req.body.friends; // the new array to merge ["54aafe9df4ee360300fc94c7"];
User.findOne({_id: req.user._id}).exec(function (err, user) {
if (err) {
res.jsonp({error: "Error fetching user info"})
} else {
friends = _.extend(friends, user.friends); //user.friends=existing friends we have in db
user.save(function (err) {
if (err) { res.jsonp({error: "Cant save"}); }
console.log("Friends NOW:"+JSON.stringify(friends)); //Here I don't see the merge, also, I can't see it in mongo db.
res.jsonp("success");
});
}
});
Thank you!
With your current implementation, you haven't actually modified the friends key in the returned user object. So rather you can use the union method as
user.friends = _.union(friends, user.friends); //user.friends=existing friends
user.save(function (err) { .. }
Or with ES6 using the spread operator for concatenating the array and Set for creating a distinct set of elements:
user.friends = [...new Set([...friends ,...user.friends])];
user.save(function (err) { .. }
Another alternative is using the aggregation framework, you could utilize the $setUnion operator:
function saveFollowers(req, res) {
var friends = req.body.friends; // the new array to merge ["54aafe9df4ee360300fc94c7"];
User.aggregate([
{ "$match": { _id: req.user._id } },
{
"$project": {
"friends": { "$setUnion": [ "$friends", friends ] }
}
}
]).exec(function (err, results){
if (err) {
res.jsonp({error: "Error fetching user info"})
} else {
User.findByIdAndUpdate(req.user._id,
{ "$set": { "friends": results[0].friends } },
{ "new": true },
function (err, user) {
if (err) { res.jsonp({error: "Cant save"}); }
console.log("Friends NOW: "+ JSON.stringify(user.friends));
res.jsonp("success");
}
);
}
});
}

Meteor return length of array in mongodb

In my users profile collection I have array with image objects in it.
A user can have a max of 3 images in their profile collection. If the user has 3, throw an error that the maximum has been reached. The user has the option to remove an image themselves in the frontend.
I thought the solution would be to check the length of the array with $size. if it's less then 3, insert the image, else throw error.
I'm using the tomi:upload-jquery package.
client:
Template.uploadImage.helpers({
uploadUserData: function() {
return Meteor.user();
},
finishUpload: function() {
return {
finished: function(index, fileInfo, context) {
Meteor.call('insert.profileImage', fileInfo, function(error, userId) {
if (error) {
// todo: display modal with error
return console.log(error.reason);
} else {
// console.log('success ' +userId);
// console.log('success ' + fileInfo);
}
});
}
};
}
});
The method (server) I use:
'insert.profileImage': function(postImage) {
check(postImage, Object);
// check array profile.images max 3
Meteor.users.update(this.userId, {
$push: {
'profile.images': postImage
}
});
},
You may do it with a function using the $where operator:
'insert.profileImage': function(postImage) {
var updateResults;
check(postImage, Object);
updateResults = Meteor.users.update(
{
_id : this.userId,
$where : 'this.profile.images.length < 3' //'this' is the tested doc
},
{
$push: {
'profile.images': postImage
}
});
if(updateResults === 0) {
throw new Meteor.Error('too-many-profile-images',
'A user can only have up to 3 images on his/her profile');
}
},
The Mongo docs warns about potential performance issues (if you run a JavaScript function on all documents of the store, you're in for bad surprises) but since we also search by _id I guess it should be fine.
This way, the update just doesn't run if the user has too many images. You can also check the number of affected document (the return value of the update) to know if something happened. If nothing (returns 0) happened, there's not many possibilities: The user has too many images.
Use the $exists operator to check the existence of all documents that have at least a fourth profile image array element (index position 3) with the dot notation. For example you could use it to check whether the size of the profile.image array is greater than 3 with the find() method as follows:
var hasSizeGreaterThanThree = Meteor.users.find(
{
'_id': this.userId,
'profile.image.3': { '$exists': true }
}).count() > 0;
So you could use that in your code as:
'insert.profileImage': function(postImage) {
check(postImage, Object);
// check array profile.images max 3
var hasSizeGreaterThanThree = Meteor.users.find(
{
'_id': this.userId,
'profile.image.3': { '$exists': true }
}).count() > 0;
if (!hasSizeGreaterThanThree){
Meteor.users.update(this.userId, {
$push: {
'profile.images': postImage
}
});
}
},

Node.js : control flow with forEach

I am trying to create array from database objects :
I have entity "group" wich hasMany "devices", I want to create array whit all groups and for each groups the list of his devices :
[
{
"group_id": “1”,
"name": “My_group”,
"devices_list": [1, 2, 18]
},
{
"group_id": “2”,
"name": “My_second_group”,
"devices_list": [3, 24]
}
]
I tried several ways like this :
Group.all(function (err, groups) {
var resJson = {};
groups.forEach(function(group, index){
group.devices(function(err, devices){
resJson[index] = group;
console.log(devices);
resJson[index].devices_list = devices;
//End of the loop
if (index == groups.length -1){
send({code: 200, data: resJson});
}
});
});
});
EDIT 1 :
I tried this way too :
var resJson = {};
groups.forEach(function(group, index){
group.devices(function(err, devices){
resJson[index] = group;
resJson[index].devices_list = [];
devices.forEach(function(device,index2){
resJson[index].devices_list.push(device);
});
//End of the loop
if (index == groups.length -1){
send({code: 200, data: resJson});
}
});
});
But finally, my resJson contains only empty groups (groups without device associated), the other groups are not visible. Thus my devices_list are all empty whereas the console.log(devices) display devices.
It seems that the "send" instruction is processed before the treatment of non-empty groups.
What is the rigth way to do this ?
Thank you for your time
Instead of tracking and using an index against the length of the list perhaps you could use an after type of construct. I really enjoy them and they're easy to integrate and serve the perfect purpose for doing something after a set number of times.
First, lets define an after function you can use.
var after = function(amount, fn) {
var count = 0;
return function() {
count += 1;
if (count >= amount) {
fn.apply(arguments);
}
};
};
That should work for you now, let's modify your code sample to use this.
var json = []; // To return, you originally wanted an array.
Group.all(function(err, groups) {
if (err) {
// Handle the error
} else {
var sendJson = after(groups.length, function(json) {
send({code: 200, data: json});
});
groups.forEach(function(group) {
group.devices(function(err, devices) {
if (err) {
// Handle the error...
} else {
group.devices_list = devices;
json.push(group); // This part is different, using this method you'll match the JSON you gave as your "goal"
}
// This is outside the if/else because it needs to be called for every group
// regardless of change. If you do not call this the exact number of times
// that you specified it will never fire.
sendJson(json);
});
}
});
Perhaps something like that might clear up your issue.

Node.JS Append Array and Convert to JSON

I have the below that runs a few functions to grab different data and i am trying to create/append to an array to "create" a new array that i can then JSON.stringify and pass to the Jade view, i seem to only be able to get the "k" variable of the two loops it does and outputs as "coin1coin2" with the "" instead of a JSON array with the balance and addresses (multiples) after each coin name.
// list all wallets //
app.get('/wallet', function(req, res){
var coins = app.get('coins');
var addresses = "";
for (var k in coins) {
addresses += k;
console.log('Coin: ' + k);
//console.log(k+' => '+coins[k]);
connect(coins[k]);
client.getBalance('44abc0a80102', 6, function(err, balance) {
if (err) return console.log(err);
addresses += balance;
//console.log('Balance: ' + balance);
});
client.getAddressesByAccount('44abc0a80102', function(err, address) {
if (err) return console.log(err);
addresses["address"] += address;
});
}
console.log(addresses);
res.render('wallet', {
title: 'Wallet',
address: JSON.stringify(addresses)
});
});
In the Jade view i am just doing p= address to show the full array so i can at least see what i am getting before creating logic in the view to loop through it.
The problem here is that your for loop is synchronous, whereas your client.getBalance and client.getAddressesByAccount calls are (almost definitely) not.
When your for loop finishes, your client.getBalance and client.getAddressesByAccount operations are still in progress, so they haven't had a chance to populate your addresses thing yet. Thus it is empty when it's passed to the templating system.
You probably want to use something like async's map method to do this instead. It'd look something like this:
// list all wallets
app.get('/wallet', function(req, res){
// array? object?
var coins = app.get('coins');
async.map(coins, function(coinId, cb) {
// ???
console.log('Coin: ' + JSON.stringify(coin));
// not sure what this does!
connect(coin);
client.getBalance('44abc0a80102', 6, function(err, balance) {
if (err) return cb(err);
client.getAddressesByAccount('44abc0a80102', function(err, address) {
if (err) return cb(err);
return cb(null, {
address: address,
balance: balance,
});
});
});
}, function(err, addresses) {
// note that we only act on an error here
if (err) {
return console.log(err);
}
// [{address: ..., balance: ...}, ...]
console.log(addresses);
res.render('wallet', {
title: 'Wallet',
address: JSON.stringify(addresses),
});
});
});
The addresses value will be an array of {address: ..., balance: ...} objects. I'm not sure what you were expecting it to be, since at one point you treat it like a string, then you treat it like an object.

Resources