I have been following a tutorial on the MDN site for Node/ express/ mongoose. It may be familiar to many people but I will put the code down anyway. What I want to do is create a view that is similar to the book_list page, however, I wish to have the ability to send the book instances with each book (details will follow). In other words I wish to be able to have the BookInstances for each book as part of the book object on the list page - it is mainly for the count (or length) but I may wish to also use it in other ways.
The book model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookSchema = new Schema({
title: {type: String, required: true},
author: { type: Schema.ObjectId, ref: 'Author', required: true },
summary: {type: String, required: true},
isbn: {type: String, required: true},
genre: [{ type: Schema.ObjectId, ref: 'Genre' }]
});
// Virtual for this book instance URL.
BookSchema
.virtual('url')
.get(function () {
return '/catalog/book/'+this._id;
});
// Export model.
module.exports = mongoose.model('Book', BookSchema);
BookInstance Schema part of the Model:
var BookInstanceSchema = new Schema(
{
book: { type: Schema.Types.ObjectId, ref: 'Book', required: true },//reference to the associated book
imprint: { type: String, required: true },
status: { type: String, required: true, enum: ['Available', 'Maintenance', 'Loaned', 'Reserved'], default: 'Maintenance' },
due_back: { type: Date, default: Date.now }
}
);
The book_list controller:
// Display list of all Books.
exports.book_list = function(req, res, next) {
Book.find({}, 'title author')
.populate('author')
.exec(function (err, list_books) {
if (err) { return next(err); }
//Successful, so render
res.render('book_list', { title: 'Book List', book_list: list_books });
});
};
The book detail controller:
// Display detail page for a specific book.
exports.book_detail = function(req, res, next) {
async.parallel({
book: function(callback) {
Book.findById(req.params.id)
.populate('author')
.populate('genre')
.exec(callback);
},
book_instance: function(callback) {
BookInstance.find({ 'book': req.params.id })
.exec(callback);
},
}, function(err, results) {
if (err) { return next(err); }
if (results.book==null) { // No results.
var err = new Error('Book not found');
err.status = 404;
return next(err);
}
// Successful, so render.
res.render('book_detail', { title: 'Book Detail', book: results.book,
book_instances: results.book_instance } );
});
};
I have a feeling it must be something that can maybe be done with populate but I have not got that to work. The only way I have managed to get the book instance object to appear in the list for each book item is to send all book instances to the view. From there I use a foreach loop and then IF statement to get the book instances for each book. It looks really ugly and I am sure there must be some other way to do this. I am used to asp.net mvc - in that you use a virtual object. I am not sure if I am supposed to modify the model here or the controller. I may also want to pass in a much more complex model with lists within lists.
I have noted the genre is actually saved into the book document unlike bookinstances - hence the lines in the book detail controller:
book_instance: function(callback) {
BookInstance.find({ 'book': req.params.id })
.exec(callback);
},
Below I have shown what I have done. I could also have done this as objects in the controller but this is what I have now:
Book Controller:
exports.book_list = function (req, res, next) {
async.parallel({
books: function (callback) {
Book.find()
.exec(callback)
},
bookinstances: function (callback) {
BookInstance.find()
.exec(callback)
},
}, function (err, results) {
if (err) { return next(err); } // Error in API usage.
// Successful, so render.
res.render('book_list', { title: 'Book Detail', books: results.books,
bookinstances: results.bookinstances });
});
};
book_list.pug code:
extends layout
block content
h1= title
table.table
th Book
th BookInstance Count
th
//- above th is for buttons only (no title)
each book in books
- var instCount = 0
each bookinstance in bookinstances
if book._id.toString() === bookinstance.book.toString()
- instCount++
tr
td
a(href=book.url) #{book.title}
td #{instCount}
td
a.btn.btn-sm.btn-primary(href=book.url+'/update') Update
if !instCount
a.btn.btn-sm.btn-danger(href=book.url+'/delete') Delete
else
li There are no books.
What the page comes out as: