Mongoose: deep population - javascript

I have Patient model contains ref to Path :
const PatientSchema = Schema({
idPatient: Schema.Types.ObjectId,
firstName: String,
lastName: String,
path: {type: Schema.Types.ObjectId, ref: 'Path'}
});
Path model with array of ref to Zone:
const PathSchema = connection.mongoose.Schema({
zones: [{
type: connection.mongoose.Schema.Types.ObjectId,
ref: 'Zone'
}],
});
and finally Zone model:
const ZoneSchema = connection.mongoose.Schema({
name: String,
duration: Number,
});
I'm trying to get all the patient with their path and the zones for each path inside patient:
Here an example of data with a simple populate on the patient:
[
{
"_id": "5d00b7dab927301ad392e6e4",
"idPatient": "5d00b7dab927301ad392e6e5",
"firstName": "Amine",
"lastName": "Harbaoui",
"path": {
"_id": "5d010263b927301ad392e6ea",
"zones": [
"5d010c72b927301ad392e6eb",
"5d010cf7b927301ad392e6ec"
]
}
}
]
And here how I tried to get what I want:
Patient.find()
.lean()
.populate('path')
.exec((error, patients) => {
if (error) {
console.log(error);
} else {
const zones = patients.map(p => p.path.zones);
Path.populate(zones, {
path: 'zones'
}, (error, data) => {
if (error) {
console.log(error);
} else {
console.log(data);
}
})
}
})
But here's the exception I get:
{ MissingSchemaError: Schema hasn't been registered for model "Zone".
Use mongoose.model(name, schema)
at new MissingSchemaError (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/error/missingSchema.js:22:11)
at NativeConnection.Connection.model (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/connection.js:888:11)
at getModelsMapForPopulate (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:4337:57)
at populate (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:3915:21)
at _populate (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:3885:5)
at utils.promiseOrCallback.cb (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:3858:5)
at Object.promiseOrCallback (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/utils.js:248:12)
at Function.Model.populate (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:3857:16)
at Patient.find.lean.populate.exec (/home/amine/ubudu/app_mn/sprint0/server/controllers/patientController.js:61:14)
at /home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/model.js:4733:16
at /home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/utils.js:263:16
at _hooks.execPost (/home/amine/ubudu/app_mn/sprint0/server/node_modules/mongoose/lib/query.js:4224:11)
at /home/amine/ubudu/app_mn/sprint0/server/node_modules/kareem/index.js:135:16
at process._tickCallback (internal/process/next_tick.js:61:11) message: 'Schema hasn\'t been registered for model "Zone".\nUse
mongoose.model(name, schema)', name: 'MissingSchemaError' }

As long as you are using one of the more recent versions of mongoose, you can populated deeply nested values in one query.
Patient.find()
.populate({
path: 'path',
populate: {
path: 'zones'
}
}).exec()

Related

How to pull items from reference array based on condition in array?

I am working in creating a todo list and one of the action I want users to do is delete all completed todos in one click. I have my models here, and the code I have, I have been reading trying to figure it out, but can't find it anywhere. Thanks in advance.
User Model
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
todos: [{
type: mongoose.Types.ObjectId,
ref: 'Todo'
}]
});
Todo model:
const TodoSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
content: {
type: String,
required: true,
},
completed: {
type: Boolean,
default: false
},
});
This is what I have now. I get a "Cast to ObjectId failed for value true at path todos.
router.delete('/delete/completed', auth, async (req, res) => {
try {
const userTodos = await User.update(
{'_id':req.user.id},
{ $pull: { 'todos': { 'completed': true } } },
{ multi: true }
);
if (!userTodos) {
return res.status(400).send('Server error');
}
return res.json({ userTodos });
//return res.json({msg: 'All completed removed'})
} catch (err) {
console.error(err.message);
return res.status(404).json({ msg: 'Something went wrong, try again' });
}
});
If you are (as it seems from your code) using mongoose, you could use mongoose's populate feature:
const userTodos = await User.find(
{'_id':req.user.id}).populate('todos', {
match: {completed: true}
});
please note, however, that you'll need to delete both the documents in the todos collection, AND the todo reference in the user's todos array. You may consider to remove one side of the reference, see the pros and cons of two-way referencing here

How do I reference mongoose model to another model?

