When I use Document.get on a nested document it returns undefined, where as when I use Document.get with the full path it works
Example:
const PostsSchema = new mongoose.Schema({
author: {
name: String
}
});
const Posts = mongoose.model('posts', PostsSchema);
const doc = new Posts({
author: {
name: 'Harry'
}
});
console.log(doc.get('author.name'));
// "Harry"
console.log(doc.author.get('name'));
// undefined
I believe the difference between the two ways you've presented is related to the difference between Subdocuments and nested paths as explained in the official doc: Subdocuments versus Nested Paths
Comparing to the examples given, 'author' is not a Subdocuments of the 'Posts', it's a property defined in the Schema, therefore, the correct syntax, as presented in the official doc Document.prototype.get(), is to pass the path as a string (as the first way you typed):
doc.get(path,[type],[options])
path «String»
[type] «Schema|String|Number|Buffer|*» optionally specify a type for on-the-fly attributes
[options] «Object»
[options.virtuals=false] «Boolean» Apply virtuals before getting this path
[options.getters=true] «Boolean» If false, skip applying getters and just get the raw value
Related
I'm trying to update multiple nested object in mongoose using a certain if condition and the object I want to update (or create if it doesn't exist) is located multiple layers deep into my wrapper schema. My simplified structure looks like this:
const C = mongoose.Schema({
data: String
})
const B = mongoose.Schema({
createdAt: Date,
tasks: [C]
})
const A = mongoose.Schema({
client_id: String,
objects: [B]
})
const AModel = mongoose.model("A", A)
First I need to find a specific A model by it's client_id;
Then find a B model from the objects array with certain createdAt value (e.g. the one with the date matching today's date) or create an empty B model if nothing matching my condition was found;
Push into tasks array of the found (or a newly created) B object a C object with a certain condition (e.g. if there's C no object with the same data field).
I don't think it's a good way to do this via basic js code as my models could store a vast amount of data and it doesn't sound good to modify all that data every time I need to insert a single object so I'm looking for a pure mongoose-query solution.
So far I've came up with something like this but I'm not sure how to properly locate and update nested objects inside my B model.
await AModel.updateOne(
{ client_id: clientId },
{
$push: {
objects: { $where: this.createdAt.toDateString() == today.toDateString() },
},
},
{ upsert: true, setDefaultsOnInsert: true }
)
I am trying to do a complete match using objectContaining instance in jest.
Below is the example object I am trying to match:
const queryCommandInput = {
KeyConditionExpression: '---keyConditionExpression---',
ProjectionExpression: '---projectionExpression----',
FilterExpression: '---filterExpression----',
ExpressionAttributeValues: { //[1] - Unable to match from here
'value1': { S: 'example1' },
'value2': { S: 'example2' },
'value3': { N: 12345 },
},
TableName: 'tableName'
}
const queryCommand = new QueryCommand(queryCommandInput);
//[2]
expect(DynamoDBClient.prototype.send).toBeCalledWith(expect.objectContaining({input: expect.objectContaining(queryCommandInput) }))
[1] - Unable to do a multilevel match without using multi objectContaining ([2])
I could have another object containing, and then another objectContaining after that. But not able to find any other better way to automatically iterate and match all the key/values
Found the solution. I am sure this will be helpful for a lot of developers out there.
Destructuring and overriding the behaviour for the properties which we know a match would fail, works!!
In this scenario, queryCommand is an instance of a class QueryCommand. The below statement would fail because even though the inputs are the same, the instances are different.
expect(DynamoDBClient.prototype.send).toBeCalledWith(queryCommand) //the queryCommand is a different instance from the instance being sent to the send method of DynamoDBClient conrtuctor, hence it fails.
That is why I was using the objectContaining class, but that only matches one level. If the object is nested, multiple nested objectContaining must be constructed. The best way is to not use an objectContaining constructor, but to match the instances itself and restructure it so that we only check the types of the properties which we know will change, and complete/deep check the properties that we want.
expect(DynamoDBClient.prototype.send).toBeCalledWith({ ...queryCommand, middlewareStack: expect.any(Object) })
I want to generate field in mongoose with function.
Because there are many fields, but they are much the same, I wanna use function to create them to keep code short.
I wrote a function, but there exists lints.
import { Schema } from 'mongoose'
function fieldGen(name, type="string", isRequired=true) {
var field = {}
field[name] = {
type: type,
required: isRequired
}
return {...field}
}
const testSchema = new Schema({
fieldGen("firstname")
fieldGen("lastname")
fieldGen("location")
})
In VS Code, Problem shows as below
Identifier expected. ts(1003) [20, 12]
I expect first argument "firstname" matches name in function, and return object.
You're adding values to your testSchema object without giving them names.
Also, you're spreading the properties of the field object into a new object literal. That doesn't accomplish anything. Just returning the field object would yield the same result.
I see what you are trying to do. If you debug this in smaller steps and take a closer look at the data you are handling, I think you'll figure it out on your own.
I have a document from a mongoose find that I want to extend before JSON encoding and sending out as a response. If I try adding properties to the doc it is ignored. The properties don't appear in Object.getOwnPropertyNames(doc) making a normal extend not possible. The strange thing is that JSON.parse(JSON.encode(doc)) works and returns an object with all of the correct properties. Is there a better way to do this?
Mongoose Models inherit from Documents, which have a toObject() method. I believe what you're looking for should be the result of doc.toObject().
http://mongoosejs.com/docs/api.html#document_Document-toObject
Another way to do this is to tell Mongoose that all you need is a plain JavaScript version of the returned doc by using lean() in the query chain. That way Mongoose skips the step of creating the full model instance and you directly get a doc you can modify:
MyModel.findOne().lean().exec(function(err, doc) {
doc.addedProperty = 'foobar';
res.json(doc);
});
JohnnyHK suggestion:
In some cases as #JohnnyHK suggested, you would want to get the Object as a Plain Javascript.
as described in this Mongoose Documentation there is another alternative to query the data directly as object:
const docs = await Model.find().lean();
Conditionally return Plain Object:
In addition if someone might want to conditionally turn to an object,it is also possible as an option argument, see find() docs at the third parameter:
const toObject = true;
const docs = await Model.find({},null,{lean:toObject});
its available on the functions: find(), findOne(), findById(), findOneAndUpdate(), and findByIdAndUpdate().
NOTE:
it is also worth mentioning that the _id attribute isn't a string object as if you would do JSON.parse(JSON.stringify(object)) but a ObjectId from mongoose types, so when comparing it to strings cast it to string before: String(object._id) === otherStringId
the fast way if the property is not in the model :
document.set( key,value, { strict: false });
A better way of tackling an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id)
}
})
and now it will work.
For reference, see: http://mongoosejs.com/docs/api.html#document_Document-toObject
To get plain object from Mongoose document, I used _doc property as follows
mongooseDoc._doc //returns plain json object
I tried with toObject but it gave me functions,arguments and all other things which i don't need.
The lean option tells Mongoose to skip hydrating the result documents. This makes queries faster and less memory intensive, but the result documents are plain old JavaScript objects (POJOs), not Mongoose documents.
const leanDoc = await MyModel.findOne().lean();
not necessary to use JSON.parse() method
You can also stringify the object and then again parse to make the normal object.
For example like:-
const obj = JSON.parse(JSON.stringify(mongoObj))
I have been using the toObject method on my document without success.
I needed to add the flattenMap property to true to finally have a POJO.
const data = document.data.toObject({ flattenMaps: true });
I want to get references in mongoDB using nodejs/mongoose.
In the documentation I read that there are two options: Manual References or DBRefs.
Since they state, its recommended to use Manual References, I decided to set up a schema in the following way:
var schema = new mongoose.Schema({
name : String,
reference : mongoose.Schema.ObjectId
});
Question: If I retrieve an array of these objects from my collection, how do I resolve the references in a good practice way?
My Idea was to use Nimble and parallelize the necessary requests. I wanted to do something like
flow.parallel(functions, function() {
return result;
});
where I dynamically fill an array of functions
var functions = []
which I pass then to nimble.
(kind of this SO-question: Javascript Array of Functions)
Question: Is this practical? The array of functions-thing seems kind of not really the way to go to me. But I don't see any alternative, since nimble needs to be called with a static number of functions.
You can use Mongoose's support for reference population to efficiently follow references.
var schema = new mongoose.Schema({
name : String,
reference : { type: mongoose.Schema.ObjectId, ref: 'OtherModel' }
});
var MyModel = mongoose.model('MyModel', schema);
MyModel.find().populate('reference').exec(function(err, docs) {...});
In the above example, the reference field of each docs element gets populated with referenced doc.