Many-to-many mapping with Mongoose - node.js

I have FlashcardSchemas and PackageSchemas in my design. One flashcard can belong to different packages and a package can contain different flashcards.
Below you can see a stripped down version of my mongoose schema definitions:
// package-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var PackageSchema = new Schema({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
var exports = module.exports = mongoose.model('Package', PackageSchema);
// flashcard-schema.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
var exports = module.exports = mongoose.model('Flashcard', FlashcardSchema);
As you can see from the comments above, these two schema definitions belong to separate files and reference each other.
I get an exception stating that PackageSchema is not defined, as expected. How can I map a many-to-many relation with mongoose?

I am new to node, mongoDB, and mongoose, but I think the proper way to do this is:
var PackageSchema = new Schema({
id: ObjectId,
title: { type: String, required: true },
flashcards: [ {type : mongoose.Schema.ObjectId, ref : 'Flashcard'} ]
});
var FlashcardSchema = new Schema({
id: ObjectId,
type: { type: String, default: '' },
story: { type: String, default: '' },
packages: [ {type : mongoose.Schema.ObjectId, ref : 'Package'} ]
});
This way, you only store the object reference and not an embedded object.

You are doing it the right way, however the problem is that you have to include PackageSchema in the the flashcard-schema.js, and vice-versa. Otherwise these files have no idea what you are referencing
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
PackageSchema = require('./path/to/package-schema.js')
var FlashcardSchema = new Schema({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});

You could use the Schema.add() method to avoid the forward referencing problem.
This (untested) solution puts the schema in one .js file
models/index.js
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
// avoid forward referencing
var PackageSchema = new Schema();
var FlashcardSchema = new Schema();
PackageSchema.add({
id : ObjectId,
title : { type: String, required: true },
flashcards : [ FlashcardSchema ]
});
FlashcardSchema.add({
id : ObjectId,
type : { type: String, default: '' },
story : { type: String, default: '' },
packages : [ PackageSchema ]
});
// Exports both types
module.exports = {
Package: mongoose.model('Package', PackageSchema),
Flashcard: mongoose.model('Flashcard', FlashcardSchema)
};

You're thinking of this too much like a relational data store. If that's what you want, use MySQL (or another RDBMS)
Failing that, then yes, a third schema could be used, but don't forget it'll still only be the id of each object (no joins, remember) so you'll still have to retrieve each other item in a separate query.

https://www.npmjs.com/package/mongoose-relationship
##Many-To-Many with Multiple paths
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
relationship = require("mongoose-relationship");
var ParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var Parent = mongoose.models("Parent", ParentSchema);
var OtherParentSchema = new Schema({
children:[{ type:Schema.ObjectId, ref:"Child" }]
});
var OtherParent = mongoose.models("OtherParent", OtherParentSchema);
var ChildSchema = new Schema({
parents: [{ type:Schema.ObjectId, ref:"Parent", childPath:"children" }]
otherParents: [{ type:Schema.ObjectId, ref:"OtherParent", childPath:"children" }]
});
ChildSchema.plugin(relationship, { relationshipPathName:['parents', 'otherParents'] });
var Child = mongoose.models("Child", ChildSchema)
var parent = new Parent({});
parent.save();
var otherParent = new OtherParent({});
otherParent.save();
var child = new Child({});
child.parents.push(parent);
child.otherParents.push(otherParent);
child.save() //both parent and otherParent children property will now contain the child's id
child.remove()

This is the problem of cyclic/circular dependency. This is how you make it work in nodejs. For more detail, check out "Cyclic dependencies in CommonJS" at http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript
//------ a.js ------
var b = require('b');
function foo() {
b.bar();
}
exports.foo = foo;
//------ b.js ------
var a = require('a'); // (i)
function bar() {
if (Math.random()) {
a.foo(); // (ii)
}
}
exports.bar = bar;

Related

Mongo Relationships/Referencing