I have a mongoose schema for stories that looks like this:
{
id: {
type: Number,
default: 0
},
title: {
type: String,
maxLength: 60
},
author: {
userid: {
type: Number
},
username: {
type: String
}
}
chapters: [chapter],
numchapters: {
type: Number,
default: 1
},
favs: {
type: Number,
default: 0
},
completed: {
type: Boolean,
default: false
}
}
What I'm trying to do is reference a document in a separate collection (users), and use the values of its userid and username fields in the author field.
how do I do this?
current code:
storyobj.populate('author', {path: 'author', model: 'users', select: 'userid username'}, (err) => {
if (err) {
console.log(err)
}
})
just in case it's relevant, the structure of the users collection looks like this:
{
username: {
type: String,
},
email: {
type: String,
},
password: {
type: String,
},
userid: {
type: Number
},
isAdmin: {
type: Boolean,
default: false
},
banned: {
type: Boolean,
default: false
}
}
EDIT:
I've changed the author field in the Stories model to look like this:
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
This is so I tell Mongoose, "Hey, I want this field to reference a user in the User collection".
Here are some more details that I hope will be of help.
Full code:
var storydb = require('../models/stories/story');
var chapterdb = require('../models/stories/chapter');
var userdb = require('../models/user');
const file = JSON.parse(fs.readFileSync('test.json')); // this is a file with the data for the stories I am trying to insert into my database
for (const d in file) {
var storyobj = new storydb({
id: d,
chapters: []
});
for (let e = 0; e < file[d].length; e++) {
var abc = file[d][e];
var updatey = {
chaptertitle: abc.chapter,
chapterid: parseInt(abc.recid),
words: abc.wordcount,
notes: abc.notes,
genre: abc.gid.split(', '),
summary: abc.summary,
hidden: undefined,
loggedinOnly: undefined,
posted: new Date(Date.parse(abc.date)),
bands: abc.bandnames.split(', ')
};
var kbv = getKeyByValue(userlookup, abc.uid);
storyobj.title = abc.title;
storyobj.numchapters = file[d].length;
storyobj.favs = file[d][0].numfavs;
updatey.characters = abc.charid.split(/, |,/g);
storyobj.chapters.push(updatey)
}
storyobj.save();
}
In file, there's a unique ID representing the author of each story. kbv returns the userid associated with that unique ID (note that they're NOT the same).
Now, here's where I'm having trouble:
What I want to do is find a user matching the userid in kbv, and make that the author property in the story model.
The code I'm currently using to try and achieve that:
storydb.findOne({storyobj}, 'author').populate("author", (f) => console.log(f));
const Stories = require("./path/to/model");
Stories
.find({ /* query */ }, { /* projection */ })
.populate("author.username", ["userid", "username"])
.then(/* handle resolve */)
.catch(/* handle rejection */)
For this to work, you have to add a ref key to the userid key in your model, where the ref value is the name of the model it's referencing.
Story.model.js
const StorySchema = new Schema({
author: {
userid: { type: Schema.Types.ObjectId, ref: "users", required: true },
/* other props */
}
/* other props */
});

Dynamic query in mongo and NodeJs asking for fields of documents embedded?

