MongoDB embedded document as key-val array - node.js

I'm trying to use mongoose to represent an association between 3 schemas:
Schema1: {
//fields
}
Schema2: {
//fields
}
Scehma3: {
//fields
- [Scehma1:Scehma2] - collection of key-val elements
where the key is a ref to schema1 and val is ref to scehma2
}
Does mongoose support this king of association without creating a schema4?

You can't create ambiguous keys in mongoose because its whole purpose is to handle your document structure for you. What you can do, however, is create an array of objects.
Schema4: {
schemaRefs: [{
refToSchema1: {type: mongoose.Types.ObjectId, ref: 'Schema1'},
refToSchema2: {type: mongoose.Types.ObjectId, ref: 'Schema2'}
}]
}
For future reference it's far easier to understand your question when you provide real examples rather than false names. Even if you falsify your example (e.g. some relationship between restaurants and customers or something) it's much easier to understand the relationship you're trying to make.

Related

Mongodb Relationship: Posts and Comments (ref v sub-documents)

I know there are a lot of similar questions, but they're too old and since Mongodb has evolved alot for last 5-6 years I am looking for a good schema design.
Goal: I want to have a post with comments by users.
What I have designed so far is:
Separate post model:
const projectSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
title: { type: String, required: true },
image: { type: String, default: undefined },
description: { type: String, required: true, minLength: 200, maxlength: 500 },
comments: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Comment'
}],
state: { type: Boolean, default: true },
collaborators: { type: Array, default: [] },
likes: { type: Array, default: [] }
})
And a separate comments model:
const commentSchema = new mongoose.Schema({
comment: { type: String, required: true },
project: { type: String, required: true, ref: 'Project' },
user: { type: String, required: true, ref: 'User' }
})
The reason I am going for the relational approach is because if the comments increase to say 10,000 in number, it will increase the size of schema by alot.
This way, no matter how many comments we can populate them using their IDs, also, we will have different collection for comments iself.
Reference : one-to-many
Is this a good approach for my project?
The way I am querying the comments from one single post:
const project = await Project.findById(
new mongoose.Types.ObjectId(req.params.projectId)
).populate({
path: 'comments',
populate: { path: 'user' }
}).lean()
Whether it's a good design depends how many comments per post do you expect, and what query will be performed on your app.
There's a good blog from mongodb.com on how to design your database schema
The common design is:
One to Few (Use embed)
One to Many (Use embed reference)
One to squillions (The usual relational database one-to-many approach)
Summary is:
So, even at this basic level, there is more to think about when designing a MongoDB schema than when designing a comparable relational schema. You need to consider two factors:
Will the entities on the ā€œNā€ side of the One-to-N ever need to stand alone?
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
Based on these factors, you can pick one of the three basic One-to-N schema designs:
Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
There is also a blog about advanced schema design which is worth the read.
You seems to be using the two-way referencing approach.
The difference between yours and one-to-squillions is you are not only storing post id reference on comment document, but also storing comment ids as reference in post document, while one-to-squillions will only stores project id reference in comment document.
Using your approach will be better if you need to get comment ids of a post. But the disadvantage is you need to run two queries when deleting or creating a comment, one to delete / create comment id from post, and the other one to delete / create the comment document it self. It's also will be slower to find "which post belongs to given comment id".
While using one-to-squillions would gives you worse performance when performing a query to get comments by post id. But you can mitigate this by properly indexing your comment collection.

Query Parse.com migrated database pointer relationship with Mongoose

Context
So we have migrated from Parse.com to an hosted MongoDB database. Now I have to write a script that queries our database directly (not using Parse).
I'm using nodejs / mongoose and am able to retrieve these documents.
Problem
Here is my schema so far:
var StorySchema = new mongoose.Schema({
_id: String,
genre: String
});
var ActivitySchema = new mongoose.Schema({
_id: String,
action: String,
_p_story: String /* Also tried: { type: mongoose.Schema.Types.ObjectId, ref: 'Story' } and { type: String, ref: 'Story' }*/,
});
I would like to write a query that fetches theses documents with the related Story (stored as a pointer).
Activity
.find({
action: 'read',
})
.exec(function(error, activities) {
activities.forEach(function(activity) {
// I would like to use activity._p_story or whatever the mean to access the story here
});
});
Question
Is there a way to have the fetched activities populated with their story, given that the _p_story field contains Story$ before the object id?
Thanks!
One option I have been looking at is the ability to create a custom data type for each pointer. The unfortunate side is Parse treats these as 'belongsTo' relationships and but does not store the 'hasMany' relationship that Mongoose wants for populate(). But once this is in place you can easily do loops to get the relational data. Not ideal but works and is what populate is really doing under the hood anyways.
PointerTypeClass.js -> This would work for populating the opposite direction.
var Pointer = function(mongoose) {
function PointerId(key, options) {
mongoose.SchemaType.call(this, key, options, 'PointerId');
}
PointerId.prototype = Object.create(mongoose.SchemaType.prototype);
PointerId.prototype.cast = function(val) {
return 'Pointer$' + val;
}
return PointerId;
}
module.exports = Pointer;
Also be sure mongoose knows about the new type by doing mongoose.Schema.Types.PointerId = require('./types/PointerTypeClass')(mongoose);
Lastly. If you are willing to write some cloudcode you could create the array of ids for your populate to know about the objects. Basically in your Object.beforeSave you would update the array of the id for the relationship. Hope this helps.

