Reputation: 1913
I create an API with mongoose in Node.js. I saved my data in a collection Transactions which gives some references from other collections objects:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const transactionSchema = new Schema({
status: String,
_user: { type: Schema.Types.ObjectId, ref: 'User' },
_borne: { type: Schema.Types.ObjectId, ref: 'Borne' },
createdAt: Date,
updatedAt: Date
});
When I do a query on transactions, I would get the Borne object instead of its id as it is saved in my database. I do not directly save it as a Borne object because some changes could appear in my Borne (or User) objects, and I would it to be saved on every Transaction objects.
So I tried to use virtual or path (override), but it doesn't change my output, and I also don't know if it's the right way to do it:
// In this example, I try to change the status output by "new status" to test if it works, and it doesn't
transactionSchema.path('status')
.get(function(value) {
return "new status";
})
});
The output is the same as previously.
Populate
is the solution, but doesn't workCurrently, I'm loading my models like that in my index.js
file:
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const apn = require('apn');
const keys = require('./config/keys');
require('./app/models/Borne');
require('./app/models/User');
require('./app/models/Transaction');
require('./app/models/Comment');
require('./app/models/Notification');
const app = express();
const apnProvider = new apn.Provider(keys.apns.options);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
mongoose.connect(keys.mongoURI, (err, database) => {
if (err) return console.log(err);
require('./app/routes')(app);
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log('We are live on ' + PORT);
});
});
Then, here is an example of model:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const transactionSchema = new Schema({
status: String,
details: {
amount: { type: Number }, // money
quantity: { type: Number }, // power consumed
date: { type: Date },
city: { type: String }
},
logs: [
{
state: String,
date: Date
}
],
_user: { type: Schema.Types.ObjectId, ref: 'User' },
_borne: { type: Schema.Types.ObjectId, ref: 'Borne' },
createdAt: Date,
updatedAt: Date
});
mongoose.model('transactions', transactionSchema);
Finally, here is where I call the populate
. It doesn't work:
const mongoose = require('mongoose');
const User = mongoose.model('users');
const Transaction = mongoose.model('transactions');
const Borne = mongoose.model('bornes');
const Comment = mongoose.model('comments');
module.exports = app => {
app.get('/v1/user/:id/transactions', async (req, res) => {
const ObjectID = require('mongodb').ObjectID;
var id = req.params.id;
var existingUser;
if (req.params.id == 'me' && req.user) {
id = req.user.id;
existingUser = req.user;
} else {
existingUser = await User.findOne({ _id: new ObjectID(id) });
}
if (existingUser) {
const transactions = await Transaction.find({
_user: new ObjectID(id),
status: { $nin: ['booked', 'charging', 'charged', 'left'] }
}).populate('_user').populate('_borne').sort({ updatedAt: -1 });
// ...
res.status(200);
res.send({
statusCode: 200,
data: transactions
});
}
});
};
Upvotes: 5
Views: 10152
Reputation: 253
I think to have a fast and robust query, you should take a look at Aggregate Query with moongodb.
Query the transaction collection (matching the correct _id) , then unwind _user property, make a lookup to the user collection (similar to JOIN in SQL), unwind _born collection, lookup the born collection.
Seems a bit complicated like this but it's very powerfull and fast.
Upvotes: 0
Reputation: 5534
According to MongoDB Documentation, you have to "manually" make a second query if you want to get the object pointed by the reference.
But Mongoose
offers the populate
method, which allows you to replace references with the right documents.
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s).
So, in your case, you could do something like that :
var transactionModel = mongoose.model('Transaction', transactionSchema);
transactionModel
.find({})
.populate('_user')
.populate('_borne')
.exec((err, transaction) => {
if (err) return handleError(err);
// deal with your transaction
});
I just read your edit, could you try that for me :
Remove all the require('./app/models/xxx')
in your index.js
file.
At the end of your models :
module.exports = mongoose.model('xxx', xxxSchema);
And then in your routes / controllers :
const User = require('/app/models/users');
const Borne = require('/app/models/borne');
const Transaction = require('/app/models/transaction');
So your models are created the same time as your schemas and you're sure that's the correct order.
Hope it helps,
Best regards
Upvotes: 11