Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Document not updated in findOneAndUpdate

I have a post route that receives data from a PUT request in an express app that aims to update a mongoose document based on submitted form input. The "Base" model is Profile, and I have two discriminator models Helper and Finder that conditionally add fields to the Profile schema (see below for details).

Thus, req.body.profile will contain different fields depending on the discriminator it's associated with, but will always contain the fields (username, email city, accountType) present in the "base" model, Profile.

Before I send my PUT request, an example of a document in Profile looks like this:

{ jobTitle: '',
  lastPosition: '',
  email: '',
  city: '',
  accountType: 'helper',
  _id: 5c77883d8db04c921db5f635,
  username: 'here2help',
  __v: 0 }

This looks good to me, and suggests that the model is being created as I want (with base fields from Profile, and those associated with the Helper model - see below for models).

My POST route then looks like this:

router.put("/profile/:id", middleware.checkProfileOwnership, function(req, res){

    console.log(req.body.profile);

    Profile.findOneAndUpdate(req.params.id, req.body.profile, function(err, updatedProfile){

        if(err){
            console.log(err.message);
            res.redirect("/profile");
        } else {
            console.log(updatedProfile);
            res.redirect("/profile/" + req.params.id);
        }

    });
});

The information I receive from the form (console.log(req.body.profile)) is what I expect to see:

{ accountType: 'helper',
  username: 'here2help',
  email: '[email protected]',
  city: 'New York',
  jobTitle: 'CEO',
  lastPosition: 'sales rep'}

However, after updating the document with req.body.profile in Profile.findOneAndUpdate(), I do not see my returned document updated:

console.log(updatedProfile)

{ jobTitle: '',
  lastPosition: '',
  email: '[email protected]',
  city: 'New York',
  accountType: 'helper',
  _id: 5c77883d8db04c921db5f635,
  username: 'here2help',
  __v: 0 }

So, the fields that are defined in my 'Base' model (ie those defined in ProfileSchema - see below) are being updated (e.g. city), but those that are in my discriminators are not - see below.

The updated information is clearly present in req, but is not propagated to the Profile model - How can this be?

I've also tried using findByIdAndUpdate but I get the same result.

Here are the Schemas I'm defining:

Profile - my "base" schema:

var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");

var profileSchema = new mongoose.Schema({ 
    username: String,
    complete: { type: Boolean, default: false },
    email: { type: String, default: "" },
    city: { type: String, default: "" }
}, { discriminatorKey: 'accountType' });

profileSchema.plugin(passportLocalMongoose);

module.exports = mongoose.model("Profile", profileSchema);

Finder

var Profile = require('./profile');

var Finder = Profile.discriminator('finder', new mongoose.Schema({
    position: { type: String, default: "" },
    skills: Array
}));

module.exports = mongoose.model("Finder");

Helper

var Profile = require('./profile');

var Helper = Profile.discriminator('helper', new mongoose.Schema({
    jobTitle: { type: String, default: "" },
    lastPosition: { type: String, default: "" }
}));

module.exports = mongoose.model("Helper");

This is my first attempt at using discriminators in mongoose, so it's more than possible that I am setting them up incorrectly, and that this is the root of the problem.

Please let me know if this is unclear, or I need to add more information.

like image 625
fugu Avatar asked Feb 26 '19 19:02

fugu


People also ask

How do I use Find one and update?

As the name implies, findOneAndUpdate() finds the first document that matches a given filter , applies an update , and returns the document. By default, findOneAndUpdate() returns the document as it was before update was applied. You should set the new option to true to return the document after update was applied.

How do you update a mongoose?

Updating Using Queries The save() function is generally the right way to update a document with Mongoose. With save() , you get full validation and middleware.

Is findOneAndUpdate Atomic?

According to these docs , single write transactions are atomic. So with findOneAndUpdate, this would indeed be atomic.

What is the difference between findOneAndUpdate and updateOne?

findOneAndUpdate returns a document whereas updateOne does not (it just returns the _id if it has created a new document).


2 Answers

It matters what schema you use to query database

Discriminators build the mongo queries based on the object you use. For instance, If you enable debugging on mongo using mongoose.set('debug', true) and run Profile.findOneAndUpdate() you should see something like:

Mongoose: profiles.findAndModify({
  _id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
  '$set': {
    email: '[email protected]',
    city: 'New York',
    accountType: 'helper',
    username: 'User NAme', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })

Notice it uses only the fields defined in Profile schema.

If you use Helper, you would get something like:

profiles.findAndModify({
  accountType: 'helper',
  _id: ObjectId("5c78519e61f4b69da677a87a")
}, [], {
  '$set': {
    jobTitle: 'CTO',
    email: '[email protected]',
    city: 'New York',
    accountType: 'helper ', 
    username: 'User Name', __v: 0 } }, { new: true, upsert: false, remove: false, projection: {} })

Notice it adds the discriminator field in the filter criteria, this is documented:

Discriminator models are special; they attach the discriminator key to queries. In other words, find(), count(), aggregate(), etc. are smart enough to account for discriminators.

So what you need to do when updating is to use the discriminator field in order to know which Schema to use when calling update statement:

app.put("/profile/:id", function(req, res){
console.log(req.body);

if(ObjectId.isValid(req.params.id)) {

  switch(req.body.accountType) {
    case 'helper':
    schema = Helper;
    break;
    case 'finder':
    schema = Finder;
    break;
    default:
    schema = Profile;
  }      

  schema.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
      if(err){
          console.log(err);
          res.json(err);
      } else {
          console.log(updatedProfile);
          res.json(updatedProfile);
      }

  });
} else {
  res.json({ error: "Invalid ObjectId"});
} });

Notice, above is not necessary when creating a new document, in that scenario mongoose is able to determine which discriminator to use.

You cannot update discriminator field

Above behavior has a side effect, you cannot update the discriminator field because it will not find the record. In this scenario, you would need to access the collection directly and update the document, as well as define what would happen with the fields that belong to the other discriminator.

db.profile.findOneAndUpdate({ _id: req.params.id }, { $set : req.body }, { new: true, upsert: false, remove: {}, fields: {} }, function(err, updatedProfile){
              if(err) {
                res.json(err);
              } else {
                console.log(updatedProfile);
                res.json(updatedProfile);      
              }
          });
like image 178
Cristian Colorado Avatar answered Oct 20 '22 23:10

Cristian Colorado


Please add option in findOneAndUpdate - { new: true };

like image 1
karthik mano Avatar answered Oct 20 '22 23:10

karthik mano