Ways to remove sub-documents in Mongoose

I am currently trying to design a schema structure with mongoose which looks like the following:
var hubSchema = new mongoose.Schema({
//some other properties
dataStream: {
dataType: String,
dataPoints: [{
createdAt: { type: Date, expires: '7d'}
data: {}
}],
storeStrategy: {
type: String,
enum: storeStrategies
}
},
});
The mongoose API docs say that:
Sub-documents enjoy all the same features as normal documents. The
only difference is that they are not saved individually, they are
saved whenever their top-level parent document is saved.
I want dataPoints to be an array of sub-documents and each sub-document should have TTL set just as a normal document. Having said that, I found out from other posts that it is not possible to set 'expires' for sub-documents. So my question is: Should I create a separate model for dataPoints and store a references here or I should implement some custom strategy for deleting sub-documents keeping this kind of structure?

Mongoose Populate Returning null due to Schema design?

I'm stuck with mongoose populate returning null. I have a very similar situation to another question where it seems to we working just fine, perhaps with one important difference:
The model I'm referencing only exist as a subdocument to another model.
Example:
// The model i want to populate
Currency = new Schema({
code: String,
rate: Number
});
// The set of currencies are defined for each Tenant
// A currency belongs to one tenant, one tenant can have multiple currencies
Tenant = new Schema({
name: String,
currencies: [Currency]
});
Product = new Schema({
Name: String,
_currency: {type: ObjectId, ref: 'Currency'},
});
Customer = new Schema({
tenant: {type: ObjectId, ref: 'Tenant'},
products: [ Product ]
});
Then I export the models and use them in one of my routes where what I would like to do is something like
CustomerModel.find({}).populate('products._currency').exec(function(err, docs){
// docs[0].products[0]._currency is null (but has ObjectId if not usinn populate)
})
Which is returning null for any given product._currency but if I don't populate i get the correct ObjectId ref, which corresponds to an objectId of a currency embedded in a tenant.
I'm suspecting I need currencies to be stand-alone schema for this to work., Ie not just embedded in tenant, but that would mean I get a lot of schemas referencing each other.
Do you know if this is the case, or should my set-up work?
If this is the case, I guess I just have to bite the bullet and have multitude of collections referencing each other?
Any help or guidance appreciated!

Mongoose - Defining child's ObjectID in another Schema

There is a similar thread # Mongoose variable key name. However, he goes with another method instead of solving this. The other method is the OR part of my title.
EDIT - IGNORE THIS SECTION, I AM USING THE ALTERNATIVE NOW. Issue now lays with referencing a child's Object ID elsewhere.
I have the following array:
selections: [{
4433d18d31f3775756ac2a70: "542e91aa31f3775756abccda"},
{4433d18d31f3775756ac2a71: "542e916c31f3775756abccd8"},
{4433d18d31f3775756ac2a72: "542e934231f3775756abccdb"
}]
My schema is currently as follows:
selections: {
<something>: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
In place <something>, is there a way of essentially saying "I don't care what's here"?
ALTERNATIVELY, as this doesn't seem possible after scouring the internet, I can have the following schema:
selections: {
pid: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Competition.CHILD'
}
selection: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Selection'
}
}
But the issue here is that ObjectID that is being used where <something> is a child schema inside of the schema Competition, and I can't find a way of saying that for the Object ID.
Any advice/help would be great please. Ideally I'd prefer the first solution but I understand it may not be possible. Thanks!
Use an array of objects with optional (but fixed) keys:
selections: [{
selection: {type: ObjectId, ref: 'Selection'},
child: {type: ObjectId, ref: 'Competition.CHILD'}
}]
This will enable you to do better queries, use mongoose population, etc.

Resources