I am new to MongoDB references. Right now I have one collection which I call users. It stores all users and their keys. I have another collection which has data for each key.
I want to just use their key as the ID to connect them. So I will have each key generated and and the keyData will be empty when first created and then I will just keep adding objects to the keyData array. That is my plan, but I do not know how I create the relation with the schema.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//Create Schema
const userKey = new Schema({
_id : {
type : String,
required: true
},
key: {
type : String,
required: true
},
keyData: [key],
date: {
type: Date,
default: Date.now
}
});
module.exports = Key = mongoose.model('key', userKey);
This doesn't work because I cannot access the key before initialization. So how canI relate the two collections?
Schema #1: userData
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create User-data Schema
const userData = new Schema({
data: {
type: Array,
require: true
}
},
{
collection: 'data' // Mentioning collection name explicitly is good!
});
module.exports = mongoose.model('data', userData);
Schema #2: Keys
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create User-Key Schema
const userKey = new Schema({
key: {
type: String,
required: true
},
keyData: {
type: Schema.Types.ObjectId,
ref: 'data'
},
date: {
type: Date,
default: Date.now
}
},
{
collection: 'keys' // Mentioning collection name explicitly is good!
});
module.exports = mongoose.model('keys', userKey);
As per this link its not possible to set string as refs. So in your keys schema use ObjectId as ref.
Would something like this work?
const userData = new Schema({
_id : {
type : String,
required: true
},
data : {
type : Array,
require: true
}
});
const userKey = new Schema({
_id : {
type : String,
required: true
},
key: {
type : String,
required: true
},
keyData: [{ type: Schema.Types.ObjectId, ref: 'data' }],
date: {
type: Date,
default: Date.now
}
});
module.exports = KeyData = mongoose.model('data', userData);
module.exports = Key = mongoose.model('key', userKey);

Error occured in nested sub-schema collections using mongoose

I need a nested subschema having ids so i tried the below code but data cant inserted
code
My model..
var connection= handler.getConnection();
console.log(connection);
autoIncrement.initialize(connection);
var subSchema = mongoose.Schema({
course_Submodule: [{
type: String,
required: true,
}]
},{ _id : false });
subSchema.plugin(autoIncrement.plugin, {
model: 'Submodule',
field: 'Id_submodule',
startAt: 1,
incrementBy: 1
});
var courseSchema = new Schema({
course_Name: String,
course_Code: String,
course_Submodule: [subSchema],
course_Author: String,
id_subject: String,
id_user: String,
});
courseSchema.plugin(autoIncrement.plugin, {
model: 'Course',
field: 'Id_course',
startAt: 1,
incrementBy: 1
});
var Course = connection.model('Course', courseSchema);
var Submodule = connection.model('Submodule', subSchema);
module.exports = Course;
bt in db data is inserted like this
"_id" : ObjectId("578efe6da667fff80d09d5ed"),
"Id_course" : 214,
"course_Name" : "chemistry1",
"course_Code" : "ch1",
"course_Author" : "David",
"id_subject" : "3",
"course_Submodule" : [
{
"Id_submodule" : 14,
"course_Submodule" : [ ]
},
{
"Id_submodule" : 15,
"course_Submodule" : [ ]
}
],
"__v" : 0
trying these code i cant insert the value of course_Submodule.Is ther any anotherway for this .help me please
Instead of duplicating the data, you can simply store the object id of the other object into your schema.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//Schema for doctors
var StudentSchema = new Schema({
name:{
type: String,
required: true
},
email: {
type: String,
unique: true
},
user : {
type : Schema.ObjectId,
ref: 'User', //i have a different model User
required : true
}
});
module.exports = mongoose.model('Student', StudentSchema);
Your query over student schema can look like this
db.collection.find(//condition, function(err, data){
//your functionality
})
.populate('User', 'dob')
.exec();
My user schema has a field dob.
This is a small and tidy example to avoid duplicacy of data.
Hope it will help.

Structure for deep nests in mongoose

