Node ORM hasMany association - node.js

I want to list out all of user ConversationData (ONE User HAS MANY CovnresationData), but i got 'undefined' when i followed tutorial on official site. Help, guys!
var User = db.define("user", {
name : String,
surname : String,
avatar : String,
username : String,
token : String
}, {
autoSave: true
});
var Conversation = db.define("conversations", {
name : Number,
createdAt : Date,
updatedAt : Date
});
var ConversationData = db.define("conversation_data", {
user_id : Number,
conversation_id : Number
});
db.sync();
User.hasMany('conversationsData', ConversationData, {}, {
autoFetch: true
});
User.find({id: 1}).first(function(err, asda) {
cconsole.log(asda.conversationsData);
});
OUTPUT:
(orm/postgres) SELECT "t1"."user_id", "t1"."conversation_id", "t1"."id" FROM "conversation_data" "t1" JOIN "user_conversationsData" "t2" ON "t2"."conversationsdata_id" = "t1"."id" WHERE "t2"."user_id" = 1
undefined
Request seems to me completely wrong. it must Join user and conversation data tables, as i understand..

At least as far as I've seen with MySQL, ORM handles hasMany relationships by creating a 'merge' table, in your case it is likely what you see as user_conversationsData. This table should have 3 columns, id (generated, ignore it), user_id to map to the Users table, and conversationdata_id to map to the conversationData table.
You have autofetch on, what ORM will do is fetch the User, and then fetch the conversationdata_id's and the conversationData's by joining the extra merge-table to the conversationData table. This is the query you see.
To access the conversationData, you should be able to see it as a property userInstance.conversationDatas. If that doesn't work, try calling console.dir(Object.keys(userInstance)) to see what's there.

Related

Am I better off splitting an elaborate mongoose model?

Assuming the case of a /login API, where, for a matching set of credentials, a user object from the collection should be returned, which approach would be more performant:
1) One model with projection queries:
var UserSchema = new Schema({
name : String,
email : String,
dob : Number,
phone : Number,
gender : String,
location : Object, // GeoJSON format {type: 'Point', geometry: { lat: '23.00', lng: '102.23' }}
followers : [UserSchema],
following : [UserSchema],
posts : [PostSchema],
groups : [GroupSchema]
// ... and so on
});
2) Split models:
var UserMinimalSchema = new Schema({
name : String,
email : String,
phone : Number,
location : Object,
});
var UserDetailSchema = new Schema({
dob : Number,
gender : String,
followers : [UserSchema],
following : [UserSchema],
posts : [PostSchema],
groups : [GroupSchema]
// ... and so on
});
Let's say:
For a logged-in user, only id, name, email, phone and location are to be returned.
The first model will use a projection query to return the properties in (1).
In the second case, only the UserMinimalSchema would be used to query the entire document.
Essentially both queries return exactly the same amount of data as mentioned in (1).
Assume that average user object is ~16MB limit and there are 1 Million records.
If someone performed such a test/links to documentation, it would be of great help to see how much it will matter to split or not.
I would not use split models:
You'll have to perform a population query everytime you want to look all of the user's data
You're increasing your data storage (you now will have to reference the user in your user details schema.
When Mongo will go do a lookup, it will find references to model instances and only extract data that you've specified in your projection query anyways. It will not load the entire object into memory unless you specify that in your query.

CSV to Mongo using mongoose schema

