Mongoose - don't allow documents to have identical arrays - node.js

I want to create a database schema where a document cannot have an array that is identical to that of another document. So, say I have the schema conversations:
var ConversationSchema = new Schema({
name: String,
participants: {
type: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}
});
Now if I create two conversations with the same participants, how can I validate this so that the second one will fail, but the third will not?
var conversation1 = new Conversation({
name: "Hello",
participants: ['12345', '09876']
});
var conversation2 = new Conversation({
name: "World",
participants: ['12345', '09876']
});
var conversation3 = new Conversation({
name: "Group chat",
participants: ['12345', '09876', '13579']
});
conversation1.save(); // Valid
conversation2.save(); // Invalid - conversation already exists
conversation3.save(); // Valid

I guess you could use some custom Mongoose validation before saving your data.
But this is not really a schema thing, as Kevin said in his comment, since you will need to make a database query to compare already existing array with the new one.
Something like this (not tested):
function checkArray(arr) {
// here make a call to the db to compare existing array with arr
}
var ConversationSchema = new Schema({
name: String,
participants: {
type: [{
type: Schema.Types.ObjectId,
ref: 'User',
validate: checkArray
}]
}
});
No better idea for now.

Related

How to add schema to another schema array?

I have address schema and customer schema. I have a field address array inside my customer schema. I will be sending an adress model as my req body and customer id as req param. How can I save that adress to adresses array which is declared inside customer schema?
This is my Customer Schema
const customerSchema = mongoose.Schema ({
_id: mongoose.Schema.Types.ObjectId,
name: String,
phone_number: String,
password: String,
type:{type:String,enum:['admin','user']},
adresses:['Adress'],
orders:[{type: mongoose.Schema.Types.ObjectId, ref: 'Order'}]
});
This is my Adress Schema
const addressSchema= mongoose.Schema({
_id:mongoose.Types.ObjectId,
postalCode:Number,
city:String,
district:String,
neighborhood:String,
streetNumber:String,
no:Number,
buildingName:String,
apartmentNumber:Number,
floor:Number,
coordinates:{
latitude:Number,
longitude:Number
},
addressName:String,
customerId: {type: mongoose.Schema.Types.ObjectId,ref:'Customer'}
});
I could not figure out how am ı going to do this. I am finding the customer which I will going to push my address to like this.
This is how I get the specific customer
Customer.find({_id:req.params.customerId},(err,data)=>{
if(err) return next(err);
else{
//What I am going to do here?
}
});
First what type should I put inside the addresses array which is inside Customer Schema?
Second after finding the customer which I am going to add address to, what should I do? Mongoose 5.4.11 documentation was not enough for me. This link seemed what I needed but I did not figure out how to accomplish this problem.
https://mongoosejs.com/docs/subdocs.html
OK, so basically what You look for is: association. You need to establish a connection between User and Customer model.
We will say that Address belongs to the User and User reference Address object by for example id.
Consider an example:
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);
Now let's try to assign an author to particular created story:
const author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50
});
author.save(function (err) {
if (err) return handleError(err);
const story1 = new Story({
title: 'Casino Royale',
author: author._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});
When you define relation between Story and Person, it is easy to manipulate references between them.
In your case you should define a reference in models and then you are able to manipulate the fields:
Customer.findOne({_id:req.params.customerId}, function(error, customer) {
if (error) {
return handleError(error);
}
customer.address = newAddress; // set customer's address to the desired address
// console.log(customer.address.city);
});
Check doc for more.

Individual nested subdocument Mongoose

I'm Trying to embed a subdocument into my main document,like this:
This is the main document.js
var mongoose = require('../../db/mongodb.connector'),
Schema = mongoose.Schema;
require('./document.model');
var Document= mongoose.model('Document');
require('./alert.model');
var Alert = mongoose.model('Alert');
var userSchema = new Schema({
name: { type: String }
created: { type: Date, default: Date.now()},
alerts: {type: Schema.ObjectId,ref: 'Alert'},
documents: [{type: Schema.ObjectId,ref: 'Document'}],
});
module.exports = mongoose.model('User', userSchema);
This is the embed document.js
var mongoose = require('../../db/mongodb.connector'),
Schema = mongoose.Schema;
var alertsSchema = new Schema({
push: {type: String, default: "true"},
email: {type: String, default: "false"},
sms: {type: String, default: "false"}
});
module.exports = mongoose.model('Alert', alertsSchema);
When I Insert a new User document like this:
exports.insertUser = function (userData, res) {
var user = new User({
name: userData.name,
alerts: {push: "true", email:"false", sms: "false"}
});
user.save...
...
The returned data is this:
{ name: 'name',
documents: [],
created: 2017-04-14T10:22:05.612Z
}
The problem is that I don't know if I'm doing correctly the sintax of embed document because the insert doesn't return any error but the alerts object doesn't appear into the inserted new document.
What would be wrong?
You are doing it wrong. You need to first save the alert document and then use its id in the user document.
let alertDoc = await new Alert({push: "true", email:"false", sms: "false"}).save();
// now use the id in the user doc
await new User({name: userData.name,alerts: alertDoc._id }).save()
In case you want to embed the whole document instead of just storing the ref. You could modify schema of user model. Define your schema like this.
var alertsSchema = new Schema({
push: {type: String, default: "true"},
email: {type: String, default: "false"},
sms: {type: String, default: "false"}
});
....
var userSchema = new Schema({
name: { type: String }
created: { type: Date, default: Date.now()},
alerts: alertsSchema,
documents: [{type: Schema.ObjectId,ref: 'Document'}],
});
....
// now this should work
var user = new User({
name: "<some name>",
alerts: {push: "true", email:"false", sms: "false"}
});
There is a small issue in userSchema. From your schema definition, it looks like you want to store only references to alerts and documents. The right syntax here would be alerts: {type: Schema.Types.ObjectId,ref: 'Alert'}. Please notice that extra Types in it.
Another issue here is, you are trying to store complete alert object inside user document. Mongoose can't allow that, as in your schema, you have told mongoose to save only references to the alert document. So what you need to do here is, create an alert document, get it's _id and then store it in alert field of user document.
Whenever you want to fetch the complete user schema, you can just populate alert and documents.
Hope this answer improves your understanding of how mongoose schema works.

Mongoose populate ObjectID from multiple possible collections

I have a mongoose model that looks something like this
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: 'article',
index:true,
},
});
But 'item' could be referenced from multiple collections. Is it possible to do something like this?
var LogSchema = new Schema({
item: {
type: ObjectId,
ref: ['article','image'],
index:true,
},
});
The idea being that 'item' could be a document from the 'article' collection OR the 'image' collection.
Is this possible or do i need to manually populate?
Question is old, but maybe someone else still looks for similar issues :)
I found in Mongoose Github issues this:
mongoose 4.x supports using refPath instead of ref:
var schema = new Schema({
name:String,
others: [{ value: {type:mongoose.Types.ObjectId, refPath: 'others.kind' } }, kind: String }]
})
In #CadeEmbery case it would be:
var logSchema = new Schema({
item: {type: mongoose.Types.ObjectId, refPath: 'kind' } },
kind: String
})
But I did't try it yet...
First of all some basics
The ref option says mongoose which collection to get data for when you use populate().
The ref option is not mandatory, when you do not set it up, populate() require you to give dynamically a ref to him using the model option.
#example
populate({ path: 'conversation', model: Conversation }).
Here you say to mongoose that the collection behind the ObjectId is Conversation.
It is not possible to gives populate or Schema an array of refs.
Some others Stackoverflow people asked about it.
Soluce 1: Populate both (Manual)
Try to populate one, if you have no data, populate the second.
Soluce 2: Change your schema
Create two link, and set one of them.
var LogSchema = new Schema({
itemLink1: {
type: ObjectId,
ref: 'image',
index: true,
},
itemLink2: {
type: ObjectId,
ref: 'article',
index: true,
},
});
LogSchema.find({})
.populate('itemLink1')
.populate('itemLink2')
.exec()
Dynamic References via refPath
Mongoose can also populate from multiple collections based on the value of a property in the document. Let's say you're building a schema for storing comments. A user may comment on either a blog post or a product.
body: { type: String, required: true },
on: {
type: Schema.Types.ObjectId,
required: true,
// Instead of a hardcoded model name in `ref`, `refPath` means Mongoose
// will look at the `onModel` property to find the right model.
refPath: 'onModel'
},
onModel: {
type: String,
required: true,
enum: ['BlogPost', 'Product']
}
});
const Product = mongoose.model('Product', new Schema({ name: String }));
const BlogPost = mongoose.model('BlogPost', new Schema({ title: String }));
const Comment = mongoose.model('Comment', commentSchema);