Mongo noob here. I'm building a mongoose schema with a lot of one-to-many relationships, and this seems to be a good opportunity to use nesting. I have a collection "countries", each has many "states", each has many "cities", each has many "people." I'm going to want to run all different sorts of queries...get states by country, insert new city into state, remove a person from a city, etc.
How do I structure it? My current schema looks like this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
;
var countrySchema = Schema({
name: { type: String },
states: [ {
name: { type: String },
cities: [ {
name: { type: String },
people: [ {
name: { type: String }
} ]
} ]
} ]
});
To me, that makes sense. I will likely be pulling entities based on deep nests, so this kind of nested schema should give me the performance edge that a heavily-joined relational database won't (right?).
It is, however, very ugly. I have also seen this style of schema construction:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
;
var countrySchema = mongoose.schema({
name: { type: String },
states: [ { type: ObjectId, ref: State } ]
});
var stateSchema = mongoose.schema({
name: { type: String },
cities: [ { type: ObjectId, ref: City } ]
});
var citySchema = mongoose.schema({
name: { type: String },
persons: [ { type: ObjectId, ref: Person } ]
});
var personSchema = mongoose.schema({
name: { type: String },
});
var Country = mongoose.model('Country', countrySchema)
, State = mongoose.model('State', stateSchema)
, City = mongoose.model('City', citySchema)
, Person = mongoose.model('Person', personSchema)
;
Is this a better way to do it? It seems more readable, but I imagine it doesn't perform as well.

Mongoose data saving without _id

I am using mongoose with node.js application. I don't want _id field in record.
I am using this code to save my record without _id field. But it is giving error
document must have an _id before saving
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PlayerSchema = new Schema({
player_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String }
}
, { _id: false }
);
var Player = mongoose.model('Player', PlayerSchema );
var athlete = new Player();
athlete.player_id = 1;
athlete.player_name = "Vicks";
athlete.player_age = 20;
athlete.player_country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});
I am using mongoose version 3.8.14
Unfortunately, You can not skip having a primary key for the document but you can override the primary key content, you can define your own primary key for each document.
Try the following schema for the same.
var PlayerSchema = new mongoose.Schema({
_id : { type: Number },
player_name : { type: String },
player_age : { type: Number },
player_country : { type: String },
}
);
I have replaced your player_id with _id. Now you have control over the primary key of the document and the system won't generate the key for you.
There are some plugins which can also do the autoincremet for your primary key. https://github.com/chevex-archived/mongoose-auto-increment. You might try these as well.
Also, about the error you are getting :
Any document is an object and should be wrapped inside the curly brackets you can not define two independent object in the same document. So you are getting this error.
Copying an answer given on a similar Github issue comment:
The _id option exists to prevent one being created and assigned to
every sub-document within your document.
So, it's not for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String
}]
}, {_id: false});
// ^^^^^^^^^
// this won't work
It's for this:
var Schema = mongoose.Schema,
ThingSchema = new Schema({
name: String,
categories: [{
label: {
type: String,
uppercase: true
},
value: String,
_id: false // <--- this works
}]
});
Took me a while but, after running into the same problem, I found the
documentation here.
instade of writing this
{
player_id : { type: Number },
player_name : { type: String },
...}
write this:-
{
_id : { type: Number },
_name : { type: String },
_age : { type: Number },
_country : { type: String }
}
hence _id will be players_id
then further write code like this:-
var athlete = new Player();
athlete._id = 1;
athlete._name = "Vicks";
athlete._age = 20;
athlete._country = "UK";
athlete.save(function(err) {
if (err){
console.log("Error saving in PlayerSchema"+ err);
}
});

mongoose - possible circular dependency?

I have the following mongoose models in my express app:
//dog.js
var mongoose = require("mongoose");
var dogSchema = (exports.dogSchema = mongoose.Schema({
name: { type: String, required: true },
}));
Then I import dog.js to my user.js
//user.js
var mongoose = require("mongoose");
var dog = require("./dog");
var userSchema = mongoose.Schema({
user: { type: String, required: true },
pass: { type: String, required: true },
dogs: [dog.dogSchema],
});
Now, from my routes I am creating a new user like this:
var user = require("../models/user");
var dog = require("../models/dog");
dog = new dog.Dog(dogData);
user = new user.User(data); //this will of course contain also dogData
user.save(next);
Is this the correct way to do this kind of operation? I have the feeling that I might be generating a circular dependency somehow, and anyway it does not look right to me. Any ideas on how to create sub-documents where the schema is from another model file?
You can create simultaneous references in two directions without creating circular problems. Create a reference from one document to the other using ref. From the docs:
http://mongoosejs.com/docs/populate.html
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
Then you can then choose to load the sub document using populate
Story.find({ --your criteria-- })
.populate('_creator')
.exec(function (err, story) {../});
You can then store the 2 schemas in separate .js files and require them both

Resources