I'm attempting to get a CSV file to my mongodb collection (via mongoose) while checking for matches at each level of my schema.
So for a given schema personSchema with a nest schema carSchema:
repairSchema = {
date: Date,
description: String
}
carSchema = {
make: String,
model: String
}
personSchema = {
first_name: String,
last_name: String,
car: [carSchema]
}
and an object that I am mapping the CSV data to:
mappingObject = {
first_name : 0,
last_name: 1,
car : {
make: 2,
model: 3,
repair: {
date: 4,
description: 5
}
}
}
check my collection for a match then check each nested schema for a match or create the entire document, as appropriate.
Desired process:
I need to check if a person document matching first_name and last_name exists in my collection.
If such a person document exists, check if that person document contains a matching car.make and car.model.
If such a car document exists, check if that car document contains a matching car.repair.date and car.repair.description.
If such a repair document exists, do nothing, exact match to existing record.
If such a repair document does not exist, push this repair to the repair document for the appropriate car and person.
If such a car document does does not exist, push this car to the car document for the appropriate person.
If such a person document does not exist, create the document.
The kicker
This same function will be used across many schemas, which may be nested many levels deep (current database has one schema that goes 7 levels deep). So it has to be fairly abstract. I can already get the data into the structure I need as a javascript object, so I just need to get from that object to the collection as described.
It also has to be synchronous, since multiple records from the CSV could have the same person, and asynchronous creation could mean that the same person gets created twice.
Current solution
I run through each line of the CSV, map the data to my mappingObject, then step through each level of the object in javascript, checking non-object key-value pairs for a match using find, then pushing/creating or recursing as appropriate. This absolutely works, but it is painfully slow with such large documents.
Here's my full recursing function, which works:
saveObj is the object that I've mapped the CSV on to that matches my schema.
findPrevObj is initially false. path and topKey both are initially "".
lr is the line reader object, lr.resume simply moves on to the next line.
var findOrSave = function(saveObj, findPrevObj, path, topKey){
//the object used to search the collection
var findObj = {};
//if this is a nested schema, we need the previous schema search to match as well
if (findPrevObj){
for (var key in findPrevObj){
findObj[key] = findPrevObj[key];
}
}
//go through all the saveObj, compiling the findObj from string fields
for (var key in saveObj){
if (saveObj.hasOwnProperty(key) && typeof saveObj[key] === "string"){
findObj[path+key] = saveObj[key]
}
}
//search the DB for this record
ThisCollection.find(findObj).exec(function(e, doc){
//this level at least exists
if (doc.length){
//go through all the deeper levels in our saveObj
for (var key in saveObj){
var i = 0;
if (saveObj.hasOwnProperty(key) && typeof saveObj[key] === "string"){
i += 1;
findOrSave(saveObj[key], findObj, path+key+".", path+key);
}
//if there were no deeper levels (basically, full record exists)
if (!i){
lr.resume();
}
}
//this level doesn't exist, add new record or push to array
} else {
if (findPrevObj){
var toPush = {};
toPush[topKey] = saveObj;
ThisCollection.findOneAndUpdate(
findPrevObj,
{$push: toPush},
{safe: true, upsert: true},
function(err, doc) {
lr.resume();
}
)
} else {
// console.log("\r\rTrying to save: \r", saveObj, "\r\r\r");
ThisCollection.create(saveObj, function(e, doc){
lr.resume();
});
}
}
});
}
I'll update for clarity, but the person.find is to check if a person with a matching first and last name exists. If they do exist, I check each car for a match - if the car exists already, there's no reason to add this record. If the car doesn't exist, I push it to the car array for the matching person. If no person was matched, I'd save the entire new record.
Ah, what you want is to update with upsert:
replace
Person.find({first_name: "adam", last_name: "snider"}).exec(function(e, d){
//matched? check {first_name: "adam", last_name: "snider", car.make: "honda", car.model: "civic"}
//no match? create this record (or push to array if this is a nested array)
});
with
Person.update(
{first_name: "adam", last_name: "snider"},
{$push: {car: {make: 'whatever', model: 'whatever2'}}},
{upsert: true}
)
If a match is found, it will push into OR create the car field this subdoucment: {car_make: 'whatever', car_model: 'whatever2'}.
If a match is not found, it will create a new doc that looks like:
{first_name: "adam", last_name: "snider", car: {car_make: 'whatever', car_model: 'whatever2'}}
This cuts your total db round trips in half. However, for even more efficiency, you can use an orderedBulkOperation. This would result in a single round trip to the database.
Here's what that would look like (using es6 here for concision...not a necessity):
const bulk = Person.collection.initializeOrderedBulkOp();
lr.on('line', function(line) {
const [first_name, last_name, make, model, repair_date, repair_description] = line.split(',');
// Ensure user exists
bulk.update({first_name, last_name}, {first_name, last_name}, {upsert: true});
// Find a user with the existing make and model. This makes sure that if the car IS there, it matches the proper document structure
bulk.update({first_name, last_name, 'car.make': make, 'car.model': model}, {$set: {'car.$.repair.date': repair_date, 'car.$.repair.description': repair_description}});
// Now, if the car wasn't there, let's add it to the set. This will not push if we just updated because it should match exactly now.
bulk.update({first_name, last_name}, {$addToSet: {car: {make, model, repair: {date: repair_date, description: repair_description}}}})
});

