Mongo: 3.2.1.
I have a model defined as such:
var MySchema = new Schema(
{
....
records: {type: Array, "default": []};
I first create an object based on that schema with no record field and it's correctly added to the database. I then update that object as such:
Client
angular.extend(this.object.records, [{test: 'test'}]);
this.Service.update(this.object);
Server (omitting the none-problematic code)
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
return updated.save()
.then(updated => {
console.log(updated);
Model.find({_id: updated._id}).then((data)=> console.log(data));
return updated;
});
};
}
The first console.log prints the object with records field updated. The second prints the object without. What am I missing? How can the resolved promise be different than the persisted object? Shouldn't data and updated be identical?
I think you have a couple problems.
You are using the variable 'updated' twice.
var updated = _.merge(entity, updates); // declared here
return updated.save()
.then(updated => { // trying to re-declare here
The other issue might be that you are trying to merge the 'updates' property with the mongo object and not the actual object values. Try calling .toObject() on your mongo object to get the data.
function saveUpdates(updates) {
return function(entity) {
// call .toObject() to get the data values
var entityObject = entity.toObject();
// now merge updates values with the data values
var updated = _.merge(entityObject, updates);
// use findByIdAndUpdate to update
// I added runValidators in case you have any validation you need
return Model
.findByIdAndUpdate(entity._id, updated, {
runValidators: true
})
.exec()
.then(updatedEntity => {
console.log(updatedEntity);
Model.find({_id: entity._id})
.then(data => console.log(data));
});
}
}
Related
What I want to do:
Send POST request
Save document into mongoDB database
Get it's newly created _id
Use _id as parameter to find and update document
Write id as parameter in string route like /api/page/screen/${id}
Problem:
Document is not updated. Console.log return Screen updated null.
What I have done:
First I make a Promise function which is called on POST request. It is saving new document.
Next I call .then where I do findByIdAndUpdate using id returned from resolved promise.
In result I have newly created document, but update is not done.
// controller.js
const mongoose = require('mongoose');
app.route('/api/page/scan').post(search_a_page)
const search_a_page = async (req, res) => {
const metric = new Metric(payload);
const saveMetric = new Promise((resolve, reject) => {
metric.save((err, document) => {
if (err) res.send(err);
res.json(document);
const id = document._id.toString(); // make string from ObjectId
if (err) reject(new Error({msg: 'It does not work'}));
resolve({msg: 'Metric saved!', id: id});
})
});
saveMetric.then((id) => {
Metric.findByIdAndUpdate(mongoose.ObjectId(id), // string to ObjectId
{"screen_url": `/api/page/screen/${id}`}, (err, result) => {
if (err) {console.log(err);}
else {console.log('Screen updated', result)} // got "Screen updated null"
})
id is taken form mongoDB auto-generated _id
// model.js
const mSchema = new Schema({
_id: Schema.Types.ObjectId,
url: String,
date: Number,
screen: String,
});
const Metric = mongoose.model('Metric', mSchema);
Is it a problem with converting ObjectId to String / String to ObjectId or something else?
UPDATE - solved
According to article we can use .save to update document. That method solved the problem.
When first .save is resolved I used method .then and made update directly accessing document screen property and assigning new value.
saveMetric.then((data) => {
metric.screen = `/api/page/screen/${data.id.toString()}`;
metric.save((err, document) => {
if (err) res.send(err);
console.log(document)
})
});
I'm having a NODE.JS project using mongoose 5.x
My model have toJSON method which removes the _id & __v fields perfectly
mySchema.method("toJSON", function toJSON() {
const {__v, _id, ...object} = this.toObject();
return {
id: _id,
...object
};
});
so when fetching data from the db:
const data = myModel.findOne({_id: id});
I get an object that when serialized to the user:
res.json(data);
It doesn't contain the _id and __v fields as required.
The problem is when I use lean():
const data = myModel.findOne({_id: id}).lean();
the data object contains those fields.
I can remove them manually when using lean
but I would prefer to find a way to sanitize the data object in both cases with the same mechanism.
any suggestions?
Thanks in advance.
Not sure if this is what you want but maybe:
const data = myModel.findOne({_id: id}).lean().then(res => {
delete res._id
return res
})
When JSON.stringify() is called on an object, a check if done if it has a property called toJSON. It's not specific to Mongoose, as it works on plain objects:
const myObj = {
_id: 123,
foo: 'bar',
// `toJSON() {...}` is short for `toJSON: function toJSON() {...}`
toJSON() {
// Because "this" is used, you can't use an arrow function.
delete this._id;
return this;
}
};
console.log(JSON.stringify(myObj));
// {"foo":"bar"}
Mongoose doesn't have an option to automatically inject a toJSON function into objects returned by lean(). But that's something you can add.
First, create a function that:
Takes an object with properties
Listens to when Mongoose runs a find query
Tells Mongoose that after the query, it should change the result
The change: merge the result with the object from step 1.
function mergeWithLeanObjects(props) {
// Return a function that takes your schema.
return function(schema) {
// Before these methods are run on the schema, execute a function.
schema.pre(['find', 'findOne'], function() {
// Check for {lean:true}
if (this._mongooseOptions.lean) {
// Changes the document(s) that will be returned by the query.
this.transform(function(res) {
// [].concat(res) makes sure its an array.
[].concat(res).forEach(obj => Object.assign(obj, props));
return res;
});
}
});
};
}
Now add it to your schema:
mySchema = new mongoose.Schema({
foo: String
});
mySchema.plugin(mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
}));
Then test it:
const full = await myModel.findOne();
const lean = await myModel.findOne().lean();
console.log(full);
// Logs a Mongoose document, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"), foo: 'bar', __v: 1 }
console.log(JSON.stringify(full));
// Logs a JSON string, all properties.
{"_id":"62a8b39466768658e7333154","foo":"bar","__v":1}
console.log(lean);
// Logs an Object, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"),
foo: 'bar', __v: 1, toJSON: [Function: toJSON] }
console.log(JSON.stringify(lean));
// Logs a JSON string, filtered properties.
{"foo":"bar"}
If you want to re-use the plugin with the same settings on multiple schemas, just save the function that mergeWithLeanObjects returns somewhere.
// file: clean.js
module.exports = mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
});
// file: some-schema.js
schema1.plugin(require('./clean.js'));
// file: some-other-schema.js
schema2.plugin(require('./clean.js'));
There's also mongoose.plugin() to add the function to all schemas.
Try this to retrieve the _id from a document
myModel.findOne({_id: id}, function(err, doc) {
if(err)
return 'do something with this err'
console.log(doc._id)
})
I'm trying to get the upsertedId for the updated doucment, but it's returning null.
Here's the function for updating a doucment and trying to return result.upsertedId:
async function run() {
try {
await client.connect()
const collection = client.db("database").collection("collection")
const filter = { username: `${username}`}
const options = { upsert: true };
const updateDoc = {
$set: { date: new Date(), topartists: data.body.items, toptracks: toptracks }
};
await collection.updateOne(filter, updateDoc, options)
.then(result => {
console.log(`${result.matchedCount} document(s) matched the filter, updated ${result.modifiedCount} document(s)`)
console.log(result.upsertedId)
})
console.log('done1')
} finally {
await client.close();
}
}
and here's the console output of the function:
1 document(s) matched the filter, updated 1 document(s)
null
done1
I'm just starting with MongoDB, and the end goal here is to get
_id. It works there is nothing in my database (when it has to make a new document instead of updating one).
That will not be the case.
Your log clearly says - your query matched 1 document and it updated one document.
If your update results in insert, then response of your update operation will have upsert ids. - Means, matched count is 0.
I'm testing mongoose queries, and I found this bizarre behaviour. I have a document with _id: "5de64b376c79643fa847e86b", and if I call the findById method the document is returned just fine. But if I call the find method on the collection, giving as args an array with the same ID value than nothing is returned, while I expect an array of one element being the document. To summarize:
mongooseModel.findById(mongoose.Types.ObjectId("5de64b376c79643fa847e86b")) // Works
mongooseModel.find({ _id: { $in: [mongoose.Types.ObjectId("5de64b376c79643fa847e86b")] } }) //Doesn't work
What's the difference between the two, and why the second one doesn't work?
EDIT: This is kinda the code to access that method.
I define a DataSource in the ApolloServer configuration
const app = new ApolloServer({
...
dataSources: () => ({
source: new SourceAPI(DocumentModel)
})
...
});
where SourceAPI is the DataSource class and DocumentModel is the mongoose model.
SourceAPI is defined like this
class SourceAPI {
async get(ids) {
return await DocumentModel.find({
_id: {
$in: ids
}
});
}
}
Now, inside the GraphQL resolver I finally call the API method to get the documents, like this
const findResolver = () =>
DocumentSchema.get("$findById").wrapResolve(next => async rp => {
let ids = [];
ids.push(mongoose.Types.ObjectId(rp.args._id));
return await rp.context.dataSources.source.get(ids);
});
where DocumentSchema is the GraphQL Schema for the Document Model generated using graphql-compose-mongoose package. The get("$findById") and wrapResolve methods are also from that package. What I do is using these methods to get the GraphQL query parameters and pass them to the API method (in this case I'm just grabbing an ID for test).
If I change the API method to something like this
async get(id) {
return await DocumentModel.findById(id);
}
and the resolver method to this
const findResolver = () =>
DocumentSchema.get("$findById").wrapResolve(next => async rp => {
return await rp.context.dataSources.source.get(mongoose.Types.ObjectId(rp.args._id));
});
everything works
$in is used to find items which holds an array, but _id is just a JSON key/value pair. So you cannot use $in. If your object looks like this then you can use $in
{
id: [ObjectId("5de64b376c79643fa847e86b"),
ObjectId("5de64b376c79643fa847e86b")]
}
Let us assume that we have two collections say "users" and "usersList"
Upon creating a new user document in users collection with following object
{username: Suren, age:31}
The function should read the above data and update other collection i.e. "usersList" with the username alone like below
{username: Suren}
Let me know the possibility
The code I have tried is
exports.userCreated =
functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
return event.data.ref.set(post, {merge: true});
})
I have done it using below code
exports.userCreated = functions.firestore.document('users/{userId}')
.onCreate((event) => {
const firestore = admin.firestore()
return firestore.collection('usersList').doc('yourDocID').update({
name:'username',
}).then(() => {
// Document updated successfully.
console.log("Doc updated successfully");
});
})
If all you want to do is strip the age property from the document, you can do it like this:
exports.userCreated = functions.firestore.document('users/{userId}').onCreate((event) => {
const post = event.data.data();
delete post.age;
return event.data.ref.set(post);
})