I'm playing with model associations in sails and I'm curious if it's possible to make a query base on the associated field.
Example:
User.js
attributes:{
classes: { collection: 'Class', via: 'students' }
}
Class.js
attributes: {
type: ...
students: { collection: 'User', via: 'classes'}
}
Is there a way to retrieve specific Classes of a Student base on the type of class because right now everything is being returned when I use .populate(). (maybe similar with the logic below)
User
.findOne({name: 'StudentA'})
.populate('classes')
.where({'classes.type':['type1', 'type2']})
.then(....)
Thanks
You can add a where clause to your populate like so:
User
.findOne({name: 'StudentA'})
.populate('classes', {where: {type: ['type1', 'type2']}})
.exec(...)
In addition to where, you can also use skip, limit and sort in the second argument to populate.
Keep in mind this is still (as of this posting) in beta, so if you find any issues where it seems to not be working correctly, please post them to the Waterline GitHub issues forum.
Related
I am trying to create a family tree website using mongodb. upon creating a new family member, i only want to reference the parent (father) of the person and have mongoose automatically generate a children field in the person model. Here is the Person schema:
Person.add({
fullName: { type: String, initial:true, required: true,label: , index:true, },
parent: {type: Types.Relationship, ref:'Person'},
});
As you can see, upon creating a new person, I would want mongoose to automatically find all people in the Person model with the parent id matching the person I'm creating and generate an array of ids in the children field.
I have done some researches and found that Mongoose Virtuals can provide something like that but i have not found any documentation regarding querying the model to add to the virtual field. Thanks in advance!
You can create a post save hook in your Person schema model, and then find the people with parent id and create/update children array of that person.
use $addToSet instead of $push so that same childrens are not pushed more than once.
Try this:
PersonSchema.post('save', function(doc, next) {
this.findOneAndUpdate({_id : doc.parent},{$addToSet : { children : doc._id}})
next();
});
Read more about mongoose hooks for detailed information.
I have following Mongo Schemas(truncated to hide project sensitive information) from a Healthcare project.
let PatientSchema = mongoose.Schema({_id:String})
let PrescriptionSchema = mongoose.Schema({_id:String, patient: { type: Number, ref: 'Patient', createdAt:Date }})
let ReportSchema = mongoose.Schema({_id:String, patient: { type: Number, ref: 'Patient', createdAt:Date }})
let EventsSchema = mongoose.Schema({_id:String, patient: { type: Number, ref: 'Patient', createdAt:Date }})
There is ui screen from the mobile and web app called Health history, where I need to paginate the entries from prescription, reports and events sorted based on createAt. So I am building a REST end point to get this heterogeneous data. How do I achieve this. Is it possible to create a "View" from multiple schema models so that I won't load the contents of all 3 schema to fetch one page of entries. The schema of my "View" should look like below so that I can run additional queries on it (e.g. find last report)
{recordType:String,/* prescription/report/event */, createdDate:Date, data:Object/* content from any of the 3 tables*/}
I can think of three ways to do this.
Imho the easiest way to achieve this is by using an aggregation something like this:
db.Patients.aggregate([
{$match : {_id: <somePatientId>},
{
$lookup:
{
from: Prescription, // replicate this for Report and Event,
localField: _id,
foreignField: patient,
as: prescriptions // or reports or events,
}
},
{ $unwind: prescriptions }, // or reports or events
{ $sort:{ $createDate : -1}},
{ $skip: <positive integer> },
{ $limit: <positive integer> },
])
You'll have to adapt it further, to also get the correct createdDate. For this, you might want to look at the $replaceRoot operator.
The second option is to create a new "meta"-collection, that holds your actual list of events, but only holds a reference to your patient as well as the actual event using a refPath to handle the three different event types. This solution is the most elegant, because it makes querying your data way easier, and probably also more performant. Still, it requires you to create and handle another collection, which is why I didn't want to recommend this as the main solution, since I don't know if you can create a new collection.
As a last option, you could create virtual populate fields in Patient, that automatically fetch all prescriptions, reports and events. This has the disadvantage that you can not really sort and paginate properly...
When viewing a sub-document with Robomongo I see something like this:
"views" : [
ObjectId("53a478431275cf0f3d91e27d"),
ObjectId("53a478431275cf0f3d91e27d")
]
But when I pull down the object through Mongoose into node.js, I see something like this:
views:
[ { _bsontype: 'ObjectID',
id: 'T\u001aôj#Ü«m¢©Ö',
viewDate: '2015-07-07T23:21:32.259Z' } ]
Yes, the schema is a little different, and I'm trying to write a script to remediate the data into the new format.
The schema is currently
views: [{view:{type: Schema.Types.ObjectId, ref: 'users'},viewDate:{type: Date, default: Date.now}}],
But
A) Why does the view object look all messed up in the latter, and
B) How can I get what I see in Robomongo? (Answered. See edit)
EDIT: Question B is answered. If I do .lean() to my query, then I'll be able to get it back as a non-mongoose object and it'll look how I expect it to look. So that just leaves question A
I managed to reproduce this.
First, you declared a schema similar to this:
views : { type : Schema.Types.ObjectId, ref : 'users' }
You created and wrote documents to the database using that schema.
Then you changed the schema to your current:
views: [{
view : { type: Schema.Types.ObjectId, ref: 'users' },
viewDate : { type: Date, default: Date.now }
}]
Using that schema, you are reading the documents that you wrote to the database using the first schema.
Those schema are fundamentally different: the first is stored as a single ObjectId in the database (the term "subdocument" is a bit confusing, because in Mongoose, subdocuments are documents that are stored with their parent document; the method you're using is called "population" in Mongoose-speak), but the second schema makes views an array of documents that have two properties (view, which is stored as an ObjectId and viewData which is a date).
This confuses Mongoose because it tries to apply the second schema to documents that were written using the first schema, and because of that, it's showing the internal representation of an ObjectId object instead of a stringified version of it.
This also explains why .lean() shows the correct results, because that tells Mongoose to return raw documents (as they are stored in the database) instead of trying to convert them according to the schema.
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.
Working on a project in KeystoneJS and I'm having trouble figuring out the mongoose relationship bit.
According to the keystone docs, let's say we have the following models: User and Post. Now a post has a relationship to a user, so I'll write:
Post.add({
author: { type: Types.Relationship, ref: 'User' }
});
and then:
User.relationship({ path: 'posts', ref: 'Post', refPath: 'author' });
Now, I want to be able to see all posts related to that User without having to query for both a User and Posts. For example, if I queried for a user object I would like to be able to do user.posts and have access to those related posts. Can you do this with mongoose/keystone?
As far as I understand, keystone's List Relationship has nothing to do with mongoose and querying. Instead, it is used by keystone's admin UI to build out the relationship queries before rendering them in the view. This said I would forget User.relationship(...); solving your problem, although you want it for what I just mentioned.
The following should work fine based on your schema, but only populates the relationship on the one side:
var keystone = require('keystone');
keystone.list('Post').model.findOne().populate('author', function (err, doc) {
console.log(doc.author.name); // Joe
console.log(doc.populated('author')); // '1234af9876b024c680d111a1' -> _id
});
You could also try this the other way, however...
keystone.list('User').model.findOne().populate('posts', function (err, doc) {
doc.posts.forEach(function (post) {
console.log(post);
});
});
...mongoose expects that this definition is added to the Schema. This relationship is added by including this line in your User list file:
User.schema.add({ posts: { type: Types.Relationship, ref: 'Post', many: true } })
After reading the keystone docs, this seems to be logically equivalent the mongoose pure way, User.schema.add({ posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] });. And now you are now maintaining the relationship on both lists. Instead, you may want to add a method to your keystone list.
User.schema.methods.posts = function(done){
return keystone.list('Post').model.find()
.where('author', this.id )
.exec(done);
};
By adding a method to your User list, it saves you from persisting the array of ObjectIds relating the MongoDB document back to the Post documents. I know this requires a second query, but one of these two options look to be your best bet.
I found this on their github https://github.com/Automattic/mongoose/issues/1888, check it for context, but basically says to use the keystone populateRelated() method. I tested it and does work
// if you've got a single document you want to populate a relationship on, it's neater
Category.model.findOne().exec(function(err, category) {
category.populateRelated('posts', function(err) {
// posts is populated
});
});
I'm aware the question is old but this has to be out there for further reference