Modelling reference to embedding document using Mongoose

I am modelling two types of events (events and subevents) in a MongoDB like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ SubeventSchema ]
});
var SubeventSchema = mongoose.Schema({
'name' : String
});
Now when I query a subevent I want to be able to also retrieve data about its corresponding superevent, so that some example data retrieved using Mongoose population feature could look like this:
EventModel.findOne({
name : 'Festival'
})
.populate('subEvent')
.execute(function (err, evt) { return evt; });
{
name : 'Festival',
subEvent: [
{ name : 'First Concert' },
{ name : 'Second Concert' }
]
}
EventModel.findOne({
'subEvent.name' : 'FirstConcert'
}, {
'subEvent.$' : 1
})
.populate('superEvent') // This will not work, this is the actual problem of my question
.execute(function (err, subevt) { return subevt; });
{
name: 'First Concert',
superEvent: {
name: 'Festival'
}
}
A solution I can think of is not to embed but to reference like this:
var EventSchema = mongoose.Schema({
'name' : String,
'subEvent' : [ {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'SubeventSchema'
} ]
});
var SubeventSchema = mongoose.Schema({
'name' : String,
'superEvent' : {
'type' : mongoose.Schema.Types.ObjectId,
'ref' : 'EventSchema'
}
});
I am looking for a solution based on the first example using embedded subevents, though. Can this be achieved and in case yes, how?
I think your mental model of document embedding isn't correct. The major misunderstanding (and this is very common) is that you "query a subevent" (query an embedded document). According to your current Event schema, a Subevent is just a document embedded in an Event document. The embedded SubEvent is not a top-level document; it's not a member of any collection in MongoDB. Therefore, you don't query for it. You query for Events (which are the actual collection-level documents in your schema) whose subEvents have certain properties. E.g. one way people translate the query
db.events.find({ "subEvent" : { "name" : "First Concert" } })
into plain English is as "find all the subevents with the name "First Concert". This is wrong. The right translation is "find all events that have at least one subevent whose name is "First Concert" (the "at least one" part depends on knowledge that subEvent is an array).
Coming back to the specific question, you can hopefully see now that trying to do a populate of a "superevent" on a subevent makes no sense. Your queries return events. The optimal schema, be it subevents embedded in events, one- or two-way references between events and subevents documents in separate collections, or events denormalized into the constituent subevent documents, cannot be determined from the information in the question because the use case is not specified.
Perhaps this is a situation where you need to modify your thinking rather than the schema itself. Mongoose .populate() supports the basic ideas of MongoDB "projection", or more commonly referred to as "field selection". So rather than try to model around this, just select the fields you want to populate.
So your second schema form is perfectly valid, just change how you populate:
EventModel.find({}).populate("subEvent", "name").execute(function(err,docs) {
// "subevent" array items only contain "name" now
});
This is actually covered in the Mongoose documentation under the "populate" section.

Mongoose findOne embedded document by _id

