Add/Merge element from from one Object to another Object inside array - javascript

I have the following code in my async function
async function test () {
try {
const aucs = await auctions.find({owner: 'owner', place: 'place'}).limit(15);
const item = await Promise.all(aucs.map(async aucs => {
const map = await items.find({id: aucs.item});
return map[0]
}));
--->we are here [1]
} catch (err) {
console.log(err);
}
}
test();
and the point [1] I have two arrays avaliable which contain another objects (both are responces from Mongo) here they are:
aucs = [ { _id: 5c00faa4936359120ceb3632,
auc: 177215422,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },
{ _id: 5c00faa4936359120ceb363f,
auc: 177215440,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },... ]
and
item = [ { _id: 5bcd8a6134cdd1223cd3239b,
id: 130251,
name: 'TEST_NAME_1',
__v: 0 },
{ _id: 5bcd8a6134cdd1223cd3239b,
id: 130252,
name: 'TEST_NAME_2',
__v: 0 },...]
And I'd like to add to aucs[i]every element in aucs, item[i].name (name: 'TEST_NAME_1')
Like:
combined = [ { _id: 5c00faa4936359120ceb3632,
auc: 177215422,
item: 130251,
name: 'TEST_NAME_1',
price: 26000000,
lastModified: 1543567955000,
date: 2018-11-30T08:53:56.290Z,
__v: 0 },...]
I'm trying to use for loop with auc[i].name = item[i].name or using aucs.push() but for some unknown reason it wasn't worked for me.
I receive error for .push is not a function and for loop didn't add anything. So maybe someone have any idea?
Note: 1
actually solve one problem with item, mongo returns me array inside array like [ [ { ...} ] ] so I should using return map[0] to fix it.
Note: 2
both of aucs and item are object according to typeof and have .length option (they are both the same length and should be all the time. So they are not promises
Note: 3
let merged = {...aucs, ...item}; returns me
{ '0': { _id: 5bcd8a6134cdd1223cd3239b,
id: 130251,
name: 'JewelCraft',
icon: 'inv_jewelcrafting_70_jeweltoy',
stackable: 1,
isAuctionable: true,
__v: 0 }...
but not what I need to

Would be more superior and faster if you use some aggregation trick here
auctions.aggregate([
{ "$match": { "owner": "owner", "place": "place" }},
{ "$lookup": {
"from": "items",
"let": { "item": "$item" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$id", "$$item"] }}}
],
"as": "item"
}},
{ "$limit": 15 }
])

If I understand correctly, the aim is to create a new collection of aucs like the ones found, each updated to include an item name from the item collection where the item has a matching id.
let aucs = [ { _id: "5c00faa4936359120ceb3632",
auc: 177215422,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: "2018-11-30T08:53:56.290Z",
__v: 0 },
{ _id: "5c00faa4936359120ceb363f",
auc: 177215440,
item: 130251,
price: 26000000,
lastModified: 1543567955000,
date: "2018-11-30T08:53:56.290Z",
__v: 0 } ];
item = [ { _id: "5bcd8a6134cdd1223cd3239b",
id: 130251,
name: 'TEST_NAME_1',
__v: 0 },
{ _id: "5bcd8a6134cdd1223cd3239b",
id: 130252,
name: 'TEST_NAME_2',
__v: 0 }];
// create a new collection of aucs modified to include the names of matching items
let combined = [];
aucs.forEach(auc => {
let combinedAuc = Object.assign({}, auc);
combined.push(combinedAuc);
let matchingItem = item.find(i => auc.item === i.id);
if (matchingItem) combinedAuc.name = matchingItem.name
});
console.log(combined)

Related

Saving a mongo document with mongoose returns the updated document, but didn't save in the database

My issue today is with MongoDB and mongoose in javascript. So first off I have this schema:
var playerModel = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "Users",
},
class: {
type: String,
required: true,
},
level: {
type: Number,
default: 1,
},
spells: {
type: [String],
default: ["", "", "", "", ""],
},
inventory: [], <- IMPORTANT
toolbelt: {
axe: [{}],
pickaxe: [{}],
fishingrod: [{}],
hammer: [{}],
saw: [{}],
sewingkit: [{}],
knife: [{}],
gemkit: [{}],
food: [{}],
potion: [{}],
},
gear: {
weapon: [{}],
head: [{}],
chest: [{}],
gloves: [{}],
legs: [{}],
shoes: [{}],
earrings: [{}],
ring: [{}],
necklace: [{}],
},
gold: {
type: Number,
default: 0,
},
bagSize: {
type: Number,
default: 20,
},
skills: {
type: [userSkills],
default: defaultSkills,
},
});
and my problem is with inventory: [].
In my code I manually push items in my array which save properly in the database, but whenever I wanna modify a field of one of my array object, it won't update it like a qty for example:
player.inventory[i].qty += 1; //Where player is my model containing my inventory
...
player.save()
.then((doc) => console.log("DOC", doc))
.catch((err) => console.log("ERR", err));
and the doc returned look something like this:
...
inventory: [
{
_id: 626a1468cdbeced19102942d,
slug: 'crude_campfire',
qty: 1,
charges: 19,
efficiency: -0.2
},
{ _id: 626a14e7530afad2c5c1894c, slug: 'wheat', qty: 1 },
{ _id: 626a14f1530afad2c5c1894d, slug: 'carrot', qty: 1 },
{ _id: 626a150f530afad2c5c1894e, slug: 'potato', qty: 1 },
{ _id: 626a155b530afad2c5c1894f, slug: 'red_bream', qty: 1 },
{ _id: 626a15b5530afad2c5c18950, slug: 'bone', qty: 1 },
{ _id: 626a15c9530afad2c5c18951, slug: 'clam_shell', qty: 1 },
{ _id: 626a15d5530afad2c5c18952, slug: 'stone', qty: 8 },
{ _id: 626a15df530afad2c5c18953, slug: 'taeroot', qty: 1 },
{ _id: 626a15e9530afad2c5c18954, slug: 'shiny_pendant', qty: 1 },
{ _id: 626a15fd530afad2c5c18955, slug: 'sickleweed', qty: 1 },
{ _id: 626a1625530afad2c5c18956, slug: 'ceruleaf', qty: 1 },
{ _id: 626a1dba272403ea21fb71ef, slug: 'stone', qty: 1 },
{ _id: 626a1e4030a144f8179789e0, slug: 'birch_log', qty: 22 }, <- IMPORTANT
{ _id: 626a1e72372733f90cfdc003, slug: 'tree_twig', qty: 2 },
{ _id: 626a1e9a372733f90cfdc004, slug: 'honey', qty: 2 }
],
...
where you can see that my birch_log qty is now at 22. But whenever I go check in the database,
I don't know what's going on, and I'm kind of tired right, so I'll go to bed hoping someone can save me tomorrow :)
Thanks and have a good night ! :)
To update the object having {slug: birch_log}, you can use the following query:
player.update( {user : 123456 , inventory.slug : "birch_log" } ,
{$inc : {"inventory.$.qty" : 1} });
instead of using save() with promise, you can use update() query with async await. It will be short and clean code.
This answer fixed my problem
Since i'm using an "anonymous" array or whatever, mongoose doesn't know what changes to track in it so I need to tell it that it's been modified !

