Querying many to one on mongoose - mongodb

Supposed I have two model User and Car, and they have one to many, many to one relationship
User Model
#modelOptions({ schemaOptions: { collection: 'users' } })
export class User extends TimeStamps {
#prop({ required: true })
name!: string
#prop({ required: true })
age!: number
#prop({ ref: () => Car })
cars?: Ref<Car>[]
}
Car Model
#modelOptions({ schemaOptions: { collection: 'cars' } })
export class Car extends TimeStamps {
#prop({ required: true })
model!: string
#prop({ required: true })
plateNumber!: string
#prop({ required: true })
type!: string
#prop({ ref: () => User })
user?: Ref<User>
}
I can save many cars into user using
async attachCarsToUser(userId: string, carsId: string[]) {
return this.findOneAndUpdate(
{ _id: userId },
{
$addToSet: {
cars: {
$each: carsId
}
}
}
)
}
and i can get the result by using:
async getAllUsers() {
return await this.usersRepository.find().populate('cars').lean()
}
Now, I can get the result correctly as it saved many cars on the user.
My question is how can I get like a bidirectional relationship on these two models such that when I query using these code:
async getAllCars() {
return await this.carsRepository.find().populate('user').lean()
}
I want to get the specific user on a car, however it doesn't work that way. and it produce result like this even if theres a field for user and also I already used the populate()
Im fairly new to monggodb, any idea what's the approach to this? Do i need to save the id of user on a car too?
BTW I used typegoose and under the hood it uses mongoose.

Related

mongoose/typegoose db with multiple collections

How do I set up a mongoose/typegoose connection with multiple collections?
class Onboarding {
#prop({ index: true, required: true })
date: string;
}
class Metrics {
#prop({ index: true, required: true })
date: string;
}
MetricsUtil.MetricsModel = getModelForClass(Metrics, {
existingMongoose: mongoose,
schemaOptions: { collection: 'metrics' },
});
MetricsUtil.OnboardingModel = getModelForClass(Onboarding, {
existingMongoose: mongoose,
schemaOptions: { collection: 'onboarding' },
});
await mongoose.connect(uri, options);
When I initialize the db, it only creates the collection 'metrics' and not 'onboarding'.
And when I try to write something to the onboarding collection, "MetricsUtil.OnboardingModel.findOneAndUpdate" , it writes to the 'metrics' collection.
Not sure what I'm missing here. Been trying to find examples on how to set this up.

How to get All items associated to a user in mongoose