I am trying to push menus to the embedded document. But I am getting not defined findOne in restaurant. I just want to push some documents into the restaurant's menu categories. As you can see in the schema:
var RestaurantSchema = new mongoose.Schema({
contactTelphone : String,
address : String,
branchID : String,
email : String,
restaurantName : String,
userID : String,
menuCategory : [MenuCategorySchema]
});
var MenuCategorySchema = new mongoose.Schema({
menuCategory : String,
menuCategoryAlt : String,
sequence : Number,
menus : [MenuSchema],
restaurantInfo : { type: Schema.Types.ObjectId, ref: 'Restaurant' },
});
var MenuSchema = new mongoose.Schema({
foodName : String,
foodNameAlt : String,
picName : String,
price : String,
rating : Number,
menuSequence : Number,
category : { type: Schema.Types.ObjectId, ref: 'MenuCategory' },
});
exports.menuForMenuCategory = function(newData, callback)
{
console.log(newData);
var menuCategoryId = newData.menuCategoryId;
var restaurantId = newData.restaurantId;
var newMenu = new Menu({
foodName : newData.foodName,
foodNameAlt : newData.foodNameAlt,
picName : newData.picName,
price : newData.price,
cookingCategory : newCookingCategory,
dishSpecial : newDishSpeical
});
Restaurant.findOne( {'_id' : restaurantId }, function(err, restaurant){
if (!err) {
//Is it how to do this? It says "findOne not defined"
restaurant.findOne( "_id" : menuCategoryId, function(err, category){
category.push(newMenu);
});
}
});
}
Subdocuments have an .id() method so you can do this:
myModel.findById(myDocumentId, function (err, myDocument) {
var subDocument = myDocument.mySubdocuments.id(mySubDocumentId);
});
See http://mongoosejs.com/docs/subdocs.html for reference.
restaurant is just an object containing the result to your query. It does not have a findOne method, only Restaurant does.
Since, MenuCategory is just a sub-document of Restaurant, this will come pre-populated whenever you retrieve a restaurant. E.g.
Restaurant.findById(restaurantId, function(err, restaurant){
console.log(restaurant.menuCategory);
// Will show your array of Menu Categories
// No further queries required
});
Adding a new Menu Category is a matter of pushing a new MenuCategory instance to the menuCategory array and saving the restaurant. This means the new menu category is saved with the restaurant and not in a separate collection. For example:
Restaurant.findById(restaurantId, function(err, restaurant){
// I'm assuming your Menu Category model is just MenuCategory
var anotherMenuCategory = new MenuCategory({
menuCategory: "The name",
menuCategoryAlt: "Alternative name",
sequence: 42,
menus: []
});
restaurant.menuCategory.push(anotherMenuCategory);
restaurant.save(); // This will save the new Menu Category in your restaurant
});
Saving a menu to a menu category follows the same procedure since, according to your schema, Menus are embedded sub-documents within each MenuCategory. But note that you need to save the restaurant as its the restaurant collection that is storing all your menus and menu categories as sub-documents
Having answered your question (I hope) I should also point out your schema design should be rethought. Having sub-documents nested within sub-documents is arguably not a good idea. I think I can see where you're coming from - you're trying to implement a SQL-like many-to-one association within your schemas. But this isn't necessary with NoSQL databases - the thinking is somewhat different. Here are some links to some SO questions about efficient schema design with NoSQL databases:
MongoDB Schema Design - Many small documents or fewer large
documents?
How should I implement this schema in MongoDB?
MongoDB relationships: embed or reference?

self referencing schema in mongoose

I've been through several tutorials. I'm still wondering what the best approach for my problem would be. I got the following Schema:
var userSchema = new Schema({
_id : Number,
first_name : String,
last_name : String,
friends : [ Number ],
messages : [{
from: Number,
body : String,
date : { type : Date, default: Date.now}
}]
}, { collection : "user"});
In friends I want to store the ids of user's friends in an array. In message.from I want to store the sender's id of a message.
Ideally I want those ids in friends and message.from to be only ids of valid user entries.
Unfortunately mongodb doesn't enforce referential integrity.
This functionality must be provided by your application.
So in your case: when a user is deleted your application must also remove references to that user in all other user's friends arrays and message fields.

Resources