I am trying to make a dynamic query based on multiple selection of the user.
In my application I have the Publication schema that has the Pet schema embedded as follows:
var status = ["public", "private", "deleted"];
var publication_schema = new Schema({
pet:{
type: Schema.Types.ObjectId,
ref: "Pet"
},
status: {
type: String,
enum: status,
default: status[0]
}
});
module.exports = mongoose.model('Publication', publication_schema);
var pet_schema = new Schema({
type: {
type: String,
require: true
},
createdDate: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Pet', pet_schema);
Insyde an async method I build the query, getting all the user input values from the object filter, also I have the query object where I push the different criteria and use it with an $and
let query = {};
let contentQuery = []
if (filter.public && !filter.private) {
contentQuery.push({ status: { $eq: "public" } });
} else if (filter.privada && !filter.public) {
contentQuery.push({ status: { $eq: "private" } });
}
query = { $and: contentQuery }
try {
const publication = await Publication.find(query).populate('pet');
} catch (e) {
console.log(e)
}
the problem is when I want to add more criteria such as follows:
if (filter.specie) { // for example filter.specie equals 'cat'
contentQuery.push({ pet: { type: { $eq: filter.specie } } });
}
I get the error:
'Cast to ObjectId failed for value "{ type: { \'$eq\': \'cat\' } }" at path "pet" for model "Publication"',
name: 'CastError',
stringValue: '"{ type: { \'$eq\': \'cat\' } }"',
kind: 'ObjectId',
value: { type: { '$eq': 'cat' } },
path: 'pet',
reason: undefined,
model: Model { Publication } }
So. How can I do to query the fields of publication and also the pet fields inside publication?
You can have a look on Populate Query Conditions
Instead of .populate('pet') you could do something like
Publication.find({})
.populate({
path: 'pet',
match: { specie: 'cat'},
// You can select the fields you want from pet, or remove the select attribute to select all
select: 'name -_id',
// Here you could add options (e.g. limit)
options: { limit: 5 }
}).exec();
The above query will get you all Publications with pet.specie equals to 'cat'

Deep query to nested types in GraphQL return NULL

Have a strange problem...
Query to nested types return null.
But, if I return anything in parent type - resolve return right result
My code:
import { GraphQLList, GraphQLString, GraphQLID, GraphQLObjectType, GraphQLSchema } from 'graphql';
import AdminModel from '../models/Admin.model';
const AdminType = new GraphQLObjectType({
name: 'AdminType',
fields: {
_id: { type: GraphQLID },
login: { type: GraphQLString },
password: { type: GraphQLString }
}
});
const AdminRooteType = new GraphQLObjectType({
name: 'AdminRooteType',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve() {
return AdminModel.find({})
}
}
}
})
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQuery',
fields: {
admin: {
type: AdminRooteType,
resolve() {
// EMPTY RESOLVE - EMPTY RESULT
}
}
}
})
});
Query:
{
admin {
getAdmins {
login
}
}
}
Result:
{
"data": {
"admin": null
}
}
If I changed returned value in fields admin in RootQuery:
import { GraphQLList, GraphQLString, GraphQLID, GraphQLObjectType, GraphQLSchema } from 'graphql';
import AdminModel from '../models/Admin.model';
const AdminType = new GraphQLObjectType({
name: 'AdminType',
fields: {
_id: { type: GraphQLID },
login: { type: GraphQLString },
password: { type: GraphQLString }
}
});
const AdminRooteType = new GraphQLObjectType({
name: 'AdminRooteType',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve() {
return AdminModel.find({})
}
}
}
})
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQuery',
fields: {
admin: {
type: AdminRooteType,
#resolve() {#
#// RETURN ANYTHING HERE:#
# return 'foobar'#
}
}
}
})
});
I've got expected result:
{
"data": {
"admin": {
"getAdmins": [
{
"login": "123"
},
{
"login": "12asdf3"
}
]
}
}
}
What is right solution for this issue? (without using dummy values in return)
Thank's a lot!
What you are seeing is the expected behavior. Imagine we have a User type with some fields:
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
}
})
And a way to fetch a single user:
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: AdminRooteType,
resolve: () => getUser(),
},
},
})
If getUser returns an object representing a User, the resolvers for all the fields (i.e. id and name on the User type will be called.
When those fields (and whatever child fields they might have) resolve, you end up with a User object for the entire user field to return. A response might look something like:
"data": {
"user": {
"id": 1,
"name": "Maria",
"comments": [
{
"id": 1,
// and so on...
}
]
}
}
Now, consider what happens when a user is not found and we return null instead. Our response looks like this:
"data": {
"user": null
}
It doesn't make sense to call any of the resolvers for the User fields. Would you expect the API to still return an id or name in this case? If it did, what values would those fields have? If we just returned a null id and name, how would the client distinguish that object from a User that existed but really did have id and name null values?
The point is, if a field returns a GraphQLObjectType and it resolves to null, none of the resolvers on the GraphQLObjectType will be called.
By unnecessarily nesting your getAdmins field inside another GraphQLObjectType, you're forced to return some kind of object inside the resolver for admin. So you will need to either live with that, or avoid creating an AdminRootType altogether and just put the getAdmins field on your Query type directly, as per convention:
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve: () => AdminModel.find({}),
},
},
})

Updating a subdoc array in Mongoose without getting the parent

Schema:
var viewSchema = new Schema({
active: Boolean
, path: String
})
var pageSchema = new Schema({
name: String
, desc: String
, url: String
, views: [viewSchema]
})
In order to add a new view to an existing page I am doing:
Page
.findOne({ id: pageId })
.exec(function (err, page) {
page.views.push({ path: path })
page.save(function(err) {
//saved
})
})
Is there a way to do the same without actually getting the page?
Page.update({ id: pageId }
, { SOMETHING }
, function(err){
//updated
})
It works..
Page.update({ _id: page.id},{ $push: { "views": { path: "#page", active: true } } })
thanks #Alistair-Nelson

Categories