How can I upsert using Mongoose based on _id? - mongodb

When I do this:
client_id = req.param("client_id") ? null
client =
name: req.param "clientName"
status: 'active'
Client.update {_id: client_id}, client, {upsert: true}, (err, updRes) ->
if err
res.json
error: "Couldn't create client"
else
res.json client
It will create a new client record, except with a null _id field. I assume that's because the insert part of the upsert looks to the query to create the document. How can I do it so that if no doc is found, then insert a new ObjectId?

Not sure if you have figured this one out yet, but in case you don't want to run into trouble with unique key constraints like Mustafa mentioned above, the way I've done it is by using mongoose's findByIdAndUpdate:
// require mongoose
client_id = req.param("client_id") ? new mongoose.Types.ObjectId
client =
name: req.param "clientName"
status: 'active'
Client.findByIdAndUpdate client_id, client, {upsert: true}, (err, updRes) ->
if err
res.json
error: "Couldn't create client"
else
res.json client
I've never written CoffeeScript, so forgive me if some of that syntax is wrong, but I think it should be fairly obvious what I'm trying to do.
One thing to notice is that you need to make sure client_id is neither an empty string (as that would cause an 'Invalid ObjectID' error) nor null (because mongoose seems infer the new document's id from the one you're querying for). In case it is, I create a new ObjectId, which will cause the query to miss and therefore create a new document with that ID since I pass {upsert: true}.
This seems to have solved the problem for me.

All you have to do with Mongoose is to call save on the client, Mongoose will figure out on its own if it's an update or an insert:
client_id = req.param("client_id") ? null
client_data =
name: req.param "clientName"
status: 'active'
_id: client_id
client = new Client(client_data)
client.save (err) ->
if err
res.json
error: "Couldn't save client"
else
res.json client
Note: Client is a Mongoose model.

Related

TypeError: Cannot read property '_id' of undefined at insertDocuments at insertOne

I was trying to insert a record into a collection when this error was thrown. I went through the mongodb doc on insertOne and I understand that mongod will automatically add the _id when it is not specified as in my case, so I'm wondering why there is an undefined id error.
Here's the code I'm working with, first the api route and the data I'm trying to insert
app.post('/api/contacts', (req, res) => {
// retrieve the user being added in the body of the request
const user = req.body;
// obtain a reference to the contacts collection
const contactsCollection = database.collection('contacts');
// insert data into the collection
contactsCollection.insertOne(user, (err, r) => {
if (err) {
return res.status(500).json({error: 'Error inserting new record.'});
}
const newRecord = r.ops[0];
return res.status(201).json(newRecord);
});
});
The json data for inserting
{
"name": "Wes Harris",
"address": "289 Porter Crossing, Silver Spring, MD 20918",
"phone": "(862) 149-8084",
"photoUrl": "/profiles/wes-harris.jpg"
}
Database connection to the to the database hosted on mlab is successful with no errors. What could possibly be wrong here and how do I go about fixing this error?
The error message means that the object that you are passing into insertOne is undefined (and hence it can’t read the _id property of of it). You might like to look at what exactly req.body contains as that is what is passed as user. (I don’t know TypeScript but, in node/express, I have had errors like this when I didn’t set up bodyParser correctly)
Faced a similar problem. Solved and it by using body-parser and then parsing the json object being sent as follows:
const bodyParser = require('body-parser');
app.use(bodyParser.json());
// parse application/json
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(JSON.stringify(req.body, null, 2))
})

Why isn't ensureIndex() working as I would expect?

