I have the following three models:
var User = {
first_name: String,
last_name: String,
}
var Student = {
role = String,
user = {type: mongoose.Schema.Types.ObjectId, ref: 'User'}
groups = [{type: mongoose.Schema.Types.ObjectId, ref: 'Group'}],
}
var Group = {
name = String,
students = [{type: mongoose.Schema.Types.ObjectId, ref: 'Student'}],
}
My express get method looks like:
router.route('/')
.get(function(req, res){
Group.find().populate('students').exec(function(err, groups){
res.json(groups);
});
My json object returns the array of student objects a that are populated, but i am only receiving a user._id from within each of the student objects. How can I also get the user object to populate? Any info would be awesome! Thanks
You can populate across multiple levels:
router.route('/')
.get(function(req, res){
Group
.find()
.populate({
path: 'students',
// Get the student's user ids
populate: { path: 'user' }
})
.exec(function(err, groups){
res.json(groups);
});
You can read more about it here
Related
I'm working on an upvoting/downvoting application using MongoDB and Node.JS
I have created two interlinked schemas:
var mongoose = require('mongoose');
var Voters = require('./voters');
var PostSchema = new mongoose.Schema({
title: String,
link: String,
upvotes: {type: Number, default: 0},
voters: [Voters.schema],
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});
mongoose.model('Post', PostSchema);
and for voters:
var mongoose = require('mongoose');
var votersSchema = new mongoose.Schema({
voter_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
votetype: Number
});
module.exports = mongoose.model('Voters', votersSchema);
For including users in the voters array, I'm using this code:
var voterModel = new Voters();
voterModel.voter_id = req.payload._id;
voterModel.votetype = 1;
foundPost.voters.push(voterModel);
foundPost.save();
Which works just fine. For removing users I tried several methods, but none seem to work. The current one is $pull:
foundPost.update({'voters.voter_id': req.payload._id}, {$pull: {'voters': {'voter_id': req.payload._id, 'votetype': 1}}}, function(err){
if (err) { console.log(err); }
});
The update action works in the mongo shell, but not from within node. I also tried foundPost.voters.remove, but the result was the same. Also tried Voters.findOne, but the query always returns null.
Any help would be appreciated.
Use the id method first to find the voter then remove it and last save document to apply changes:
var voter = foundPost.voters.id(req.payload._id).remove();
foundPost.save(function (err) {
if (err) return handleError(err);
console.log('the voter was removed')
});
I am having problem managing the states of different types of users using Passport.js in Express.js 4.x.
I have 3 kinds user collections in my mongodb database
1. Member (has his own profile page)
2. Operator (has his own dashboard)
3. Admin (handles the backend)
I have created their separate Login/Registration systems. But only member seems to work, and the others don't. I have even written different sets of login/registration strategies for each user.
Like for the member passport.use('signup') and passport.use('login').
for operator passport.use('op-signup') and passport.use('op-login') and so on.
What I think is that I am not using the correct approach for handling users, means the collections don't need to be separated but role based in a single collection. Right ?
Here is the current mongoose models I have right now;
// Member Schema
var MemberSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
Is the above approach correct or like this ?
var UserSchema = new Schema({
username: String,
password: String,
roles: {
member: { type: Schema.Types.ObjectId, ref: 'Member' },
operator: { type: Schema.Types.ObjectId, ref: 'Operator' },
admin: { type: Schema.Types.ObjectId, ref: 'Admin' }
}
});
module.exports = mongoose.model('User', UserSchema);
// and then plug the sub models to this parent one
// Member Schema
var MemberSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
I am quite confused here and in a stuck situation, because when a user state is managed in session after login, the user object is exposed to the request object, and so it can only handle one type of user at a time, and may be member, operator and admin can't log in at the same time from the same browser.
So how do I manage all of these user as different instances in the browser ?
I am quite a newbie in Node.js and coming from a PHP background where managing user states was a breeze :)
What i would do is to add plugins, because you are duplicating username and password field, it is very redundant
models/plugins/member.js
module.exports = function(schema) {
schema.add({
// All the appropriate fields that your member schema need
role: String,
});
}
models/user.js
var member = require(./plugins/member);
var UserSchema = mongoose.Schema({
username: String,
password: String
});
UserSchema.plugins(member);
Later on when you want to check which user could access to which route, use middleware to check it
create this in your passport configuration
exports.requireRole = function(role) {
return function(req, res, next) {
if (req.user && req.user.role === role) next();
else
res.send(404);
}
}
In your route later
app.get('/profile', requireRole('member'), function(req, res) {
// do whatever you want to do
});
app.get('/dashbord', requireRole('operator'), function(req, res) {
// do whatever you want to do
});
There are a lot of ways to implement different access level to a user. This method is one of many.
The best solution would be to use schema inhertiance. That is why we use an ORM like mongoose.
var VehicleSchema = mongoose.Schema({
make : String,
}, { collection : 'vehicles', discriminatorKey : '_type' });
var CarSchema = VehicleSchema.extend({
year : Number
});
var BusSchema = VehicleSchema.extend({
route : Number
})
var Vehicle = mongoose.model('vehicle', VehicleSchema),
Car = mongoose.model('car', CarSchema),
Bus = mongoose.model('bus', BusSchema);
var accord = new Car({
make : 'Honda',
year : 1999
});
var muni = new Bus({
make : 'Neoplan',
route : 33
});
accord.save(function(err) {
muni.save(function(err) {
// vehicles are saved with the _type key set to 'car' and 'bus'
});
})
At this point in MongoDB you will have documents similar to this
{ "_type" : "car", "make" : "Honda", "year" : 1999, "_id" : ObjectId("5024460368368a3007000002"), "__v" : 0 }
{ "_type" : "bus", "make" : "Neoplan", "route" : 33, "_id" : ObjectId("5024460368368a3007000003"), "__v" : 0 }
Source
when querying
Vehicle.find({}, function(err, vehicles) {
console.log(vehicles[0]); // vehicles[0] instanceof Car === true
console.log(vehicles[1]); // vehicles[1] instanceof Bus === true
});
Checkout source / more examples by looking at briankircho little cheatsheet enter link description here
I am currently trying to learn how to work with NoSQL, coming from a relational database background. In this project, I am using Express with Mongoose.
I am struggling with callbacks as I try to merge two models together, which reference each other. I am trying to edit each item in a group of one model (Ribbits) to contain the attributes of another (Users who posted a Ribbit). Because the call to find the User associated with a Ribbit is asynchronous, I am unable to return the collection of edited Ribbits (with user info).
In my website, I have ribbits (a.k.a. tweets) which belong to users. Users can have many ribbits. In one of my pages, I would like to list all of the ribbits on the service, and some information associated with the user who posted that ribbit.
One solution I found was embedded documents, but I discovered that this is, in my case, limited to showing ribbits which belong to a user. In my case, I want to start by getting all of the ribbits first, and then, for each ribbit, attach info about who posted that.
Ideally, I'd want my schema function to return an array of Ribbit objects, so that I can then render this in my view.
// models/user.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var userSchema = Schema({
username: String,
email: String,
password: String,
name: String,
profile: String,
ribbits: [{
type: Schema.Types.ObjectId,
ref: 'Ribbit',
}]
});
module.exports = mongoose.model('User', userSchema);
// models/ribbit.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
User = require('./user');
var ribbitSchema = Schema({
content: { type: String, maxlength: 140 },
created: { type: Date, default: Date.now() },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
});
ribbitSchema.methods.getOwnerObj = function(cb) {
return User.findOne({ _id: this.owner }, cb);
}
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}, function(err, ribbits) {
console.log('Before Transform');
console.log(ribbits);
ribbits.forEach(function(ribbit) {
ribbit.getOwnerObj(function(err, owner) {
ribbit = {
content: ribbit.content,
created: ribbit.created,
owner: {
username: owner.username,
email: owner.email,
name: owner.name,
profile: owner.profile,
}
};
});
});
});
}
module.exports = mongoose.model('Ribbit', ribbitSchema);
If I understand correctly, you can use Mongoose populate method for this scenario:
ribbitSchema.statics.getAllRibbits = function(cb) {
this.find({}).populate('owner').exec(function(err, ribbits){
console.log(ribbits[0].owner)
return cb(err, ribbits);
})
}
I got 3 database models in mongoose that looks like this:
//profile.js
var ProfileSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
matches: [{ type: Schema.Types.ObjectId, ref: 'Match' }]
});
//match.js
var MatchSchema = new Schema({
scores: [{ type: Schema.Types.ObjectId, ref: 'Score', required: true }],
});
//score.js
var ScoreSchema = new Schema({
score: {type: Number, required: true},
achivement: [{type: String, required: true}],
});
And I try to populate a profile with
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches')
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});
The matches get populated but I dont get the scores in matches to populate. Is this not supported in mongoose or do I do something wrong? Populate gives me this:
{
user_token: "539b07397c045fc00efc8b84"
username: "username002"
sex: 0
country: "SE"
friends: []
-matches: [
-{
__v: 1
_id: "539eddf9eac17bb8185b950c"
-scores: [
"539ee1c876f274701e17c068"
"539ee1c876f274701e17c069"
"539ee1c876f274701e17c06a"
]
}
]
}
But I want to populate the score array in the match array. Can I do this?
Yes, you are right. I tried using Chaining of populate I got same output.
For your query please use async.js and then populate by the method mentioned below.
For more details please have a look at this code snippet. It is a working, tested, sample code according to your query. Please go through the commented code for better understanding in the code below and the link of the snippet provided.
//Find the profile and populate all matches related to that profile
Profile.findOne({
_id: mongoose.Types.ObjectId(profile_id)
})
.populate('matches')
.exec(function(err, profile) {
if (err) throw err;
//We got the profile and populated matches in Array Form
if (profile) {
// Now there are multiple Matches
// We want to fetch score of each Match
// for that particular profile
// For each match related to that profile
async.forEach(profile.matches, function(match) {
console.log(match, 'match')
// For each match related to that profile
// Populate score achieved by that person
Match.find({
_id:match.id
})
.populate('scores', 'score')
.exec(function (err, score) {
if (err) throw err;
// here is score of all the matches
// played by the person whose profile id
// is passed
console.log(score);
})
})
}
});
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});
I'm running Expressjs with mongoosejs I made the connection between the collections CustomerId as below:
.
.
/**
* Customer Schema
*/
var CustomerSchema = new Schema({
id : Number,
name: String,
joined: { type: Date, default: Date.now },
city: String
});
mongoose.model('Customer', CustomerSchema);
.
.
/**
* Order Schema
*/
var OrderSchema = new Schema({
id : Number,
products: [Schema.Types.Mixed],
total: Number,
comments: String,
customerId: {type: Number, ref: 'Customer'}
});
mongoose.model('Order', OrderSchema);
.
.
exports.customerOrders = function (req, res) {
return Order.find({customerId: req.params.customerId}, function (err, orders) {
Order.populate(orders, {path: 'customerId', model: 'Order'}, function (err, orders) {
if (!err) {
return res.json(orders);
} else {
return res.send(err);
}
});
});
};
the above code generate the following error:
{
message: "Cast to ObjectId failed for value "1" at path "_id"",
name: "CastError",
type: "ObjectId",
value: 1,
path: "_id"
}
the relation between objects is id no _id
Please help me to use the populate method in the right way.
Thanx,
Mongoose's populate functionality only supports using the _id field to find the related doc in the referenced collection.
So you can't use another field like id and you'd need to change customerId to be:
customerId: {type: ObjectId, ref: 'Customer'}
in OrderSchema and then populate it with the _id value of the customer instead.