adding a new property to existing doesn't work - javascript

I'm trying to add another property to existing object returning from findOne() promise in mongoose.
In the response I get the object without the property convertName
app.get('/getItem', (req, res) => {
var itemID = req.query.itemID;
Item.findOne({_id: itemID}).then(item => {
item.convertName = 'cm';
res.send(item);
}).catch( err => {
res.status(401).send();
});
})
I know that the way to add another property to an existing object is similar to this, just specify the property name and set a value to it, so I don't know why it is not working in this case.
Hope you can explain and help me why its not working.

It's a bit complicated with Mongoose: by default MongooseDocument is returned by query, and the property that you try to add to such a document is not reflected on its serialized value (the one that is sent in the response).
One possible way around this is using lean() method to enable lean option. Quoting the doc:
Documents returned from queries with the lean option enabled are plain
javascript objects, not MongooseDocuments. They have no save method,
getters/setters or other Mongoose magic applied.

Related

Difficulty updating an existing subdocument's array element with Mongoose using 'set'?

I am not able to save & update a Mongoose subdocument's array element using the set function.
Brief
I use a front end HTML form that creates a request eventually calling this controller in my Node Express REST API. The req.body parameter being passed into the controller is an object matching the structure of an element of the purchaseOrders array. I successfully GET the element, modify it in a pretty form on the front end, then POST to an app level controller which then creates a PUT request into my API controller (API controller function below).
Problem
I can't seem to use the set method on an element of the array I'm targetting. Secondly, if I use a direct assignment instead, I get a save error.
When I use
customer[0].purchaseOrders[0] = req.body
I receive a type error
TypeError: purchaseOrderRaw.save is not a function
and if I use the set method
customer[0].purchaseOrders[0].set(req.body)
I receive a different type error
TypeError: purchaseOrderRaw[0].purchaseOrders[0].set is not a function
Please note, my goal is not to assign each field individually. Ideally, I would like to use the set method so that I can pass the req.body directly into the array. What am I doing wrong?
Here is the Document returned by Mongoose using the aggregate framework. I'm basically trying to update the first element in the purchaseOrders array.
{
"customerID":1,
"purchaseOrders":[
{
"_id":"5eee249ac534edf5d09ed036",
"purchaseOrderID":450012321
}
]
}
The controller in my REST API. I've removed a lot of error handling for brevity.
const Customer = mongoose.model('Customer');
const purchaseOrderUpdate = (req, res) => {
Customer
.aggregate()
.match({customerID:req.params.customerID})
.project({_id:0, customerID:1, purchaseOrders: {
$filter: {
input: '$purchaseOrders',
as: 'purchaseOrders',
cond: { $eq: ['$$purchaseOrders.purchaseOrderID', req.params.purchaseOrderID]}
}}})
.exec((err, customer) => {
customer[0].purchaseOrders[0].set(req.body);
customer.save(); // excluded the callback for brevity
});
}

How to customize response object in Bookshelf JS?

