I'm building a deletion process where I need to find out if a certain document is referenced from another documents before deleting the original document.
In the example below, I need to find out if a given Company is used somewhere in my other documents:
Model metadata:
Company = {
id: ID,
name: name
}
User = {
id: ID,
name: name,
company_id: ID
}
Application = {
id: ID,
type: string,
company_id: ID
}
...
Here is what I would do using asynchronous calls:
const checkInUse = async (company_id) => {
let ret = await UserModel.findOne({ company_id: company_id });
if (ret) return true;
ret = await ApplicationModel.findOne({ company_id: company_id });
if (ret) return true;
...
return false;
}
I have some dozen of collections, so some checks would take several calls to findOne(), and that's what I want to avoid.
Is there is a better way of doing this check ?
Related
acUser is the field with existing data that I want to add to the collection
db.getCollection("old_user").find({id: 58})
.forEach((elm) => {
const userId = elm.id;
let userN = db.getCollection("user_new").find({ id: userId }).toArray()
if (userN) {
db.getCollection("old_user").updateOne({id: userId }, {$set: {newField: userN.acUser }});
}
})
As far as I can tell, populate() is being called in my code (because I get an error if I give it a wrong path), but it doesn't seem to be doing anything.
I searched for past question in Stack Overflow, and I've not seen one where someone's using a model that's referencing itself, so my guess is that that might be the problem.
This Mongoose doc is where I'm reading up on how to use populate().
My Model
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
email: { type: String, unique: true },
teamLeaders: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Agent' }],
teamMembers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Agent' }]
});
let Agent = mongoose.model('Agent', schema);
Agent.init();
module.exports = Agent;
The actual document in MongoDB Atlas (anonymised name + email)
{
"_id": {
"$oid": "62e3e0ab57560a5c15a535e0"
},
"teamLeaders": [],
"teamMembers": [
{
"$oid": "62e3f548678dbed5593acc8e"
},
{
"$oid": "62e3f548678dbed5593acc91"
},
{
"$oid": "62e3f548678dbed5593acc94"
},
{
"$oid": "62e3f548678dbed5593acc97"
},
{
"$oid": "62e3f548678dbed5593acc9a"
},
{
"$oid": "62e3f548678dbed5593acc9d"
},
{
"$oid": "62e3f548678dbed5593acca0"
},
{
"$oid": "62e3f548678dbed5593acca3"
}
],
"firstName": "John",
"lastName": "Smith",
"email": "john#smith.com",
"__v": 8
}
Code where I'm calling populate()
const Agent = require('../models/agents');
const mongoose = require("mongoose");
const db = require("../config/db");
mongoose.connect(process.env.MONGODB_URI || db.url);
// I've removed other functions that are not related to this. And the DB connection is definitely working fine.
// Actual private function in my code.
async function addAgent(firstName, lastName, email, isTeamLeader, teamLeader) {
let newAgent = Agent();
newAgent.firstName = firstName;
newAgent.lastName = lastName;
newAgent.email = email;
if (isTeamLeader) {
await newAgent.save();
} else {
newAgent.teamLeaders.push(teamLeader);
let savedAgent = await newAgent.save();
teamLeader.teamMembers.push(savedAgent);
await teamLeader.save();
}
}
// This is a dummy function to show how I created the agents.
async function createAgents() {
await addAgent('John', 'Smith', 'john#smith.com', true, null);
// Some time later... I called addAgent() manually since this is for an internal team with only 30 people.
// It's also why I'm just querying for the firstName since there's only one John in the internal team.
let teamLeader = await Agent.findOne({ firstName: 'John' });
await addAgent('Peter', 'Parker', 'peter#parker.com', false, teamLeader);
}
// This is the main one where I try to call populate().
async function mainFunction() {
Agent.findOne({ firstName: 'John' }).populate({ path: 'teamMembers', model: 'Agent' }).exec((err, agent) => {
if (err) return handleError(err);
console.log('Populated agent: ' + agent);
});
}
The data you got does bot correspond to the data you have. How dhould mongoose know that the reference id is inside oid?
With
teamLeaders: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Agent' }],
one should expect inside atlas something like
"teamLeaders": [
ObjectId("62e3f548678dbed5593acca0"),
ObjectId("62e3f548678dbed5593acca3"),
...
],
Make sure the ref is exactly referencing the correct value.
Anyway I'd use an aggregation, as it is probably way more efficientand can be used also with mongoose if you have to
https://www.mongodb.com/docs/manual/aggregation/
Thanks to questions/prompt from #Weedoze, I've figured out where the issues lie.
There are two main problems.
Problem 1
I had completely misunderstood the Mongoose docs for populate().
My initial understanding was that calling populate() will update the actual MongoDB document with the populated result. However, what it does is that it's a quality-of-life/convenience feature where it does a second query to replace the reference with the actual document contents. All of these only exist/happen at runtime.
Problem 2
populate() is doing its thing here, the problem is that the callback in exec() isn't actually running - I'm still pretty stumped by this as I can't see a reason why it wouldn't work, and I'm doing the same thing as the doc (as well as other guides on the internet).
To clarify, this means that the populate() function does run, but the callback function inside exec() isn't being reached at all (I've checked by blocking it with a debugger checkpoint).
Ways I'm using the callback which do not work.
Agent.findOne({ firstName: 'John' }).populate('teamMembers').exec((err, agent) => {
if (err) return handleError(err);
console.log('Populated agent: ' + agent);
});
Agent.findOne({ firstName: 'John' }).populate('teamMembers').exec(function(err, agent) {
if (err) return handleError(err);
console.log('Populated agent: ' + agent);
});
To get around this, I just access the results in other ways, e.g.
let agent = await Agent.findOne({ firstName: 'John' }).populate('teamMembers');
console.log('Populated agent: ' + agent);
// Pretty much same thing as above. Only difference is that exec() returns a proper Promise.
// Tried this mostly to confirm that exec() does run and the issue is with the callback.
let agent = await Agent.findOne({ firstName: 'John' }).populate('teamMembers').exec();
console.log('Populated agent: ' + agent);
// Since populate() returns a "then-able" result anyway.
Agent.findOne({ firstName: 'John' }).populate('teamMembers').then((err, agent) => {
if (err) return handleError(err);
console.log('Populated agent: ' + agent);
});
I have a problem accessing the _id of the last created element inserted in to mongodbe.
is there any solution to just get the id, instead of getting all elements? especially if the data list is so long and nested so its really hard to pin the created element and gain access to his id
I am using mongoose driver on this one.
let updateDeptArr = await Budget.findOneAndUpdate(
// Dynamic
{
'_id': `${propertyValues[0]}`, // user ID
[`${keys[2]}._id`]: `${propertyValues[1]}`
},
{
'$push': {
[`${keys[2]}.$.${keys[3]}`]: propertyValues[3]
}
}, { _id: true, new: true }
).then(function (data) {
// we need to get and send The id of the last created element!!!
console.log(data[keys[2]]);
// let order = data[keys[1]].length - 1
// let id = data[keys[1]][`${order}`]._id
// res.json({ _id: id })
})
}
You can use select after query.
In the upcoming listing, you have a mongoose schema being used to query MongoDB, and just two fields are selected, as you want.
Loc
.findById(req.params.locationid)
.select('name reviews')//select chained
.exec();
Try to chain select to your call. It will just give back the name and reviews.
Try this:
let updateDeptArr = await Budget.findOneAndUpdate(
// Dynamic
{
'_id': `${propertyValues[0]}`, // user ID
[`${keys[2]}._id`]: `${propertyValues[1]}`
},
{
'$push': {
[`${keys[2]}.$.${keys[3]}`]: propertyValues[3]
}
}, { _id: true, new: true }
).select("_id")// not sure if Mongoose will chain this way
.then(function (data) {
// we need to get and send The id of the last created element!!!
console.log(data[keys[2]]);
// let order = data[keys[1]].length - 1
// let id = data[keys[1]][`${order}`]._id
// res.json({ _id: id })
})
}
I'm trying to delete a mongodb object and then once deleted, I want to delete everything associated with that mongodb object. Including nested mongodb objects from my mongo database.
var parentObjectSchema = new mongoose.Schema({
name: String,
split: Number,
parts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "ChildObjectSchema"
}
],
});
var childObjectSchema = new mongoose.Schema({
name: String,
number: Number,
things: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Things"
}
],
});
So I am trying to delete the parentObject, and childObjects that come along with it. Not sure how I would go about doing that. I am successful in deleting the parentObject but that childObject is still in the mongodb, taking up space. Any ideas?
MongoDB doesn't provide the notion of foreign keys like other databases do. Mongoose has convenience methods in the client library that populates your documents with other documents using multiple queries and joining the results:
https://mongoosejs.com/docs/populate.html
If you want to do a cascading deletion then you'll need to grab the object ids of the children in the parent documents you want to delete, and then execute a delete against those children documents.
Here's a simplified example:
const deleteThing = (thingId) => {
thingObjectSchema.remove({ _id: thingId });
};
const deleteChild = (childId) => {
childObjectSchema.findOne({ _id: childId }).select('things').lean().exec((err, child) => {
for (const thingId of child.things) {
deleteThing(thingId);
}
childObjectSchema.remove({ _id: childId });
})
};
const deleteParent = (parentId) => {
parentObjectSchema.findOne({ _id: parentId }).select('parts').lean().exec((err, parent) => {
for (const childId of parent.parts) {
deleteChild(childId);
}
parentObjectSchema.remove({ _id: parentId });
})
};
// note: not actually tested
I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256