I have architectural question about how to design my meanjs controller and routes for mongoose subdocuments.
my model looks as following:
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Customerrpc Schema
*/
var CustomerrcpSchema = new Schema({
company: {
type: String,
enum: ['Option1', 'Option2'],
required: 'Please fill company name'
},
rcp: {
type: String,
required: 'Please fill rcp'
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
/**
* Customer Schema
*/
var CustomerSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Customer name',
trim: true
},
description: {
type: String,
default: '',
//required: 'Please fill Customer description',
trim: true
},
url: {
type: String,
default: '',
//required: 'Please fill Customer url',
trim: true
},
rcp: [CustomerrcpSchema],
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Customer', CustomerSchema);
mongoose.model('Customerrcp', CustomerrcpSchema);
I tried it out by adding on the server controller the following code during the create methode:
exports.create = function(req, res) {
var customer = new Customer(req.body);
customer.user = req.user;
var rcp = new Customerrcp({
company: 'Option1',
rcp: 'dumm',
user: req.user
});
customer.rcp = rcp;
customer.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(customer);
}
});
};
This works perfectly fine. Now my question is what is the best procedure to create / modify / remove a subdocument from the maindocument?
I thought of always work with the main document 'Customer' but this brings several issues with it that i dont like, like saving always the hole document. Since i do have a uniq _id for each subdocument i guess there must be a better way.
What i would like to have is a controller only for the subdocument with the create / save / remove statement for it. Is this even possible?
As far as i understand it:
to create a new subdocument i need the following: _id of the main document for the mongoose query. So i need a service which would handover the _id of the maindocument to the controller of the selected subdocument. I was able to do this.
But im insecure if this is the proper way.
Any Ideas?
Cheers,
Michael
Related
How to fetch posts by user and all his followings posts (Mongodb, Mongoose, Nodejs)
User Schema
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true, trim: true },
lastName: { type: String, required: true, trim: true },
});
userSchema.set('timestamps', true);
export default mongoose.model('user', userSchema);
Followers Schema
const followSchema = new mongoose.Schema({
follower: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
following: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
status: { type: Boolean, default: true }
});
followSchema.set('timestamps', true);
export default mongoose.model('follow', followSchema);
Posts Schema
const postSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
contents: { type: String, trim: true },
photo: { type: String }
});
postSchema.set('timestamps', true);
export default mongoose.model('post', postSchema);
Thank you in advance! :)
Hy Alamghir its happy to see you here and sorry that you still did not get the answer what i can suggest you after seeing your schema that i think there is no need to create three collections you only need 2 schema first one
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true, trim: true },
lastName: { type: String, required: true, trim: true },
followers:[]
});
userSchema.set('timestamps', true);
export default mongoose.model('user', userSchema);
Now in followers array just push the ids of users who followed this user Now it would be very easy for you to get posts done by these people like this let suppose you have user data in userData variable now you can do this
db.postSchema.find($or:[{userId:userData._id},{userId:$in:userData.followers}])
Sorry, got your question wrong.
There might be a better solution but what you should be able to do is this:
(this gets the posts of the people that are following your original user. If you did mean it the other way around, just switch :) )
// get the user's posts:
var theUserPosts = await postSchema.find({userId: userData._id}).exec();
// get the follower-IDs:
var theFollowersIDs = await followSchema.find({follower: userData._id, status: true}, 'following').exec();
// get the posts of those followers:
var followerPosts = await postSchema.find({userId: {$in: theFollowersIDs}}).exec();
Can you try this and tell us if this works for you?
Im using MongoDb, and I have a workspace schema with mongoose (v4.0.1):
var Workspace = new mongoose.Schema({
name: {
type: String,
required: true
},
userId: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
}
});
And a user schema:
var User = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
organisation: {
type: String,
required: true
},
location: {
type: String,
required: true
},
verifyString: {
type: String
},
verified: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
},
isAdmin: {
type: Boolean,
default: false
}
});
So the Workspace userId is the ObjectID from the User document.
When Im logged in as an adminstrator, I want to get all workspaces, as well as the email of the user that owns the workspace.
What Im doing is getting very messy:
Workspace.find({}).exec.then(function(workspaceObects){
var userPromise = workspaceObects.map(function(workspaceObect){
// get the user model with workspaceObect.userId here
});
// somehow combine workspaceObjects and users
});
The above doesnt work and gets extremely messy. Basically I have to loop through the workspaceObjects and go retrieve the user object from the workspace userId. But because its all promises and it becomes very complex and easy to make a mistake.
Is there a much simpler way to do this? In SQL it would require one simple join. Is my schema wrong? Can I get all workspaces and their user owners email in one Mongoose query?
var Workspace = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'User' //add this to your schema
}
});
Workspace.find().populate('userId').exec( (err, res) => {
//you will have res with all user fields
});
http://mongoosejs.com/docs/populate.html
Mongo don't have joins but mongoose provides a very powerfull tool to help you with you have to change the model a little bit and use populate:
Mongoose population
You have to make a few changes to your models and get the info of the user model inside your workspace model.
Hope it helps
I want to display friends of Authenticated user in angularjs page.
// Find a list of Friends
$scope.find = function() {
$scope.friends = Authentication.user.friends;
$scope.firstFriendName = Authentication.user.friends;
};
I'm using mongoose with nodejs.(MeanJS)
How can I populate friends of current user in meanjs?
Thanks.
Either extend the user object or add a custom model for example Person , that consists of both user reference, and list of friends:
/**
* Person Schema
*/
var PersonSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Person name',
trim: true
},
desc:{
type: String,
default: '',
trim: true
},
friends:[{
rate: Number,
date: Date
}],
,
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Person', PersonSchema);
Then write the corresponding controller and views. Or just use meanJS generators:
yo meanjs:crud-module Persons
Then add the appropriate changes to the module. By the way, there is a package for something similar that seems to patch the User schema: moongose-friends
var PersonSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Person name',
trim: true
},
desc:{
type: String,
default: '',
trim: true
},
friends:[{
type: Schema.ObjectId,
ref: 'User'
}],
,
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
Then you would use the populate() method built into Mongoose which MEANJS uses. You can actually see how this works if you use the generators to build a CRUD module. Then you can look at the express controller for a specific view and you will see how it uses populate() (this is pretty much what you will see for a list function in the express controller when you generate a crud module.
Friends.find().sort('-created').populate('user', 'displayName').exec(function(err, friends) {
// Handle error & return friends code here
});
Hope that helps. You would then want to look at the angular controller and view so that you could modify the object in the controller and reflect it in the view.
can someone please help me with population of this schema? I need to populate array of Staff by their userId.
var PlaceSchema = new Schema ({
name: { type: String, required: true, trim: true },
permalink: { type: String },
country: { type: String, required: true },
...long story :D...
staff: [staffSchema],
admins: [adminSchema],
masterPlace:{ type: Boolean },
images: []
});
var staffSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account' },
role: { type: Number }
});
var adminSchema = new Schema ({
userId: { type: Schema.Types.ObjectId, ref: 'Account'}
})
var Places = mongoose.model('Places', PlaceSchema);
I tried to use this query, but without success.
Places.findOne({'_id' : placeId}).populate('staff.userId').exec(function(err, doc){
console.log(doc);
});
Polpulation is intended as a method for "pulling in" information from the related models in the collection. So rather than specifying a related field "directly", instead reference the related fields so the document appears to have all of those sub-documents embedded in the response:
Places.findOne({'_id' : placeId}).populate('staff','_id')
.exec(function(err, doc){
console.log(doc);
});
The second argument just returns the field that you want. So it "filters" the response.
There is more information on populate in the documentation.
So I've got these schemas:
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Comment Schema
*/
var CommentSchema = new Schema({
post_id: {
type: Schema.Types.ObjectId,
ref: 'Post',
required: true
},
author:{
type: String,
required: true
},
email:{
type: String,
required: true
},
body: {
type: String,
required: true,
trim: true
},
status: {
type: String,
required: true,
default: 'pending'
},
created: {
type: Date,
required: true,
default: Date.now
},
meta: {
votes: Number
}
});
/**
* Validations
*/
CommentSchema.path('author').validate(function(author) {
return author.length;
}, 'Author cannot be empty');
CommentSchema.path('email').validate(function(email) {
return email.length;
}, 'Email cannot be empty');
CommentSchema.path('email').validate(function(email) {
var emailRegex = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
return emailRegex.test(email);
}, 'The email is not a valid email');
CommentSchema.path('body').validate(function(body) {
return body.length;
}, 'Body cannot be empty');
mongoose.model('Comment', CommentSchema);
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
monguurl = require('monguurl'),
Schema = mongoose.Schema;
/**
* Article Schema
*/
var PostSchema = new Schema({
title: {
type: String,
required: true,
trim: true
},
author:{
type: String,
required: true,
default: 'whisher'
},
slug: {
type: String,
index: { unique: true }
},
body: {
type: String,
required: true,
trim: true
},
status: {
type: String,
required: true,
trim: true
},
created: {
type: Date,
required: true,
default: Date.now
},
published: {
type: Date,
required: true
},
categories: {
type: [String],
index: { unique: true }
},
tags: {
type: [String],
required: true,
index: true
},
comment: {
type: Schema.Types.ObjectId,
ref: 'CommentSchema'
},
meta: {
votes: Number
}
});
/**
* Validations
*/
PostSchema.path('title').validate(function(title) {
return title.length;
}, 'Title cannot be empty');
PostSchema.path('body').validate(function(body) {
return body.length;
}, 'Body cannot be empty');
PostSchema.path('status').validate(function(status) {
return /publish|draft/.test(status);
}, 'Is not a valid status');
PostSchema.plugin(monguurl({
source: 'title',
target: 'slug'
}));
mongoose.model('Post', PostSchema);
by an api I query Post like
exports.all = function(req, res) {
Post.find().sort('-created').exec(function(err, posts) {
if (err) {
res.jsonp(500,{ error: err.message });
} else {
res.jsonp(200,posts);
}
});
};
How to retrieve how many comments has the post ?
I mean I want an extra propriety in post object
like post.ncomments.
The first thing I think of is adding an extra
field to the post schema and update it whenever a user
add a comment
meta: {
votes: Number,
ncomments:Number
}
but it seems quite ugly I think
If you want the likely the most efficient solution, then manually adding a field like number_comments to the Post schema may be the best way to go, especially if you want to do things like act on multiple posts (like sorting based on comments). Even if you used an index to do the count, it's not likely to be as efficient as having the count pre-calculated (and ultimately, there are just more types of queries you can perform when it has been pre-calculated, if you haven't chosen to embed the comments).
var PostSchema = new Schema({
/* others */
number_comments: {
type: Number
}
});
To update the number:
Post.update({ _id : myPostId}, {$inc: {number_comments: 1}}, /* callback */);
Also, you won't need a comment field in the PostSchema unless you're using it as a "most recent" style field (or some other way where there'd only be one). The fact that you have a Post reference in the Comment schema would be sufficient to find all Comments for a given Post:
Comments.find().where("post_id", myPostId).exec(/* callback */);
You'd want to make sure that the field is indexed. As you can use populate with this as you've specified the ref for the field, you might consider renaming the field to "post".
Comments.find().where("post", myPostId).exec(/* callback */);
You'd still only set the post field to the _id of the Post though (and not an actual Post object instance).
You could also choose to embed the comments in the Post. There's some good information on the MongoDB web site about these choices. Note that even if you embedded the comments, you'd need to bring back the entire array just to get the count.
It looks like your Post schema will only allow for a single comment:
// ....
comment: {
type: Schema.Types.ObjectId,
ref: 'CommentSchema'
},
// ....
One consideration is to just store your comments as subdocuments on your posts rather than in their own collection. Will you in general be querying your comments only as they related to their relevant post, or will you frequently be looking at all comments independent of their post?
If you move the comments to subdocuments, then you'll be able to do something like post.comments.length.
However, if you retain comments as a separate collection (relational structure in a NoSQL DB-- there are sometimes reasons to do this), there isn't an automatic way of doing this. Mongo can't do joins, so you'll have to issue a second query. You have a few options in how to do that. One is an instance method on your post instances. You could also just do a manual CommentSchema.count({postId: <>}).
Your proposed solution is perfectly valid too. That strategy is used in relational databases that can do joins, because it would have better performances than counting up all the comments each time.