In the previous tutorial we just created an end to end framework. but in real world there are some chalenges to be addressed.
(1) in an order customer may like to include more quantity of same product.
(2)The price of product may change. Thus the price when order is made is relevant, and not current price.
(3) discount.
So the Order model having reference to product model in previous tutorial is not sufficient. The Order should ideally refer to something which not only holds reference to product, but also holds other information like discount and quantity.
mongoose addresses real life complexities
The mongoose documentation for schematype suggestes that its possible to have nested field using {} or array of fields using [], or mixed (dynamic). More over it can also embed other schema as subdocument.
The structure we need now for our use case is this.
items: [{
product: { type: Types.Relationship, ref: 'Product', many: false, },
quantity: { type: Number}
}]
Does keystoneJS also addresses them?
As keystoneJS used mongoose it must be possible? but when I tried I get this error .
throw new Error('Fields must be specified with a type function');
When asked to keystone developer, He said that
Complex models with nested collections (or, for that matter, using mongoose's
Mixed
Schema Type) are currently (a) possible, and (b) not supported in the Admin UI yet.The mongoose
schema
is available for you to modify to your heart's content, so you could still achieve the schema you want by schema.add e.g Gallery.schema.add({})
Thus we can not use mongoose syntax in a Keystone JS function.we can not use mongoose complex model, or collections in Keystone JS .
However we can add them using the mongoose schema object which is exposed when you're defining the list.
So in our case we can add array of order Items as follow
Order.schema.add({
items: [{
productid: Schema.Types.ObjectId,
quantity: Number,
}]
The Developer also warns that The downside is, obviously, the items
field will be completely ignored by the Admin UI, and you won't get those Add, Edit and Trash buttons :)
So lets go with a less risky approach. that is to add a new model called OrderItem.
OrderItem.add({
product: { type: Types.Relationship, ref: 'Product', many: false, index: true , required: true,initial:true},
quantity: { type: Types.Number, default: 1 },
price: { type: Types.Number, required: true },
discount: { type: Types.Number, required: true }
});
and then link it inside Order model a s this.
items: { type: Types.Relationship, ref: 'OrderItem', many: true, index: true , required: true,initial:true},
I have also made UI changes for supporting this new usecase. You can see the youtube video for more detail. basically I have added quantity when you add a product to cart.
In this tutorial I have also explained about using async library with great length. that you will need to save all orderItem before you save the order like this.
async.forEach(req.session.cart, function (item, callback){
OrderItem = new OrderItem.model({
--
});
OrderItem.save(function(err) {
callback();
});}, function() {
//save the order
}
I have also explained in this tutorial How can we use deepPopulate module to populate more then 1 level of relationship. so that you can do mongoose 2 level population in KeystoneJS.
For doing this you first install deep population module .
then go to the model which you want to be deeply populated, and add this code before register
var deepPopulate = require('mongoose-deep-populate')(keystone.mongoose);
Order.schema.plugin(deepPopulate);
then after you can replace .populate with .deeppopulate any where like this
Order.paginate({page: req.query.page || 1,
perPage: 2,
maxPages: 10
})
.where('customer', req.user.id)
//.populate('customer items')
.deepPopulate('customer items.product')
aaa