Relationship field within same collection? - mongodb

I have a keystonejs model for product categories (MongoDB). Some categories should have subcategories. Currently I have set a relationship field "ChildCategoryOf", where I can manually select the Parent Category in admin panel. To have more functionality, I would like to create another Field called "ParentCategoryOf" that would consist an Array of subcategories. How is it possible to have a field that automatically stores Child categories in Array? I imagine it like this:
Current model:
let ProductCategory = new keystone.List('ProductCategory', {
autokey: {
from: 'name',
path: 'key',
unique: true
}
});
ProductCategory.add({
name: {
type: String,
required: true
},
ChildCategoryOf: {
type: Types.Relationship,
ref: 'ProductCategory',
many: false,
required: false,
},
IsParentCategory: Types.Boolean,
});

For categories in mongo you can use an inheritance like model, in which you can store the parentId and ancestor Id for each model and its children and then add a method to the mongoose model to add a child each time you call it on an instance, this would be like below code:
const ProductCategory = new Schema({
name: String,
parent: { type: Schema.Types.ObjectId, ref: 'ProductCategory'},
ancestors: [{type: Schema.Types.ObjectId, ref: 'ProductCategory'}],
children: [{type: Schema.Types.ObjectId, ref: 'ProductCategory'}]
});
ProductCategory.methods = {
addChild: function(child){
let that = this;
child.parent = this._id;
child.ancestors = this.ancestors.concat([this._id]);
return this.model('ProductCategory').create(child).addCallback
(function(child){
that.children.push(child._id);
that.save();
});
}
};
then later from where you want to find products by their categories you should concat the category id that you find with it's children ids and use this array in find query with $in operator to find all the products in a category with its children.

Related

Is possible to have multiple different types of refs for a single attribute in mongoose (mongodb)

I am using mongodb with mongoose. And i am wondering if it is possible to have multiple references for an object id attribute in a schema.
I have tried the code underneath and it did not work.
const Schema = new Schema({
refrens: {
type: ObjectId,
// this did not work
ref: [
"Post",
"Account"
],
required: true
}
});
I know it is possible to remove the ref attribute (field, key) and then all object ids are valid but i want certain object ids to be valid, the object ids of the Post and Account model.
const Schema = new Schema({
refrens: {
// This will allow all different types of object ids to be the value
type: ObjectId,
required: true
}
});
Look like refPath is what you need. You can do something like this:
const Schema = new Schema({
refrens: {
type: ObjectId,
refPath: 'onModel',
required: true
},
onModel: {
type: String,
required: true,
enum: ['Post', 'Account']
}
});

How to delete in cascade in several models with mongoose?

