Creating query(join) or correct the design? - javascript

I am having problem with building up some simple queries with this design. The problem is simple I want get the posts with included user information, which should be all in one array so i can pass it to the express view.
Simple and for me it seems the wrong solution would be after finding all the posts(db.post.find({email:'mo#gmail.com'}) and then looping through the posts and making one find query yo user collection and then merge the results.
Other solution would be using the DBref linking the author this may be better but i haven't found out how to make queries.
// User, the profile is not complete there will be more fields
var u = {
name: 'Michael', // Is not unique
email: 'mo#gmail.com', // Should be also unique
fbid: '4545454asdadsa'
}
db.user.insert(u);
// User can have 0 to many posts
var p = {
title: 'Suprise',
body: 'Well done',
tags: ['first', 'post', 'english'],
author: 'mo#gmail.com',
created: new Date(),
modified: new Date()
};
db.post.insert(p);
var p = {
title: 'Weather in Estonia',
body: 'Always looks bad',
tags: ['bad', 'weather', 'estonia'],
author: 'mo#gmail.com',
created: new Date(),
modified: new Date()
}
db.post.insert(p);

var p = {
title: 'Suprise',
body: 'Well done',
tags: ['first', 'post', 'english'],
author: {
name: 'Michael', // Is not unique
email: 'mo#gmail.com', // Should be also unique
fbid: '4545454asdadsa'
},
created: new Date(),
modified: new Date()
};
You embed documents, you denormalize. This is how it works. Now you can just get all the posts and dont have to query for the users because they are already there.
Data structure
I'll spell out how this can be done. I went completely overboard with the denormalization. This means you never need joins. Note that some of this can be removed if it's just cruft and never needed.
Posts collection
{
title: String,
body: String,
tags: [String],
author: {
name: String,
email: String,
fbid: String
},
created: Date,
modified: Date,
comments: [{
body: String,
created: Date,
modified: Date
}]
}
User collection
{
name: String,
email: String,
fbid: String,
posts: [{
title: String,
body: String,
tags: [String],
created: Date,
modified: Date,
comments: [{
body: String,
created: Date,
modified: Date
}]
}]
}
Comment collection
{
body: String,
created: Date,
modified: Date,
author: {
name: String,
email: String,
fbid: String
},
post: {
title: String,
body: String,
tags: [String],
created: Date,
modified: Date
comments: [{
body: String,
created: Date,
modified: Date
}]
}
}

Related

Inserting data into a double-nested array

I'll try to keep it short and simple,
The schema looks like below...
import mongoose from 'mongoose'
const QuestionSchema = mongoose.Schema({
questionTitle: { type: String, required: " title"},
questionBody: { type: String, required: "body"}
userId: { type: String},
askedOn: { type: Date, default: Date.now},
Comment:[{
commentBody: String,
userCommented: String,
userId: String,
commentedOn: { type: Date, default: Date.now},
}],
answer: [{
answerBody: String,
userAnswered: String,
userId: String,
answeredOn: { type: Date, default: Date.now},
Comment:[{
commentBody:String, ////
userCommented: String, ////
userId: String, ////
commentedOn: { type: Date, default: Date.now}, ////
}]
}]
})
export default mongoose.model("Question", QuestionSchema)
How do i fill data in the slashed part of code?? ( i.e comment section of answers)
i wanted to pass answerId with the comment data, to somehow look for that answerId in whole schema and fill my data into this comment section
You can do achieve this using $ and $push.
Example:
const updateTheCol = await Question.updateOne(
{ "answer.userId": "user id" },
{ $push: {
"answer.$.Comment": {
commentBody: "comment body",
userCommented: "user commented",
userId: "user id"
}
}
});

How to populate field in all objects of an array response?

I have a User model:
const userSchema = new Schema({
username: String,
email: String,
_id: generated
})
and a Project model:
const projectSchema = new Schema({
owner: { type: Schema.Types.ObjectId, ref: 'User' },
name: String,
_id: generated
})
I am using a request to get all projects, and would like to then populate the 'owner' field of each project with the corresponding user (or, even better, their username)
Project.find()
.populate('owner')
.then((allProjects) => {
console.log(allProjects);
res.status(200).json(allProjects);
})
The response from the find(), as seen in the console.log() is an array of objects, on which populate seems to have had no effect:
[{
_id: 4kal5mah5lam6la2lam40am3,
owner: 28eqo29roqi5lqmdka91ma01,
name: Project1
},
{
_id: 0akm40am593na7n4fnau25a,
owner: 85jan5l60oq23la1p07d8q2,
name: Project2
}]
I've tried many things with the populate call and its parameter but to no effect. The code runs, just doesn't populate the field. Any ideas?
Many thanks!

How to count number of ObjectId occurrences in an array of ObjectIds' and attach the result to each document that is being returned?

I have a Tag schema:
const TagSchema = new Schema({
label: {type: String, unique: true, minlength: 2},
updatedAt: {type: Date, default: null},
createdAt: {type: Date, default: new Date(), required: true},
createdBy: {type: mongoose.Schema.Types.ObjectId, ref: 'Account', default: null}
});
const Tag = mongoose.model('Tag', TagSchema);
Then I have a Page schema:
const PageSchema = new Schema({
tags: {type: [{type: mongoose.Schema.Types.ObjectId, ref: 'Tag'}], default: [], maxlength: 5}
});
const Page = mongoose.model('Page', PageSchema);
As you can see it contains a Tag reference in its tags array.
Now what I need to do is when I fetch all tags via /tags, I also need to count the number of times each tag is used in all Pages.
So if a Tag is used 2times across all Pages, it should set an .occurrences property on the returned tag. For example this would be a response from /tags:
[
{_id: '192398dsf7asdsdds8f7d', label: 'tag 1', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 2},
{_id: '19239asdasd8dsf7ds8f7d', label: 'tag 2', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 1},
{_id: '192398dsf7zxccxads8f7d', label: 'tag 1', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 5},
]
I would have imagined that I could achieve this pretty easily in a mongoose pre('find') hook like this:
TagSchema.pre('find', function() {
Page.count({tags: this._id}, (err, total) => {
this.occurrences = total;
});
});
However, there are two problems here:
Page.count throws an error saying it's not a function, which I don't understand why because I use it somewhere else perfectly fine. And Page has been imported properly. Suggesting that you can't use count in a hook or something similar.
this is not the Tag document.
So I guess that the way I am going about this is completely wrong.
Since I am a novice in MongoDB maybe someone can provide me with a better, working, solution to my problem?
db.Page.aggregate([
{
$unwind:"$tags"
},
{
$group:{
_id:"$tags",
occurrences:{$sum:1}
}
},
{
$lookup:{ //get data for each tag
from:"tags",
localField:"_id",
foreignField:"_id",
as:"tagsData"
}
},
{
$project:{
tagData:{$arrayElemAt: [ "$tagsData", 0 ]},
occurrences:1
}
}
{
$addFields:{
"tagData._id":"$_id",
"tagData.occurrences":"$occurrences"
}
},
{
$replaceRoot: { newRoot: "$tagData" }
}
])
Ok first, you need getQuery() this allows you to access the properties that you are finding the tag with. so if you find the tag by _id, you will have access to it in the pre hook by this.getQuery()._id
Second, you are going to need to use the "Parallel" middleware, can be found in the docs. This allows the find method to wait until the done function is called, so it is gonna wait until the tag is updated with the new occurrences number.
As the object can no be accessed inn the pre hook as it is yet to be found, you are going to have to use findOneAndUpdate method in the pre hook.
So the code should look like :
The find method :
Tag.find({ _id: "foo"}, function (err, foundTag) {
console.log(foundTag)
})
The pre hook :
TagSchema.pre('find', true, function (next, done) {
mongoose.models['Page'].count({ tags: this.getQuery()._id }, (err, total) => {
mongoose.models['Tag'].findOneAndUpdate({ _id: this.getQuery()._id }, { occurrences: total }, function () {
next()
done()
})
});
});

Nested associations for multiple accounts

I am trying to associate various login methods with the same User model in mongo.
const UserSchema = new Schema({
email: String
isVerified: { default: false, type; Boolean }
accounts: {
ref: 'Account',
type: Schema.Types.ObjectId
}
})
const AccountSchema = new Schema({
facebook: {
ref: 'FacebookAccount',
type: Schema.Types.?
},
local: {
ref: 'LocalAccount',
type: Schema.Types.?
},
twitter: {
ref: 'TwitterAccount',
type: Schema.Types.?
},
})
const LocalAccount = new Schema({
email: String,
name: String,
phone: String,
password: String,
_user: {
ref: 'User',
type: Schema.Types.ObjectId
}
})
What I would like to get the data coming back to me looking like would be:
{
_id: '12345789',
email: 'turdFerguson#gmail.com',
accounts: {
facebook: { ... }
local: { ... }
}
}
I'm really unsure about these associations though hence Schema.Types.? on the individual accounts. Also unsure if I should be using embedded vs object reference and where is appropriate. I'm going in circles trying to get the associations to match up.
I suggest you keep it simple with embedded.
Here is a quick suggestion:
const UserSchema = new Schema({
isVerified: {
default: false,
type: Boolean
},
accounts: {
local: {
email: String,
name: String,
phone: String,
password: String
},
facebook: {
// fields related to Facebook
},
twitter: {
// fields related to Twitter
}
}
})
I removed email as it seems redundant to have it since you already have accounts.local.email

Cant set headers after they are sent ALSO CASTError: Cast to [ObjectId] failed

I seem to be getting 2 errors.....
Here is the Route:
router.post("/event", isLoggedIn, function (req,res){
// get data from form and add to events array
var title = req.body.title;
var date = req.body.date;
var description = req.body.description;
var venue = req.body.venue;
var photo = req.body.photo;
var category = req.body.category;
//get user data to save to the event.
var owner = {
id: req.user._id,
username: req.user.username
};
var newEvent = {category: category, title: title, date: date, description: description, venue: venue, photos:{link: photo,date: date}, owner: owner};
//Create the event itself in the database, event will return the actual database event.
Event.create(newEvent, function(err, event){
if (err) {
console.log(err)
} else {
//This takes the event owner ID and saves it into the Event model
console.log(event);
event.owner.id = req.user._id;
//This takes the event username and saves it into the Event model
event.owner.username = req.user.username;
event.save();
//Save the event ID in the user document
console.log(event);
User.findByIdAndUpdate(
req.user._id,
{$push: {events:{"ObjectId": event._id}}},
{save: true, upsert: true, new: true},
function (err,newEventData){
if(err){
console.log("error at saving the id ..." + err)
res.redirect("/dashboard");
} else {
console.log(newEventData);
}
}
);
//Add the Event ID to the User model
console.log (owner);
};
});
res.redirect('events');
});
Here is the output of the console.log of the returned value from Mongoose and also the error.
The id of the user 583f30b1e5e7e376502762f5
Below are all the events pulled{ _id: 583f30b1e5e7e376502762f5,
username: 'asdf',
__v: 0,
favoriteMoments: [],
favoriteEvents: [],
likeEvents: [],
likeMoments: [],
friends: [],
moments: [],
events: [],
categories: [] }
{ __v: 0,
title: 'asdf',
description: 'asdf',
_id: 583f3175b6a3b376a515c146,
comments: [],
photos: { link: '', date: null },
moments: [],
category: [ '' ],
owner: { id: 583f30b1e5e7e376502762f5, username: 'asdf' } }
{ __v: 0,
title: 'asdf',
description: 'asdf',
_id: 583f3175b6a3b376a515c146,
comments: [],
photos: { link: '', date: null },
moments: [],
category: [ '' ],
owner: { id: 583f30b1e5e7e376502762f5, username: 'asdf' } }
{ id: 583f30b1e5e7e376502762f5, username: 'asdf' }
error at saving the idCastError: Cast to [ObjectId] failed for value "[{"ObjectId":"583f3175b6a3b376a515c146"}]" at path "events"
events.js:141
throw er; // Unhandled 'error' event
^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:335:11)
at ServerResponse.header (/var/www/familysite.com/node_modules/express/lib/response.js:719:10)
at ServerResponse.location (/var/www/familysite.com/node_modules/express/lib/response.js:836:15)
at ServerResponse.redirect (/var/www/familysite.com/node_modules/express/lib/response.js:874:18)
at /var/www/familysite.com/routes/eventRoute.js:67:29
at /var/www/familysite.com/node_modules/mongoose/lib/model.js:3388:16
at /var/www/familysite.com/node_modules/mongoose/lib/model.js:3388:16
at /var/www/familysite.com/node_modules/kareem/index.js:207:48
at /var/www/familysite.com/node_modules/kareem/index.js:127:16
at nextTickCallbackWith0Args (node.js:419:9)
at process._tickCallback (node.js:348:13)
Here is the schema of the Users Model:
const userSchema = new mongoose.Schema({
username: String,
password: String,
nickname: String,
firstName: String,
middleName: String,
lastName: String,
address: String,
city: String,
state: String,
phone: Number,
birthday: Date,
birthplace: String,
userCover: String,
categories: Array,
events: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Event"
}],
moments: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Moments"
}],
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}],
likeMoments: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Moments"
}],
likeEvents: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Event"
}],
favoriteEvents: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Event"
}],
favoriteMoments: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Moments"
}]
})
I have been getting no where with this Cast issue and now I have 2 errors which seems odd... getting very frustrated at this point and unsure where to go.
In the end, I have route that needs to create an event, save it event ID to the user that created it and then go to the /event page and display the data for each event.
If you look at the first block of code about 3/4 the way down...
this line:
{$push: {events:{"ObjectId": event._id}}},
Should look like this:
{$push: {events:{_id: event._id}}},
Thats it! so _id is how you tell it to be an ID.

Categories

Resources