I am writing code in nodejs/MongoDB and am countering this particular issue which I was hoping to get help with:
I have various schemas defined within my Models and I note that for each of these MongoDB automatically populates a unique id, which is represented by the _id field.
However, I am interested in creating a customized ID, which is basically an integer that auto-increments. I have done so by utilizing the 'mongoose-auto-increment' npm package. For example, in my UserSchema:
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
autoIncrement.initialize(mongoose.connection);
UserSchema.plugin(autoIncrement.plugin, {
model: 'UserSchema',
field: 'user_id',
startAt: 1,
incrementBy: 1
});
To speed up my application, I have a seeds.js file which aims to load a bunch of data upon application initialization. However, to make this fully functional, I need a way to access my models and reference them over to other models (for cases when there is a one-to-one and one-to-many relationship). Since the mongoDB default _id is extremely long and there is no way to get the result unless I am actually on the html page and can use the req.params.id function, I have been trying to use mongoDB's findOne function to do this without success.
For example:
var myDocument = User.findOne({user_id: {$type: 25}});
if (myDocument) {
var myName = myDocument.user_id;
console.log(myName);
}
However, the result is always 'undefined' even though I know there is a User model saved in my database with a user_id of 25.
Any help would be much appreciated :)
User.findOne({ user_id: 25 }).exec(function (err, record) {
if (err) {
console.log(err);
} else {
console.log(record);
}
});
You need to undestand the nature of Node.js.
Node.js runs in async nature so you can't get the result here.
You need to do with other ways
like:
use callback
use promise
use async/await(ES8)
Try this:
User.findOne({user_id: {$type: 25}}, function (err, myDocument) {
if (myDocument) {
var myName = myDocument.user_id;
console.log(myName);
} else {
console.log(err);
}
});
Related
I want to delete from an articles table using knex by article_id. This already exists in comments table as a foreign key.
How can I test that data has been deleted and how can I send that to the user.
I decided to approach this by writing a function to delete from both functions with a .then. Does this look like I am on the right lines?
exports.deleteArticleById = function (req, res, next) {
const { article_id } = req.params;
return connection('comments')
.where('comments.article_id', article_id)
.del()
.returning('*')
.then((deleted) => {
console.log(deleted);
return connection('articles')
.where('articles.article_id', article_id)
.del()
.returning('*');
})
.then((article) => {
console.log(article);
return res.status(204).send('article deleted');
})
.catch(err => next(err));
};
At the moment I am getting the correct data with the logs but I am getting a status 500 but I think I need to be trying to get a 204?
Any help would be much appreciated.
What you're trying to do is called a cascading deletion.
These are better (and almost always) handled at the database level instead of the application level.
It's the job of the DBMS to enforce this kind of referential integrity assuming you define your schema correctly so that entities are correctly linked together, via foreign keys.
In short, you should define your database schema as such that when you delete an Article, it's associated Comments also get deleted for you.
Here's how I would do it using knex.js migrations:
// Define Article.
db.schema.createTableIfNotExists('article', t => {
t.increments('article_id').primary()
t.text('content')
})
// Define Comment.
// Each Comment is associated with an Article (1 - many).
db.schema.createTableIfNotExists('comment', t => {
t.increments('comment_id').primary() // Add an autoincrement primary key (PK).
t.integer('article_id').unsigned() // Add a foreign key (FK)...
.references('article.article_id') // ...which references Article PK.
.onUpdate('CASCADE') // If Article PK is changed, update FK as well.
.onDelete('CASCADE') // If Article is deleted, delete Comment as well.
t.text('content')
})
So when you run this to delete an Article:
await db('article').where({ article_id: 1 }).del()
All Comments associated with that Article also get deleted, automatically.
Don't try to perform cascading deletions yourself by writing application code. The DBMS is specifically designed with intricate mechanisms to ensure that deletions always happen in a consistent manner; It's purpose is to handle these operations for you. it would be wasteful, complicated and quite error-prone to attempt to replicate this functionality yourself.
I have a GET all products endpoint which is taking an extremely long time to return responses:
Product.find(find, function(err, _products) {
if (err) {
res.status(400).json({ error: err })
return
}
res.json({ data: _products })
}).sort( [['_id', -1]] ).populate([
{ path: 'colors', model: 'Color' },
{ path: 'size', model: 'Size' },
{ path: 'price', model: 'Price' }
]).lean()
This query is taking up to 4 seconds, despite there only being 60 documents in the products collection.
This query came from a previous developer, and I'm not so familiar with Mongoose.
What are the performance consequences of sort and populate? I assume populate is to blame here? I am not really sure what populate is doing, so I'm unclear how to either avoid it or index at a DB level to improve performance.
From the Mongoose docs, "Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s)"
So your ObjectId reference on your model gets replaced by an entire Mongoose document. Doing so on multiple paths in one query will therefore slow down your app. If you want to keep the same code structure, you can use select to specify what fields of the document that should be populated, i.e. { path: 'colors', model: 'Color', select: 'name' }. So instead of returning all the data of the Color document here, you just get the name.
You can also call cursor() to stream query results from MongoDB:
var cursor = Person.find().cursor();
cursor.on('data', function(doc) {
// Called once for every document
});
cursor.on('close', function() {
// Called when done
});
You can read more about the cursor function in the Mongoose documentation here.
In general, try to only use populate for specific tasks like getting the name of a color for only one product.
sort will not cause any major performance issues until you reach much larger databases.
Hope it helps!
I was trying to console.log(record._id) all of records on my mongodb collection using Mongoose. I kept getting undefined for each of the _id values.
I struggled until I bumped into this post. Then I used console.dir to find the location of the _id and used that in my console.log:
MySchemaModel.find({}).then(function(records) {
records.forEach(function(record) {
console.log(record._doc._id); // <-- I added ._doc
});
});
But, this looks down-right hacky. Is there a better way to do this?
NOTE: This isn't just something that affects console.log. I'm just keeping the question narrow.
If you want to customize/edit record then you should use .lean() function.The .lean() function will turn it into a normal JavaScript object. If you don't use .lean() function then each record is still a mongoose document and _id behaves differently in that context. So can use like
MySchemaModel.find({}).lean().exec(function(error, records) {
records.forEach(function(record) {
console.log(record._id);
});
});
N.B: when use .exec() then first parameter used for error and second one for success data.
Mongoose assigns each of your schemas an id virtual getter by default
which returns the documents _id field cast to a string, or in the case
of ObjectIds, its hexString. If you don't want an id getter added to
your schema, you may disable it passing this option at schema
construction time.
Source: Mongoose Docs
var schema = new Schema({ name: String }, { id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
I guess the issue is with .then promise, I have never seen that.
MySchemaModel.find({}).then
So just try simple .exec call with callback.
MySchemaModel.find({}).exec(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
The problem is that each record is still a mongoose document and _id behaves differently in that context. The .lean() function will turn it into a normal JavaScript object.
MySchemaModel.find({}).lean().then(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
you can also use the .map() method :
MySchemaModel.find({}).exec(function(records) {
console.log(records.map(record => record._id);
});
if you are using a model you don't get the full object but an instance of _doc as record
so you should directly
console.log(record._id)
or
console.log(record._id.valueOf())
but when you return record as response you get the full object so it's better to use .find().lean()
I was wondering whether sequelize implements (or plans to implement) any change tracking mechanism, which goal would be to avoid running unnecessary queries. For example:
var user = sequelize.User.find { where: { name: 'bob' } };
user.name = 'john';
user.save();
The sequelize will of course update the username. Now, imagine that the assignment is missing.
var user = sequelize.User.find { where: { name: 'bob' } };
user.save();
Is sequelize smart enough to figure it out that no database update is needed (and won't do one)? Is there any flag exposed by the sequelize object to see whether any changes were done to the model?
Or should I simply test by hand each property against the original model?
I have noticed an user._previousDataValues and user.options.isDirty properties on the sequelize model, but I am not sure what is their purpose and whether I should rely my code on those in any way.
You can call user.changed() to get an array of keys that have been changed. However, I think that calling .save() will trigger the save regardless if there has been a change in the instance.
The code I currently have is:
User.findOne(
{
"subUsers.email" : userEmail
},
{
subUsers : {
$elemMatch: {
email : userEmail }
}
},
function(err, user){
if(user){
var information = user.subUsers[0].information.id(id);
information.arrayA.push({someId : "something"});
user.save(callback(err)); // Also tried information.save()
// without luck
}
callback(err);
}
);
This doesn't return any kind of error, but when I check the DB, the new array element hasn't been pushed (the whole document is intact).
Any help would be much appreciated. Thanks!
You should probably check out the first faq, here: http://mongoosejs.com/docs/faq.html
Mongoose doesn't create getters/setters for array indexes; without them mongoose never gets
notified of the change and so doesn't know to persist the new value. The work-around is to
use [MongooseArray set][1] available in Mongoose >= 3.2.0.
So in your case, you want to add this third line
var information = user.subUsers[0].information.id(id);
information.arrayA.push({someId : "something"});
user.subUsers.set(0, information);
Or something like that.
As of today, Mongoose is currently not prepared for multilevel nesting in an atomic way.
Therefore, even if it's going back to a kind-of relational database, in this case it's better to split the nesting into at least 2 collections, and reference using the automatically generated ObjectId.