Javascript .find() not working for nested object array [duplicate]

This question already has answers here:
Curly Brackets in Arrow Functions
(3 answers)
Closed 1 year ago.
I have two nested object arrays, one is an array describing a school (id, name, tests it conducts along with their counts), and another is an array of school teachers corresponding to the schools in the first array. Here is some sample data for both arrays:
import { ObjectId } from 'mongodb'
monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1"
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2"
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5')
schoolName: "School2"
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6')
schoolName: "School1"
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
I obtained the second array by passing as an argument an array of schoolIds that I got from the query results for the first array, so I know that both arrays have about the same number of objects (assuming queries don't return null values). I am trying to link the test information in the first array with the school teacher in the second array but the .find() method is not working for me. Here is my code:
monthlySchoolTests.map(testObj => {
const selectedSchoolTeachers = schoolTeachers.find(teacherObj => {
String(testObj.schoolId) === String(teacherObj.schoolId)
})
console.log(selectedSchoolTeachers)
})
For some reason, I only get undefined even though I have tested this by mapping through each object in the first array and asserting that there is a match for every schoolId in the second array. (console.log yields true).
Sorry for any errors as this is my first time posting. Would appreciate any help!
Well, you could just quick-generate a combined array that had everything you need in it:
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
const ObjectId = (v) => v; // for testing
let monthlySchoolTests = [{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
schoolTests: [
{ testName: "Chemistry", count: 15 },
{ testName: "Music", count: 8 }
]
},
{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
schoolTests: [
{ testName: "Math", count: 3 },
{ testName: "Physics", count: 12 },
{ testName: "Biology", count: 10 }
]
}
]
let schoolTeachers = [{
schoolId: ObjectId('804ef074384b3d43f125ghs5'),
schoolName: "School2",
teachers: [
{ name: "Michael", email: "michael#school2.edu" },
{ name: "Jane", count: "jane#school2.edu" },
{ name: "Lukas", count: "lukas#school2.edu" }
]
},
{
schoolId: ObjectId('804d8527f390ghz26e3426j6'),
schoolName: "School1",
teachers: [
{ name: "Cleo", email: "cleo#school1.edu" },
{ name: "Celine", count: "celine#school1.edu" },
{ name: "Ike", count: "ike#school1.edu" }
]
}
]
let combined = monthlySchoolTests.map(e => ({...e, teachers: schoolTeachers.filter(t => t.schoolId === e.schoolId)[0].teachers}))
console.log(combined)

Using $graphLookup to traverse a nested data structure in MongoDB

I have the following schema:
const MenuSchema = new mongoose.Schema({
name: String,
type: String,
children: [{ type: ObjectId, ref: 'Menu' }],
});
And the following query:
const res = await Menu.aggregate([
{ $graphLookup: { from: "menus", startWith: "$children", connectToField: "children", connectFromField: "_id", as: "menus" }}
]);
As you can see, the menu schema is a self-referential data structure, children stores references to other instances of the same entity, with a 'type' field to differentiate the levels. I'm attempting to find and populate documents for each array of children (which are just BSON IDs) and return the results.
The above example seems to get most of the way there, however when I access one of the populated menus, it has a list of all the populated children in a flattened array, it doesn't retain the relationship structure.
For example, if I print out res, I get:
[ {
_id: 5f1212d053e5494bb45f18f3,
children: [ 5f1212d053e5494bb45f18f1 ],
name: 'Vodka',
type: 'Item',
__v: 0,
menus: [ [Object], [Object], [Object], [Object] ]
},
{
_id: 5f1212d053e5494bb45f18f4,
children: [ 5f1212d053e5494bb45f18f3, 5f1212d053e5494bb45f18f2 ],
name: 'Drinks',
type: 'Category',
__v: 0,
menus: [ [Object], [Object] ]
},
{
_id: 5f1212d053e5494bb45f18f5,
children: [ 5f1212d053e5494bb45f18f4 ],
name: 'Main Menu',
type: 'Menu',
__v: 0,
menus: [ [Object] ]
}
]
But when I print out res[1].menus, I get:
[
{
_id: 5f1212d053e5494bb45f18f3,
children: [ 5f1212d053e5494bb45f18f1 ],
name: 'Vodka',
type: 'Item',
__v: 0
},
{
_id: 5f1212d053e5494bb45f18f1,
children: [ 5f1212d053e5494bb45f18f0 ],
name: 'Double',
type: 'Variant',
__v: 0
},
{
_id: 5f1212d053e5494bb45f18f4,
children: [ 5f1212d053e5494bb45f18f3, 5f1212d053e5494bb45f18f2 ],
name: 'Drinks',
type: 'Category',
__v: 0
}
]
Which is all of the children in a flat array.
Is $graphLookup the correct approach, or am I just using it wrong?
I don't know if you are still looking for the answer for this, but if you use mongoose you can take advantage of the populate feature and use it as a middleware
Here's an example:
Let's say I want a list of people and their friends, and their friends-friends, etc. The result should look like this:
[
{
_id: "abc123",
name: "John Doe",
friends: [
{
_id: "efg456",
name: "Foo bar",
friends: [
{
_id: "hij789",
name: "Jane Doe",
friends: [more friends...]
}
]
}
]
]
In the db they are stored like this
{_id: "abc123", name: "John Doe", friends: ["efg456"]}
{_id: "efg456", name: "Foo bar", friends: ["hij789"]}
{_id: "hij789", name: "Jane Doe", friends: [more friends...]}
Your schema and middleware would be:
const Person = new Schema<Folder>({
name: {type: String, required: true},
friends: [{type: Schema.Types.ObjectId, ref: "person"}],
}, {timestamps: true})
Person.pre("find", function(next) {
this.populate("friends")
next()
})
Adding the function as a middleware to find will make it run for every person found. That includes the children in the friends array.

Remove duplicate keys and combine unique values in JavaScript array

I have an array containing a customer ID, value of transaction, and transaction ID for each transaction performed by the customer.
I have 20,000 transactions performed by 9,000 customers.
I want one customer ID, an array of all the prices per that customer ID, and an array of all the transaction Ids per customer ID.
Currently looks like this:
var transactionArray =
{
customerId: '1',
price: [ 100 ],
transactionID: ['00a13']
},
{
customerId: '2',
price: [ 200 ],
transactionID: ['00a14']
},
{
customerId: '1',
price: [ 700 ],
transactionID: ['00a15']
},
{
customerId: '2',
price: [ 1700 ],
transactionID: ['00a16']
},
... 19996 more items
and I'd like it to look like this:
var customerArray =
{
customerId: '1',
price: [ 100, 700 ],
transactionID: ['00a13', '00a15']
},
{
customerId: '2',
price: [ 200, 1700 ],
transactionID: ['00a14', '00a16']
},
...8998 more items
Just using reduce and push the elements onto the array
var transactionArray = [{
customerId: '1',
price: [100],
transactionID: ['00a13']
},
{
customerId: '2',
price: [200],
transactionID: ['00a14']
},
{
customerId: '1',
price: [700],
transactionID: ['00a15']
},
{
customerId: '2',
price: [1700],
transactionID: ['00a16']
},
]
var results = Object.values(transactionArray.reduce((custs, { customerId, price, transactionID }) => {
var customer = custs[customerId]
if (!customer) {
custs[customerId] = {
customerId: customerId,
price: [...price],
transactionID: [...transactionID]
}
} else {
customer.price = [...customer.price, ...price]
customer.transactionID = [...customer.transactionID, ...transactionID]
}
return custs
}, {}))
console.log(results)
It's convenient that customerId's are integers. If that ever changes, then you will need to index them, then rebuild the object.
// create a separate array for holding order of customerIds
const customerIds = []
const result = transactionArray
.reduce((acc, { customerId, price, transactionID }) => {
const idIndex = customerIds.indexOf(customerId)
// check if customerId exists in customerIds array
if (idIndex < 0) {
// if it doesn't, add it to the customerId's array
customerIds.push(customerId)
// then add the matching price and transactionID to the accumulator
// of this reduce, spreading the contents of the array into 2 new arrays.
acc.push([[...price], [...transactionID]])
} else {
// if the customerId is already accounted for, then
// add the price and transactionID to that customer's bucket
// at the same index where the customerId exists inside customerIds
acc[idIndex][0].push(...price)
acc[idIndex][1].push(...transactionID)
}
return acc
}, [])
// finally, convert the arrays back into objects
.map((value, index) => {
return ({
customerId: customerIds[index],
price: value[0],
transactionID: value[1],
})
})
console.log(result)
which logs:
[
{
customerId: '1',
price: [ 100, 700 ],
transactionID: [ '00a13', '00a15' ]
},
{
customerId: '2',
price: [ 200, 1700 ],
transactionID: [ '00a14', '00a16' ]
}
]
If the customerIds were strings that didn't represent integers, this will still work -- for example, if your customer data looked like this:
const transactionArray = [
{
customerId: '324asdrea',
price: [ 100 ],
transactionID: ['00a13']
},
{
customerId: '4hdffgi2',
price: [ 200 ],
transactionID: ['00a14']
},
{
customerId: '324asdrea',
price: [ 700 ],
transactionID: ['00a15']
},
{
customerId: '4hdffgi2',
price: [ 1700 ],
transactionID: ['00a16']
}
]
which results in:
[
{
customerId: '324asdrea',
price: [ 100, 700 ],
transactionID: [ '00a13', '00a15' ]
},
{
customerId: '4hdffgi2',
price: [ 200, 1700 ],
transactionID: [ '00a14', '00a16' ]
}
]

Mongoose Populate with a condition

in a Node.js App with Mongoose(Mongodb), With this code i fetch users and their books:
Users.find({user_id: req.user.id}).populate('books').exec(..);
Right now i want to fetch users that has an special book. i do like this:
Users.find({user_id: req.user.id}).populate('books',null,{bookname:"harry potter"}).exec(..);
But it doesn't work. With this, my code fetches users with their books of null value and if that condition matches, return them instead of null. In fact most of my users has null value for books. but what i want is that if that condioton in populate section is not matches, do not return that user in result array at all!
What i have to do? i have to do another query or something on results for what i need?
All I can think here is that you are calling it wrong. You do not really show much context here other than that your .populate() parameters do not look correct.
Here is a correct listing as a reproducible example:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var thingSchema = new Schema({
_id: Number,
name: String
},{ _id: false });
var parentSchema = new Schema({
name: String,
things: [{ type: Number, ref: 'Thing' }]
});
var Thing = mongoose.model( 'Thing', thingSchema ),
Parent = mongoose.model( 'Parent', parentSchema );
mongoose.connect('mongodb://localhost/thingtest');
var things = { "one": 1, "two": 2, "three": 3 };
async.series(
[
function(callback) {
async.each([Thing,Parent],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
var parentObj = new Parent({ "name": "me" });
async.each(
Object.keys(things).map(function(key) {
return { "name": key, "_id": things[key] }
}),
function(thing,callback) {
var mything = new Thing(thing);
parentObj.things.push(thing._id)
mything.save(callback)
},
function(err) {
if (err) callback(err);
parentObj.save(callback);
}
);
},
function(callback) {
console.log("filtered");
var options = {
path: 'things',
match: { "name": { "$in": ['two','three'] } }
};
Parent.find().populate(options).exec(function(err,docs) {
if (err) callback(err);
console.log(docs);
callback();
});
},
function(callback) {
console.log('unfiltered');
Parent.find().populate('things').exec(function(err,docs) {
if (err) callback(err);
console.log(docs);
callback();
})
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Which will consistently give results like this:
filtered
[ { _id: 55ec4c79f30f550939227dfb,
name: 'me',
__v: 0,
things:
[ { _id: 2, name: 'two', __v: 0 },
{ _id: 3, name: 'three', __v: 0 } ] } ]
unfiltered
[ { _id: 55ec4c79f30f550939227dfb,
name: 'me',
__v: 0,
things:
[ { _id: 1, name: 'one', __v: 0 },
{ _id: 2, name: 'two', __v: 0 },
{ _id: 3, name: 'three', __v: 0 } ] } ]
So take a good look at your data and your calls. The .populate() call needs to match the "path" and then also provide a "match" to query the documents that are to be populated.
Use elemMatch:
var title = 'Harry Potter';
Users.find({books: {$elemMatch: {name: title}})
.exec(processResults);

Categories