I am currently following a tutorial on implementing a database (Mongoose) in a simple website created using Express-framework. I do not have any problem understanding the concept of models, but I fail to make sense of the lines following the comment "Virtual for book's URL" in the code attached. How do these lines operate, and what role does having a virtual property have in this context?
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookSchema = new Schema(
{
title: {type: String, required: true},
author: {type: Schema.Types.ObjectId, ref: 'Author', required: true},
summary: {type: String, required: true},
isbn: {type: String, required: true},
genre: [{type: Schema.Types.ObjectId, ref: 'Genre'}]
}
);
// Virtual for book's URL
BookSchema
.virtual('url')
.get(function () {
return '/catalog/book/' + this._id;
});
//Export model
module.exports = mongoose.model('Book', BookSchema);
Virtuals are typically used for computed properties on documents.
BookSchema
// 'url' is the name of the virtual
.virtual('url')
.get(function () {
// you are computing dynamic url
return '/catalog/book/' + this._id;
});
Important thing about virtuals is it does not store anything on database. Imagine you stored "name" and "lastname" but you want to display fullname without having "fullname" column. You would be using repeated data on database if you add fullname property to your schema, this will slow down and will also cost you. Instead you write virtual:
YourSchema.get(function(){
return this.firstname + ' ' + this.lastname
})
You can also go one step further and return fullname property as it is in a column in database. mongoose Schema takes options object
let options={
toJSON:{
virtuals: true
}
}
set the virtual
let YourSchemaVirtual=YourSchema.virtual('fullname')
now when querying the database, get the collection
YourModel.findOne({firstname:"name"}).exec(function(err,user){
res.send(user)
})
In return value you will see fullname field computed as this.firstname + ' ' + this.lastname even though you have no fullname stored in database.
Related
long time Java programmer here trying to figure out Node.js
I am trying to cascade a delete to child objects using the pre middleware, but no child delete is happening, while the parent deletes without a hitch. Leaving my database full of sad orphans. When I started logging to the console I see that the reference to 'this' is empty. For most cases this seems to be a problem of using ==> to create the function, but I am not doing so:
GameSession(parent):
var mongoose = require('mongoose');
var TimeLineEvent = require('../models/timelineevent');
//Define a schema
var Schema = mongoose.Schema;
var GameSessionSchema = new Schema({
name: {type: String, required: true},
gameMasterId : {type: Schema.Types.ObjectId, ref: 'GameMaster', required: true},
});
GameSessionSchema.pre('findOneAndDelete', function(next) {
console.log('GameSessionSchema.pre findOneAndDelete this ='+this);
console.log('GameSessionSchema.pre findOneAndDelete id ='+this._id);
TimeLineEvent.deleteMany({gameSessionId: this._id}).exec();
next();
});
//Export function to create "SomeModel" model class
module.exports = mongoose.model('GameSessionModel', GameSessionSchema );
TimeLineEvent(child):
//Require Mongoose
var mongoose = require('mongoose');
//Define a schema
var Schema = mongoose.Schema;
var TimeLineEventSchema = new Schema({
name: {type: String, required: true},
gameSessionId: {type: Schema.Types.ObjectId, ref: 'GameSession', required: true},
time: {type: Number, required: true},
nextAction: {type: Number}
});
module.exports = mongoose.model('TimeLineEventModel', TimeLineEventSchema );
This is what the console shows when the pre method is called:
GameSessionSchema.pre findOneAndDelete this =[object Object]
GameSessionSchema.pre findOneAndDelete id =undefined
Can you see what I am missing? Thanks!
From the documentation: "In query middleware functions, this refers to the query."
If you use console.log(this) you will be able to view the full Query object. You might find this.model references what you need.
I have the below code in my application
const citySchema = new Schema({
cityName: {
type: String,
required: true,
},
citizen:[{
type: Schema.Types.ObjectId,
ref: "Citizen",
}],
});
module.exports = mongoose.model("City", citySchema);
const citizenSchema = new Schema({
citizenName: {
type: String,
required: true,
},
city:{
type: Schema.Types.ObjectId,
ref: "City",
},
});
module.exports = mongoose.model("Citizen", citizenSchema);
router.post('/', (req, res) => {
// req.body.cityName
// req.body.citizenName
})
In my POST request , I receive both city name(new city) and citizen name(new citizen) that are not present . But I want both these schemas to be updated properly .
City should contain Citizen references
Citizen should contain City reference
How can I do that ? Kindly help
Rather than doing that, I think you'll be much better to apply referencing via pre-hook middleware in your data model.
The code should be like this:
const citySchema = new Schema({
cityName: {
type: String,
required: true,
},
citizen:[{
type: Schema.Types.ObjectId,
ref: "Citizen",
}],
});
// Query middleware to populate the 'citizen' attribute every time the 'find' function is called.
citySchema.pre(/^find/, function (next) {
this.populate('citizen');
next();
});
module.exports = mongoose.model("City", citySchema);
const citizenSchema = new Schema({
citizenName: {
type: String,
required: true,
},
city:{
type: Schema.Types.ObjectId,
ref: "City",
},
});
citizenSchema.pre(/^find/, function (next) {
this.populate('city');
next();
});
module.exports = mongoose.model("Citizen", citizenSchema);
If you want to select just the ID and not the 'full data', you could do it like this for example:
citizenSchema.pre(/^find/, function (next) {
this.populate({
path: 'city',
select: '_id',
});
next();
});
Explanation:
By doing this, every time you call Mongoose's functions like findByIdAndUpdate, find, findOne, the referenced data will appear in the city and citizen attribute. This is actually more efficient rather than updating each time there is a new data.
The populate method is used to populate the attributes with data from another data model.
The object that I inserted into the populate method is used to get the 'name' of the model (in the path), and to select what kind of data to take from the referenced model. In this case, I only want to take the _id attribute.
Here is what I have. I created a project model that references a user model for an array of members.
var ProjectSchema = new mongoose.Schema(
{title: {
type: String,
required: true
},
members: [
{user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users'
}
}],
});
User Schema (I have code that creates a model from both of these schemas)
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
In a separate file, I want to export the JSON of a found project and include the information of the users in the members array, but I am not sure how to do that. This is the code I have right now.
const project = await Project.findById(req.params.proj_id).populate(
'members'
);
res.json(project);
It has no trouble finding the project but the only information I can get on the members is their id. I tried using for loops to gather the information from members separately using the id that I can get from the project, but the code gets messy and I am hoping to find a simpler way to do it.
You can use mongoose populate query to get all members associated with a project. It should populate array of objects users associated to a project. You should be doing something like this:
const project = await Project.findById(req.params.proj_id)
await project.populate('members').execPopulate()
console.log(project.members)
Mongoose docs for the reference: Populate
You can give the model to your mongoose populate.
const project = await Project.findById(req.params.proj_id)
.populate({
'members',
model: UserModel
})
.exec()
res.json(project);
keep in mind you've to create UserModel just like
const UserModel = mongoose.model('User', userSchema);
I Set manually an uid to each item in my collections...I Want To Know It's Possible that I use uid for populate?
I Dont Want Use from '_id' because Have Many collections In My DB And should change many things...somthing like this :
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var PersonSchema = new Schema({
name : String
, age : Number
, stories : [{ type: String, ref: 'Story' }]
});
var StorySchema = new Schema({
_creator : { type: String, ref: 'Person' }
, title : String
, fans : [{ type: String, ref: 'Person' }]
});
var Story = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);
No, it is not possible to have a mongodb document without '_id' , if you want to avoid duplication of ids, you can put uid in '_id' before saving the document.
If you keep uid seperately, '_id' will get autopopulated as ObjectId.
Here is a clear example of how to do it (https://mongoosejs.com/docs/populate.html):
Story.
find(...).
populate({
path: 'fans',
select: 'name -_id' // <---------------'-_id' exclude _id field.
}).
exec();
I have recently started using mongoDB and mongoose for my new node.js application. Having only used relational databases before I am struggling to adapt to the mongoDB/noSQL way of thinking such as denormalization and lack of foreign key relationships. I have this relational database design:
**Users Table**
user_id
username
email
password
**Games Table**
game_id
game_name
**Lobbies Table**
lobby_id
game_id
lobby_name
**Scores Table**
user_id
game_id
score
So, each lobby belongs to a game, and multiple lobbies can belong to the same game. Users also have different scores for different games. So far for my user schema I have the following:
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
So my question is, how would I go about structing the relational design into mongoDB/mongoose schemas? Thanks!
EDIT 1
I have now tried to create all the schemas but I have no idea if this is the right approach or not.
var UserSchema = new mongoose.Schema({
_id: Number,
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
_id: Number,
name: String
});
var LobbySchema = new mongoose.Schema({
_id: Number,
_game: { type: Number, ref: 'Game' },
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : { type: Number, ref: 'User' },
_game : { type: Number, ref: 'Game' },
score: Number
});
Mongoose is designed in such a way that you can model your tables relationally with relative ease and populate relational data based on the ref you defined in the schema. The gotcha is that you need to be careful with populating. If you populate too much or nest your populations you will run into performance bottle necks.
Your approach in Edit 1 is largely correct however you usually don't want to populate a remote ref based on a Number or set the _id of a model to a Number since mongo uses it's own hashing mechanism for managing the _id, this would usually be an ObjectId with _id implied. Example as shown below:
var ScoreSchema = new mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
game : { type: Schema.Types.ObjectId, ref: 'Game' },
score: Number
});
If for some reason you need to maintain a number id for your records consider calling it uid or something that won't conflict with mongo / mongoose internals. Good luck!
First of all, you are hitting on some good points here. The beauty of Mongoose is that you can easily connect and bind schemas to a single collection and reference them in other collections, thus getting the best of both relational and non-relational DBs.
Also, you wouldn't have _id as one of you properties, Mongo will add it for you.
I've made some changes to your schemas using the mongoose.Schema.Types.ObjectId type.
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
name: String
});
var LobbySchema = new mongoose.Schema({
_game: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
_game : {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
score: Number
});
This will allow you to query your database and populate any referenced collections and objects.
For example:
ScoreSchema.find({_id:##userIdHere##})
.populate('_user')
.populate('_game')
.exec(function(err, foundScore){
if(err){
res.send(err)
} else {
res.send(foundScore)
}
}
This will populate the related user and game properties.
As you edited the post, I think it would be good. At least not bad :)
Check Mongoose Query Population. It's very useful to get related data.
var mongoose = require('mongoose'),
ObjectId = mongoose.Schema.Types.ObjectId
// code, code, code
function something(req, res) {
var id = req.params.id
// test id
return Lobby.findOne({_id: new ObjectId(id)})
.populate('_game')
.exec(function(error, lobby) {
console.log(lobby._game.name);
});
}
Two ways (that I know of). You store an id (that is indexed) and once you query the first table, you then query the second table to grab info from that, as there are no joins. This means that if you grab say, user id's from one table, you will then need to make multiple queries to the user table to get the user's data.
The other way is to store it all in one table, even if it's repetitive. If all you need to store is for example, a user's screen name with something else, then just store it with the other data, even if it's already in the user table. I'm sure others will know of better/different ways.