I have a collection of Items that a particular make and perform a transaction. In my schema, I associate the userId to each item. I want to be able to display as a list all the items that the user owns.
Here I have managed to total up all sizes of each item but I cant work out a way how to get a total for each user
{
id: Number,
x: Number,
y: Number,
xSize: String,
ySize: String,
imageSource: String,
user: { type: mongoose.Schema.ObjectId, ref: 'User' }
},
{ timestamps: true }
);
const UserSchema = new Schema(
{
id: Number,
name: String,
website: String,
},
{ timestamps: true }
);
Item.find({}, function (err, items) {
var itemMap = {};
items.forEach(function (item) {
itemMap[item._id] = item;
});
var countedNames = items.reduce(function (allNames, name) {
if (name.xSize in allNames) {
allNames[name.xSize]++;
}
else {
allNames[name.xSize] = 1;
}
return allNames;
}, {});
Essentially i want to get a list basically saying
{name:"Dave", website:"www.google.com, items:[item1, item2]}
where item1 and item2 relate to the item schema
You should rewrite your UserSchema to contain a reference to the item, in this format:
const UserSchema = new Schema(
{
id: Number,
name: String,
website: String,
items:
[{
item: {type: mongoose.Schema.ObjectId, ref: 'Item'}
}]
},
{ timestamps: true }
);
This will simply allow you to perform the following query:
User.find({}).populate('Item')
Which would return the User document, and all items associated under the document.
You could do the following:
let users = User
.find({})
.populate('Item')
.exec(function (err, users) {
if (err) { console.log(err); }
console.log(users)
}
Rewriting the schema will make querying users for their items much easier.

MongoDB, properly setting OneToMany, ManyToOne relations methods

I'm trying to properly setup oneToMany, ManyToOne relation method, so far my result (pseudocode below). I'm using Typegoose (basically is it Mongoose with types), if you never used it before, it's nothing to worry about, this would be about architecture anyway. I wanna discuss "createBook" method, as you can see, I'm using "await, await" in sequence which makes it really inefficient, I can use promises and at the end of the method write Promise.all(bookQuery, authorQuery), but I still have a problem with error handling, what if one of them fail, other still can succeed, which is not good (properly: if one them fail, other should retrieve any changes, which that made). How I should properly, efficiently write "createBook" method?
class Author extends Typegoose {
#prop()
fullName: string;
#prop({ ref: Book, default: [] })
books: Ref<Book>[];
}
class Book extends Typegoose {
#prop()
title: string;
#prop({ ref: Author })
author: Ref<Author>;
}
async createBook(title, authorId): Promise<Book> {
const book = await new this.bookRepository({
title,
author: ObjectId(authorId)
}).save();
await this.authorRepository.updateOne(
{ _id: authorId },
{ $push: { books: Types.ObjectId(book.id) } }
);
return book;
}
First way is this
async createBook(title, authorId): Promise<Book> {
const book = new this.bookRepository({
title,
author: ObjectId(authorId)
}).save();
const author = this.authorRepository.updateOne(
{ _id: authorId },
{ $push: { books: Types.ObjectId(book.id) } }
);
try {
await Promise.all([book, author])
return book;
} catch (e) {
console.log(e)
await Promise.all([
this.bookRepository.deleteOne({_id: book._id}),
this.authorRepository.updateOne(
{ _id: authorId },
{ $pull: { books: Types.ObjectId(book.id) }
}
)]
}
}
The second way is to use MongoDB Transactions. However, it comes from Mongo 4.0 version and it used only on Replica Set. In addition, from 4.2 version Transactions will be available also on sharded collections

Fetch user details based on userId which is referenced field in userDetails table

Database: MongoDB
Backend: Node.js
There are two tables in the database: User and userDetails. ObjectId in User table has been used as a reference in userDetails table as userId. Now based on this userId I want to display userDetails.
I have tried to make a function and tried to test that in postman but I get null data. When I test this function in api testing tool postman, I don't get any user Details. I am not able to understand why I am not getting any data.
Here is the function:
function getUserDetailByUserId(req, res) {
userDetails.find(req.params.userId, (error, detail) => {
if (error)
res.send(error);
res.json(detail);
}).populate(userId);
}
use ObjectId
new mongoose.mongo.ObjectID(req.params.userId)
function getUserDetailByUserId(req, res) {
userDetails.findOne({userId: new mongoose.mongo.ObjectID(req.params.userId)})
.populate(userId)
.exec(function(error, detail){
if (error)res.send(error);
res.json(detail);
});
}
userDetail Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const userDetailSchema = new Schema({
dateOfBirth: {
type: Date,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
interest: {
type: String,
required: true
},
remarks: {
type: String,
},
userId: {
type: Schema.Types.ObjectId, // Referencing the Registered Users in User Model by their ObjectId reference.
ref: 'User' // Creating a relation between User and UserDetails Model through unique ObjectId.
}
});
module.exports = mongoose.model('UserDetails', userDetailSchema);

Populate does not retrieve the whole referenced object just the ids

I've been reading a few answers regarding this and yet I still can't get it to work.
My model objects aren't deeply nested and are quite simple. It's events that have a list of users attending them and users that have a list of events they've attended. like so:
let DinnerSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title:{type: String, require: true},
attending: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
and the users:
let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{ type: String, require: true },
password: {type: String ,required: true},
dinners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Dinner'
}]
})
And for clarity here's the entire route that's using populate:
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('User', 'name') //I'VE TRIED ADDING 'name' BASED ON SOME ANSWERS I SAW
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
If I understand correctly in the database itself there should only be a reference to the other model and not actually all of it's fields and the join happens with the populate. My db structure show's just the reference so that's ok.
I've tried specifying the name of the fields i'm after (the name field in this case) but that didn't work.
My population result always looks like the following and doesn't show any other fields except for the _id one:
{
_id: 57ebbf48bd6914036f99acc7,
date: 2016-09-27T22:00:00.000Z,
title: '1',
__v: 0,
attending: [ 57ebbcf02c39997f9cf26891, 57ebbdee098d3c0163db9905 ]
}
What am I screwing up here?
In mongoose populate receives 4 parameters.
path
selection(fields to be return) ,
condition
options (like {limit:10})
In your case you are not passing right path to populate. It should be
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('attending', 'name')
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
Now it will return all the names of attending users.
you need to populate attending - that's your user reference in the dinner schema