Given the code
express = require('express')
bodyParser = require('body-parser')
url = require('url')
app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.set 'port', process.env.PORT || 5000
app.use express.static(__dirname + '/public')
mongodb = require('mongodb')
mongojs = require('mongojs')
dbLocation = process.env.MONGOLAB_URI || 'mongodb://localhost/wesave-companion';
db = mongojs(dbLocation)
transactions = db.collection 'transactions'
transactions.ensureIndex({item_id: 1}, unique: true)
app.post '/transactions', (req, resp) ->
transaction = buildTransaction req.query
createTransaction transaction
resp.status = 200
resp.send 'ok'
createTransaction = (transaction, user) ->
transactions.insert transaction, (err, doc) ->
return err if err
return doc if doc
I would generally expect that based on mongodb documentation, http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/ my db would not be allowing multiple records with an identical 'item_id' key to exist, but that is not the case.
I've tried writing the ensureIndex method as both:
transactions.ensureIndex({item_id: 1}, {unique: true})
and
transactions.ensureIndex({"item_id": 1}, {unique: true})
and
transactions.ensureIndex({'item_id': 1}, {unique: true})
But clearly, using the mongoshell, records entries are definitely duplicating. What am I missing here?
Interestingly, if I attempt to write the index directly to the db, I get the below response, which I'm not sure how to interpret.
What direction should I be digging? Thx.
You don't have any duplicates. You're calling db.transactions.find(), which returns the entire contents of your collection.
It returns one document, which means your collection contains exactly one document.
You're calling it three times, which, you will note, returns the same thing every time.

Mongoose No matching document found using id() method. Error caused by asynchronous delete requests

Making asynchronous requests in a loop to delete documents from an embedded collection:
_.each deletedItem, (item) ->
item.$delete()
Erratically throws this error:
{ message: 'No matching document found.', name: 'VersionError' }
When executing:
var resume = account.resumes.id(id);
resume.remove();
account.save(function (err, acct) {
console.log(err);
if(err) return next(err);
res.send(resume);
});
After logging account.resumes and looking through the _id's of all of the resumes, the document I am attempting to find by id, exists in the collection.
530e57a7503d421eb8daca65
FIND:
{ title: 'gggff', _id: 530e57a7503d421eb8daca65 }
IN:
[{ title: 'asddas', _id: 530e57a7503d421eb8daca61 }
{ title: 'gggff', _id: 530e57a7503d421eb8daca65 }
{ title: 'ewrs', _id: 530e57a7503d421eb8daca64 }]
I assume this has to do with the fact that I am performing these requests asynchronously, or that there is a versioning issue, but I have no idea how to resolve it.
It doesn't make any sense to me how when I log the resumes, I can see the resume I attempt to find, yet if I log:
log(account.resumes.id(id));
I get undefined.
UPDATE
I've discovered that my issue is with versioning.
http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html
But I am still unsure how to resolve it without disabling versioning, which I don't want to do.
In mongodb version 3, documents now have an increment() method which manually forces incrementation of the document version. This is also used internally whenever an operation on an array potentially alters array element position. These operations are:
$pull $pullAll $pop $set of an entire array
changing the version key
The version key is customizable by passing the versionKey option to the Schema constructor:
new Schema({ .. }, { versionKey: 'myVersionKey' });
Or by setting the option directly:
schema.set('versionKey', 'myVersionKey');
disabling
If you don’t want to use versioning in your schema you can disable it by passing false for the versionKey option.
schema.set('versionKey', false);
MongooseJs API docs explicitly warn on disabling versioning, and recommend against it. Your issue is due to workflow. If you're updating your collection from the UI, sending the API request and not refreshing your object with the object from the backend -- then attempt to update it again, you'll encounter the error you are reporting. I suggest either consuming/updating the object scope from the API response, then __v is correctly incremented. Or don't send the __v field on the PUT API request, this way it won't conflict with version on the collection in the database.
Another option is -- when requesting the object from the backend, have the API response not send the __v field, this way you don't have to code logic to NOT send it from the frontend. On all your gets for that collection, do either one of the following (depends how you write your queries):
var model = require('model');
var qry = model.find();
qry.select('-__v');
qry.exec(function(err,results){
if(err) res.status(500).send(err);
if(results) res.status(200).json(results);
});
OR
var model = require('model');
model.find({}, '-__v', function(err,results){
if(err) res.status(500).send(err);
if(results) res.status(200).json(results);
});