I have these 3 models in mongoose:
TravelSchema
var travelSchema = new mongoose.Schema({
name: String,
description: String,
mexican_currency_value: mongoose.Schema.Types.Decimal128
})
travelSchema.pre('deleteOne', function(next) {
const id = this.getQuery()['_id'];
Product.deleteMany({ travel: id }, (err, value) => {
});
next();
});
ProductSchema
var productSchema = new mongoose.Schema({
name: String,
description: String,
purchased_amount: Number,
unit_price_mex: mongoose.Schema.Types.Decimal128,
unit_price_to_sell: mongoose.Schema.Types.Decimal128,
travel: { type: mongoose.Schema.Types.ObjectId, ref: 'Travel' }
})
InvoiceSchema
var invoiceSchema = new mongoose.Schema({
product: productSchema,
client: { type: mongoose.Schema.Types.ObjectId, ref: 'Client' },
purchased_amount: Number,
fch: String
});
Where Travel and Product have a one-to-many relationship and Product and Invoice have a one-to-many relationship.
I need the following:
When a Travel is deleted, all Products that are related to that Travel are also deleted.
When these Products are eliminated, all the Invoices related to each Product are also eliminated.
I have managed to eliminate all the products, but when I try to eliminate the invoices I do not obtain the ids of the Products.
invoiceSchema.pre('deleteMany', (next) => {
console.log(this);
// this print { n: 2, ok: 1, deletedCount: 2 }
})
I think you should start the other way when looking at deleting all the related docs. Like you have travel id, with it get all the products and store their id in array and then your first delete should be of the invoices where product._id: { $ in: _arrayOfProducIds }. Once that is complete then deleteMany your products since you already have their ids in the _arrayOfProducIds and lastly deal with the Travel:
travelSchema.pre('deleteOne', function(next) {
const id = this.getQuery()['_id']; // travel id check
// Query to get all the product ids based on the travel id and return array for `$in`
const productIds = this.DoYourQuery // [productIds] check
Invoice.deleteMany({'product._id': { $in: productIds }}, // ... etc
Product.deleteMany({ _id': { $in: productIds }}, // ... etc
next();
});
I would assume you do not have a large number of products and invoices ... like thousands since then $in might be somewhat of a performance issue. Hope this helps.

Create unique multikey index via model settings

I am using Sails v1.1 -
I created a many-to-many through custom model association following the sails doc here - https://sailsjs.com/documentation/concepts/models-and-orm/associations/through-associations
The PetUser model has two columns pet and user, where each is the respective id. I want to create a unique multi-key index, meaning there cannot be two rows with the same combination of "pet and user". Meaning the second call should succeed, and third call should fail with uniqueness error:
await PetUser.create({ user: 1, pet: 33 }); // should succeed
await PetUser.create({ user: 1, pet: 44 }); // should succeed as user/pet combination is different
await PetUser.create({ user: 1, pet: 33 }); // should fail
I tried adding unique: true to both the owner and pet attribute on PetUser model below, but only the first unique: true gets respected.
So this is my code in myApp/api/models/PetUser.js
module.exports = {
attributes: {
owner: {
model:'user',
unique: true
},
pet: {
model: 'pet',
unique: true
}
}
}
For implementing similar behavior I added a combined attribute and mark it unique. Also, I added beforeCreate and beforeUpdate model hooks on which I generate my combined attribute to check is it unique or not.
const YourModel = {
attributes: {
owner: {
model: 'user',
},
pet: {
model: 'pet',
},
petOwner: {
type: 'string',
unique: true,
}
},
beforeCreate : function(values,cb) {
// TODO get ids from related records or reset to default on missed relation record if you need it
const petId = 35;
const ownerId = 8;
values.petOwner = `${petId}-${ownerId}`;
cb();
},
beforeUpdate : function(values,cb) {
YourModel.beforeCreate(values, cb)
},
};
module.exports = YourModel;
In result when you tries to add the record with the same relations, you will get E_UNIQUE as you expected.

Populate multiple fields using lookup in mongodb

I am new to mongodb and wanted to populate two ids using lookup
Eg:
{
"sampleId1": "5kjksds8nkjfhsjfi8kl",
"sampleId2": "7jhjshfi9jsfkjsdfkkk"
}
I am using aggregate framework to query the data and wanted to popualte both ids.
I want $loopup to populate both ids which is similar to
Model.find().populate('sampleId1').populate('sampleId2')
For your case, I want to suggest you mongoose-autopopulate like this
const autopopulate = require('mongoose-autopopulate')'
const sampleSchema = new Schema({
sampleId1: {type: Schema.ObjectId, ref: 'ColleactionName', autopopulate: {select: 'firstName, lastName'}},
sampleId2: {type: Schema.ObjectId, ref: 'ColleactionName', autopopulate: {select: 'firstName, lastName'}}
})
sampleSchema.plugin(autopopulate)
module.exports = mongoose.model('sampleSchema', sampleSchema)
now whenever you request for find it automatically populates all field
who have Schema.ObjectId
let criteria = {},
projection = {},
options = {lean: true}
Model.find(criteria, projection, options, (err, result) => {
console.log(result); // See out-put
})
The second thing you need to check in your schema that sampleId1 and sampleId2 both have type type: Schema.ObjectId with reference of collection name ref: 'ColleactionName'
the second way to this thing which you already have done you question
sampleSchema.
find(...).
populate('sampleId1').
populate('sampleId2').
exec();

mongoose schema multi ref for one property

How to write multi ref for one property of one mongoose schema, like this(but wrong):
var Schema = mongoose.Schema;
var PeopleSchema = new Schema({
peopleType:{
type: Schema.Types.ObjectId,
ref: ['A', 'B'] /*or 'A, B'*/
}
})
You should add string field to your model and store external model name in it, and refPath property - Mongoose Dynamic References
var Schema = mongoose.Schema;
var PeopleSchema = new Schema({
externalModelType:{
type: String
},
peopleType:{
type: Schema.Types.ObjectId,
refPath: 'externalModelType'
}
})
Now Mongoose will populate peopleType with object from corresponding model.
In the current version of Mongoose i still don't see that multi ref possible with syntax like you want. But you can use part of method "Populating across Databases" described here. We just need to move population logic to explicitly variant of population method:
var PeopleSchema = new Schema({
peopleType:{
//Just ObjectId here, without ref
type: mongoose.Schema.Types.ObjectId, required: true,
},
modelNameOfThePeopleType:{
type: mongoose.Schema.Types.String, required: true
}
})
//And after that
var People = mongoose.model('People', PeopleSchema);
People.findById(_id)
.then(function(person) {
return person.populate({ path: 'peopleType',
model: person.modelNameOfThePeopleType });
})
.then(populatedPerson) {
//Here peopleType populated
}
...