I want to add a custom property to the standard Bookshelf.js response object. In this case, the goal is to fetchAll() Regions and append the property link to each Region. The value of link is simply /some/api/route/${region.id}.
I believe there are two ways to achieve this: (1) modify to the Regions model to include some function getRegionLink() to generate the desired value; or (2) iterate over and modify the response object. I think that (1) is preferred, but I can imagine when (2) may be necessary.
With that context, I haven't been able to figure out how to do (2). The Bookshelf.js docs state that _.each() is available, though the lodash docs deprecated each in favor of forEach. However, the following did not work as expected. Some error occurs. On a related note, the err objects log empty on Postman.
Region Service
Region.forge()
.fetchAll()
.then(regions => {
_.forEach(regions, region => {
region.link = `${API_BASE}/location/regions/v1/${region.id}`;
});
res.json(regions);
.catch(err => {
res.status(404).json(err);
});
The core of this question is answered by the "virtuals" plugin for Bookshelf. Related post: Bookshelf.js set attribute not in database

Stripped Attributes with toJSON/toObject

I have something like the following code:
User.findOne(id)
.exec((err, user) => {
Pets.find(_.pluck(user.pets, 'id'))
.populate("toys")
.exec((err, petsWithToys) => {
user.pets = petsWithToys;
return res.ok({ user: user });
});
});
When I look at the response in the client I don't see the toys array inside the pet.
I thought maybe this was due to overriding the toJSON function in my User model but even when removing it I get the same behavior.
Also, I've found out that if I assign the values to a new property that is not defined in the model, I do see the values at the client. I.e. if I do
user.petsNew = petsWithToys;
I will see the fully populated property.
I've seen the documentation of toObject where is says it removes instance methods (here) but I am not sure why the collection is considered a method and don't understand how after changing the value it is still removed.
Any comments/explanations/workarounds?
P.S. Tried to step through the code but can't step into toObject...
Add user = user.toJSON(); before user.pets = petsWithToys;
Check https://stackoverflow.com/a/43500017/1435132

get _id with mongoose

I was trying to console.log(record._id) all of records on my mongodb collection using Mongoose. I kept getting undefined for each of the _id values.
I struggled until I bumped into this post. Then I used console.dir to find the location of the _id and used that in my console.log:
MySchemaModel.find({}).then(function(records) {
records.forEach(function(record) {
console.log(record._doc._id); // <-- I added ._doc
});
});
But, this looks down-right hacky. Is there a better way to do this?
NOTE: This isn't just something that affects console.log. I'm just keeping the question narrow.
If you want to customize/edit record then you should use .lean() function.The .lean() function will turn it into a normal JavaScript object. If you don't use .lean() function then each record is still a mongoose document and _id behaves differently in that context. So can use like
MySchemaModel.find({}).lean().exec(function(error, records) {
records.forEach(function(record) {
console.log(record._id);
});
});
N.B: when use .exec() then first parameter used for error and second one for success data.
Mongoose assigns each of your schemas an id virtual getter by default
which returns the documents _id field cast to a string, or in the case
of ObjectIds, its hexString. If you don't want an id getter added to
your schema, you may disable it passing this option at schema
construction time.
Source: Mongoose Docs
var schema = new Schema({ name: String }, { id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
I guess the issue is with .then promise, I have never seen that.
MySchemaModel.find({}).then
So just try simple .exec call with callback.
MySchemaModel.find({}).exec(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
The problem is that each record is still a mongoose document and _id behaves differently in that context. The .lean() function will turn it into a normal JavaScript object.
MySchemaModel.find({}).lean().then(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
you can also use the .map() method :
MySchemaModel.find({}).exec(function(records) {
console.log(records.map(record => record._id);
});
if you are using a model you don't get the full object but an instance of _doc as record
so you should directly
console.log(record._id)
or
console.log(record._id.valueOf())
but when you return record as response you get the full object so it's better to use .find().lean()

Cast plain object to mongoose document

UPDATE 1: 5 votes have been received, so I have submitted a feature request: https://github.com/LearnBoost/mongoose/issues/2637
Please cast your +1 votes there to let the core team know you want this feature.
UPDATE 2: See answer below...
ORIGINAL POST:
Lets say I do a "lean" query on a collection OR receive some data from a REST service and I get an array of objects (not mongoose documents).
These objects already exist in the database, but I need to convert some/all of those objects to mongoose documents for individual editing/saving.
I have read through the source and there is a lot going on once mongoose has data from the database (populating, casting, initializing, etc), but there doesn't seem to be a method for 'exposing' this to the outside world.
I am using the following, but it just seems hacky ($data is a plain object):
// What other properties am I not setting? Is this enough?
var doc = new MyModel( $data );
doc.isNew = false;
// mimicking mongoose internals
// "init" is called internally after a document is loaded from the database
// This method is not documented, but seems like the most "proper" way to do this.
var doc = new MyModel( undefined );
doc.init( $data );
UPDATE: After more searching I don't think there is a way to do this yet, and the first method above is your best bet (mongoose v3.8.8). If anybody else is interested in this, I will make a feature request for something like this (leave a comment or upvote please):
var doc = MyModel.hydrate( $data );
Posting my own answer so this doesn't stay open:
Version 4 models (stable released on 2015-03-25) now exposes a hydrate() method. None of the fields will be marked as dirty initially, meaning a call to save() will do nothing until a field is mutated.
https://github.com/LearnBoost/mongoose/blob/41ea6010c4a84716aec7a5798c7c35ef21aa294f/lib/model.js#L1639-1657
It is very important to note that this is intended to be used to convert a plain JS object loaded from the database into a mongoose document. If you are receiving a document from a REST service or something like that, you should use findById() and update().
For those who live dangerously:
If you really want to update an existing document without touching the database, I suppose you could call hydrate(), mark fields as dirty, and then call save(). This is not too different than the method of setting doc.isNew = false; as I suggested in my original question. However, Valeri (from the mongoose team) suggested not doing this. It could cause validation errors and other edge case issues and generally isn't good practice. findById is really fast and will not be your bottleneck.
If you are getting a response from REST service and say you have a User mongoose model
var User = mongoose.model('User');
var fields = res.body; //Response JSON
var newUser = new User(fields);
newUser.save(function(err,resource){
console.log(resource);
});
In other case say you have an array of user JSON objects from User.find() that you want to query or populate
var query = User.find({});
query.exec(function(users){
//mongoose deep-populate ref docs
User.deeppopulate users 'email_id phone_number'.exec({
//query through populated users objects
});
});
MongoDB doesn't support Joins and Transfers. So for now you can't cast values to an object directly. Although you can work around it with forEach.

Categories