Mongoosejs can't query with findById

I have a meteor application saving stuff to mongodb and I have an api I wish to make and expose via REST.
express = require 'express'
mongoose = require 'mongoose'
app = express()
mongoose.connect process.env.MONGO_URL
Account = mongoose.model 'users',
profile:
available: Boolean
app.get "/accounts/meta/:account_id", (req, res) ->
account = Account.findById req.params.account_id
, (error, account) ->
if account?
res.jsonp
account: account
else
res.jsonp 404,
error: "Account not found"
app.listen 2000
The problem is that I can't query by the id's I see in my database. For example I have this user:
{
"_id": "zcdsHuKr5dTh3xHz5",
"createdAt": 1373188729653,
"last_seen": 1373465529548,
"profile": {
....
If I go to /accounts/meta/zcdsHuKr5dTh3xHz5 it says 'Cast to ObjectId failed for value "zcdsHuKr5dTh3xHz5" at path "_id"'. I tried in every possible way to query for my document without luck. Any ideas?
The problem is that you need to define a schema for your model to tell Mongoose that your _id field is a String instead of the standard ObjectId in this collection:
AccountSchema = new mongoose.Schema
_id: String
profile:
available: Boolean
Account = mongoose.model 'users', AccountSchema
Seems that your _id field isn't an ObjectId. The method findById on Mongoose expects:
id objectid, or a value that can be casted to one
So, if your _id in fact isn't an ObjectId, you should query using findOne method
account = Account.findOne { "_id" : req.params.account_id }
The currently propagated answer is not a good idea. Don't change the schema for your model to tell Mongoose that your _id field is a String. That is a bad idea in terms of validation and should be considered a hack.
Try this instead:
If you want to query for an _id in mongoose, you have to cast the _id to ObjectId
AccountSchema = new mongoose.Schema({
_id: ObjectID(),
profile: {
available: Boolean
});
Account = mongoose.model('users', AccountSchema);
Mongoosejs requires you to cast the id in the query as well (I didn't find anything about this in the mongoose documentation either):
account = Account.findOne({ "_id" : mongoose.Types.ObjectId(req.params.account_id) });

How to put embedded document from one document into another document using Mongoose?

What I'm trying to do should be straight forward but for some reason I'm having real difficulties figuring this out. I have the following Mongoose schemas (simplified).
var Status = new Schema({
name : { type: String, required: true },
description : { type: String }
});
var Category = new Schema({
statuses : [Status], // contains a list of all available statuses
// some other attributes
});
var Book = new Schema({
statuses : [Status], // preferably this would not be an array but a single document, but Mongoose doesn't seem to support that
// some other attributes
});
Now, I want to do the following:
Retrieve the Category document
Find a particular embedded Status document (based on request param)
Assign that particular embedded Status document to a particular Book document. I want to replace the existing Book status as at any given time there should only be one status set for a book.
Here is what I'm currently doing:
mongoose.model('Category').findOne({_id: id}, function(err, category){
if(err) next(err);
var status = category.statuses.id(statusId); // statusId available via closure
book.statuses[0] = status; // book available via closure; trying to replace the existing status here.
book.save(function(err){
if(err) next(err);
next();
});
});
The above seems to run fine and I don't get any errors. However, the new status is not saved to the document. Next time I output the updated Book document, it will still have the old status. I debugged this and the find() methods as well as setting the status seems to be fine.
The only thing I can think of right now is that somehow the status value I'm assigning is not in the right format to be saved with Mongoose. Although, I would expect some kind of error message then.
Or maybe there is a better way to do all of this anyway?
It could be because you are trying to copy an embedded document, which itself could have an ObjectId associated with it. Trying to save the duplicate Status within the Book would create two embedded documents with the same ObjectId. Try creating a new Status object and copying the fields.
It is hard to find docs on ObjectsIds for embedded documents, but they are mentioned here: http://mongoosejs.com/docs/embedded-documents.html.