Mongoose: how to structure a schema with a SET of subdocuments (one unique field)?

I'm trying to make the following schema to work:
var FormSchema = new mongoose.Schema({
form_code: { type: String, unique: true },
...
});
var UserSchema = new mongoose.Schema({
...
submissions: [{
form_code: { type: String, unique: true },
last_update: Date,
questions: [{
question_code: String,
answers: [Number]
}]
}],
});
The rationale here is that a user can have many unique forms submitted, but only the last submission of each unique form should be saved. So, ideally, by pushing a submission subdocument when updating a user, the schema would either add the submission object to the set, or update the subdocument containing that form_code.
The following code doesn't work as desired (it pushes the new subdocument even if the form_code is already present):
User.findOneAndUpdate(
{ _id: user.id },
{ $addToSet: { submissions: submission_object } },
function (err, user) {
// will eventually have duplicates of form_code at user.submissions
}
);
The above schema clearly doesn't work, what must be changed to achieve that "upsertToSet"?

Mongoose Relationship Populate Doesn't Return results

var SecuritySchema = new Mongoose.Schema({
_bids: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'BuyOrder'
}],
_asks: [{
type: Mongoose.Schema.Types.ObjectId,
ref: 'SellOrder'
}]
});
var OrdersSchema = new Mongoose.Schema({
_security: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Security'
},
price: {
type: Number,
required: true
},
quantity: {
type: Number,
required: true
}
});
// declare seat covers here too
var models = {
Security: Mongoose.model('Security', SecuritySchema),
BuyOrder: Mongoose.model('BuyOrder', OrdersSchema),
SellOrder: Mongoose.model('SellOrder', OrdersSchema)
};
return models;
And than when I save a new BuyOrder for example:
// I put the 'id' of the security: order.__security = security._id on the client-side
var order = new models.BuyOrder(req.body.order);
order.save(function(err) {
if (err) return console.log(err);
});
And attempt to re-retrieve the associated security:
models.Security.findById(req.params.id).populate({
path: '_bids'
}).exec(function(err, security) {
// the '_bids' array is empty.
});
I think this is some sort of naming issue, but I'm not sure, I've seen examples here and on the moongoose website that use Number as the Id type: http://mongoosejs.com/docs/populate.html
The ref field should use the singular model name
Also, just do:
models.Security.findById(req.params.id).populate('_bids').exec(...
My main suspicion given your snippet at the moment is your req.body.order has _security as a string instead of an array containing a string.
Also, you don't need an id property. Mongodb itself will automatically do the _id as a real BSON ObjectId, and mongoose will add id as a string representation of the same value, so don't worry about that.
While I don't understand your schema (and the circular nature of it?), this code works:
var order = new models.BuyOrder({ price: 100, quantity: 5});
order.save(function(err, orderDoc) {
var security = new models.Security();
security._bids.push(orderDoc);
security.save(function(err, doc) {
models.Security.findById({ _id: doc._id })
.populate("_bids").exec(function(err, security) {
console.log(security);
});
});
});
It:
creates a BuyOrder
saves it
creates a Security
adds to the array of _bids the new orderDoc's _id
saves it
searches for the match and populates
Note that there's not an automatic method for adding the document to the array of _bids, so I've done that manually.
Results:
{ _id: 5224e73af7c90a2017000002,
__v: 0,
_asks: [],
_bids: [ { price: 100,
quantity: 5,
_id: 5224e72ef7c90a2017000001, __v: 0 } ] }

Resources