I have the following schema in my Node js / express app where each warehouse can optionally have a parent warehouse. I wrote the code to save warehouse, but can't figure out how to retrieve any warehouse (which has a parent) and get its parent warehouse name...so was wondering if there is any way I can do that in one call? Thanks
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var warehouseSchema = new Schema({
name: { type: String, required: true },
parentID: { type: String, ref: 'Warehouse' }
});
module.exports = mongoose.model('Warehouse', warehouseSchema);
Just as Philipp said, you can't do it with a single MongoDB auery.
But you can do it with single Mongoose command, using its Query Population feature:
Warehouse.findById(warehouse_id).populate('parentID').exec(function(err, doc) {
if (err) {
// something get wrong
} else {
// doc.parentID is a parrent Warehouse here
}
})
Internally, Mongoose will make two separate queries to MongoDB (one for the child document, and then another for its parent), bu for you it'll look like a single command.
MongoDB can't do JOINs on the database. As long as the parent is a reference in the warehouse object, you can not retrieve both with one query. You first have to query the warehouse(s) and then resolve the reference(s) with a second query.
To avoid the second query you might want to look into embedding the required information about the parent-document in the child-document. Duplicate information in MongoDB isn't as abnormal as in a relational database.
Related
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.
I'm trying to build some sort of a social media app using node.js and mongoDB.
I have a mongoose schema for 'User', and when i render some user page on the app, it needs to also show all of his posts/images/list of friends and etc...
right now i have a mongoose schema for 'UserPost' and also for 'Image', and when i save an image for example, it has a field which keeps the username of the user who uploaded it, so when i render the user page it finds all of his images.
It is the first time i'm dealing with db's so i heard that i might have to use a reference data instead of embedded data.
can someone explain to how should i organize the data model for my app?
It's very handful to use mongoose population for handling db references
Define your schemas like these:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var userSchema = Schema({
name : String,
posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
var postSchema = Schema({
title : String,
images : [{ url: String, filename: String }]
});
var User = mongoose.model('User', userSchema);
var Post = mongoose.model('Post', postSchema);
According to mongoose population docs, you can get all your data:
User.findOne().populate('posts').exec(function(error, user) {
console.log(user.posts) // there are populated posts objects inside array
})
Not sure, is it a good idea to use separated collection for image uploads, it's simpier to embed it inside Post (or User), but you may always add { type: Schema.Types.ObjectId, ref: 'Image' } for images
MongoDB is a NoSql DBMS. It means, you schould not create references between data fields, because the performance coming from NoSql will be killed: https://en.wikipedia.org/wiki/NoSQL
But, if you are really thinking you need references, checkout this: http://docs.mongodb.org/master/reference/database-references/
You can reference to a data document in the mongoDB by the _id ("Page"=>"createdBy" = "User"=>"_id" for example).
It depends of what kind of data you want to embed. MongoDB has object size limits according to the storage engine you use. Thus you should predict or estimate the the size of the object you want to embed.
See more about limits here: http://docs.mongodb.org/master/reference/limits/
See more about references here: http://docs.mongodb.org/master/reference/database-references/
I have a product model, it has many fields. Some of them are dedicated to front-end application, ex:
var GameSchema = new Schema({
likes: {
type: [{
type: Schema.ObjectId,
ref: 'User'
}]
},
likes_count: {
type: Number
}
});
I don't need likes_count field in Db, but controller returns only fields that model have, so i add likes_count field to db model
exports.some_method = function(req, res){
var game = req.game;
game.likes_count = game.likes.length
res.json(game);
}
Is there a way to add extra data to db model when sending request without having them in db?
Please note, problem is not in likes_count field itself, i have different models, but the point is having extra data on db model.
For those who still interested, mongo_db mongoose(#robertklep) has virtual fields, that can be used as temporary data field, that doesn't exist in database
GameSchema.virtual('likes_count').get(function () {
return this.likes.length;
});
And note, your model must have permission for virtuals like this, so that you can use it inside controllers
var UserSchema = new Schema({
username: {
type: String
}
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
"Is there a way to add extra data to db model when sending request without having them in db?"
You may be able to do so from a driver's perspective and I'll leave that to those who know abut such things. Check out the following post Mapping a private backing field with MongoDB C#.
I can answer from the MongoDB engine & server processes aspect; if you are looking for a way to flag a field in the JSON document to make it private when sent to the actual CRUD request the MongoDB engine receives then no.
However, you could intercept the JSON prior to the actual CRUD request and transform it. The JSON you are generating is not inserted until you execute one of the INSERT, Modify, or Update statements. The pseudo steps would be to generate a JSON document, send it to a broker\wrapper etc in front of MongoDB, and then transform it by removing the fields in question, then send the new object as a CRUD request to the MongoDB engine.
Here is my Mongoose Schema:
var SchemaA = new Schema({
field1: String,
.......
fieldB : { type: Schema.Types.ObjectId, ref: 'SchemaB' }
});
var SchemaB = new Schema({
field1: String,
.......
fieldC : { type: Schema.Types.ObjectId, ref: 'SchemaC' }
});
var SchemaC = new Schema({
field1: String,
.......
.......
.......
});
While i access schemaA using find query, i want to have fields/property
of SchemaA along with SchemaB and SchemaC in the same way as we apply join operation in SQL database.
This is my approach:
SchemaA.find({})
.populate('fieldB')
.exec(function (err, result){
SchemaB.populate(result.fieldC,{path:'fieldB'},function(err, result){
.............................
});
});
The above code is working perfectly, but the problem is:
I want to have information/properties/fields of SchemaC through SchemaA, and i don't want to populate fields/properties of SchemaB.
The reason for not wanting to get the properties of SchemaB is, extra population will slows the query unnecessary.
Long story short:
I want to populate SchemaC through SchemaA without populating SchemaB.
Can you please suggest any way/approach?
As an avid mongodb fan, I suggest you use a relational database for highly relational data - that's what it's built for. You are losing all the benefits of mongodb when you have to perform 3+ queries to get a single object.
Buuuuuut, I know that comment will fall on deaf ears. Your best bet is to be as conscious as you can about performance. Your first step is to limit the fields to the minimum required. This is just good practice even with basic queries and any database engine - only get the fields you need (eg. SELECT * FROM === bad... just stop doing it!). You can also try doing lean queries to help save a lot of post-processing work mongoose does with the data. I didn't test this, but it should work...
SchemaA.find({}, 'field1 fieldB', { lean: true })
.populate({
name: 'fieldB',
select: 'fieldC',
options: { lean: true }
}).exec(function (err, result) {
// not sure how you are populating "result" in your example, as it should be an array,
// but you said your code works... so I'll let you figure out what goes here.
});
Also, a very "mongo" way of doing what you want is to save a reference in SchemaC back to SchemaA. When I say "mongo" way of doing it, you have to break away from your years of thinking about relational data queries. Do whatever it takes to perform fewer queries on the database, even if it requires two-way references and/or data duplication.
For example, if I had a Book schema and Author schema, I would likely save the authors first and last name in the Books collection, along with an _id reference to the full profile in the Authors collection. That way I can load my Books in a single query, still display the author's name, and then generate a hyperlink to the author's profile: /author/{_id}. This is known as "data denormalization", and it has been known to give people heartburn. I try and use it on data that doesn't change very often - like people's names. In the occasion that a name does change, it's trivial to write a function to update all the names in multiple places.
SchemaA.find({})
.populate({
path: "fieldB",
populate:{path:"fieldC"}
}).exec(function (err, result) {
//this is how you can get all key value pair of SchemaA, SchemaB and SchemaC
//example: result.fieldB.fieldC._id(key of SchemaC)
});
why not add a ref to SchemaC on SchemaA? there will be no way to bridge to SchemaC from SchemaA if there is no SchemaB the way you currently have it unless you populate SchemaB with no other data than a ref to SchemaC
As explained in the docs under Field Selection, you can restrict what fields are returned.
.populate('fieldB') becomes populate('fieldB', 'fieldC -_id'). The -_id is required to omit the _id field just like when using select().
I think this is not possible.Because,when a document in A referring a document in B and that document is referring another document in C, how can document in A know which document to refer from C without any help from B.
I'm calling MongoDB from my Node app using Mongoose like this:
var query = itemModel.find();
query.exec(function (err, items) {
console.log(err);
socket.emit("items", items);
});
I have 3 models defined like this:
var Schema = mongoose.Schema
, ObjectId = Schema.ObjectId;
var playerModel = require('./models/player.js').make(Schema, mongoose);
var characterModel = require('./models/character.js').make(Schema, mongoose, ObjectId);
var itemModel = require('./models/item.js').make(Schema, mongoose);
my models look like this:
function make(Schema, mongoose) {
itemSchema = new Schema({
name: String
, bonus: [{
type: String
, value: Number
}]
, price: Number
, slot: String
});
return mongoose.model('Character', characterSchema);
}
exports.make = make;
For some reason I'm getting all documents, regardless of them being items, characters or players. Since I'm calling find() on itemModel I was expecting only Items, what am I doing wrong?
The model that you have shown appears to be the item model, but you are creating the model with the 'Character' name. This means that you told Mongoose about the scheme for an item and that it is stored in the 'character' collection. Assuming you've done the same for each other model (Character/Player), you've been Mongoose that everything is in the same collection.
Then you query that collection and you seem surprised that everything is stored in the same collection. It strikes me as if you have little experience with Mongoose/MongoDB, so I will suggest you download and learn to love MongoVUE. This application is a good GUI to see what is going on under the hood of the MongoDB database. While developing, you also might want to enable debugging so you can see what queries mongoose is launching to the server (mongoose.set('debug', true)).