BIOSphere
BIOSphere

Reputation: 33

How to update an object for a document in MongoDB using React and axios?

I am able to update: deviceName, macAddress, and blacklisted, but any values in the connection object will not update (ie: active, connectionURL & configuration). The purpose of the if statements are to not modify the values in the database if the user left them blank.

Schema:

const deviceSchema = new Schema({
  deviceName: { type: String, required: true, trim: true },
  macAddress: { type: String, required: true, unique: true, trim: true },
  blacklisted: {type: Boolean, required: true},
  connection: { type: Object, required: true,
      active: { type: Boolean, required: true, trim: true },
      connectionUrl: { type: String, required: true, trim: true},
      configuration: { type: String, required: true, trim: true},
  },
}, {
  timestamps: true,
});

Updating Database:

router.route('/update/config/:id').post((req, res) => {

  Device.findById(req.params.id)
    .then(device => {
      device.macAddress = req.body.macAddress;
      device.connection.active = req.body.active;
      if (req.body.connectionUrl != '' && req.body.connectionUrl != "Choose...") {
        device.connection.connectionUrl = req.body.connectionUrl;
      }
      if (req.body.configuration != '') {
        device.connection.configuration = req.body.configuration;
      }
      device.save()
        .then(() => res.json('device configuration(s) updated!'))
        .catch(err => res.status(400).json('Error: ' + err));
    })
    .catch(err => res.status(400).json('Error: ' + err));
});

Upvotes: 3

Views: 455

Answers (1)

Cody Haines
Cody Haines

Reputation: 1235

In Mongoose, if you're assigning a Schema the type of 'Object', it will become a Mixed schema, which means a couple things, but the most important of which in your case is that mongoose cannot automatically detect changes to that part of the document.

What you're likely looking for here is either a 'Nested Path' or a 'SubDocument'. See here for more information on the difference between the two.

The gist is, you need to change how you're defining the schema. There's a few options here:

1. You know exactly what members the connection object has, and always want to be able to assign to them, even if you've never explicitely created the connection.

In this case, your best bet is a 'nested path', which would be defined like so:

const deviceSchema = new Schema({
  deviceName: { type: String, required: true, trim: true },
  macAddress: { type: String, required: true, unique: true, trim: true },
  blacklisted: {type: Boolean, required: true},
  connection: { 
      active: { type: Boolean, required: true, trim: true },
      connectionUrl: { type: String, required: true, trim: true},
      configuration: { type: String, required: true, trim: true},
  },
}, {
  timestamps: true,
});

2. You know exactly what members the connection object has, but only want to be able to assign to specific members if the entire connection has already been created.

In this case, you're going to want to use a SubDocument

const connectionSchema = new Schema({
    active: { type: Boolean, required: true, trim: true },
    connectionUrl: { type: String, required: true, trim: true },
    configuration: { type: String, required: true, trim: true },
}, { timestamps: false });

const deviceSchema = new Schema({
  deviceName: { type: String, required: true, trim: true },
  macAddress: { type: String, required: true, unique: true, trim: true },
  blacklisted: {type: Boolean, required: true},
  connection: { type: connectionSchema, required: true },
}, {
  timestamps: true,
});

EDIT: In reference to a question posted in a comment below: If you want to be able to reference the same connectionSchema from multiple 'deviceSchema's, you would need to change to something along the lines of this:

const deviceSchema = new Schema({
  deviceName: { type: String, required: true, trim: true },
  macAddress: { type: String, required: true, unique: true, trim: true },
  blacklisted: {type: Boolean, required: true},
  connection: { type: Schema.Types.ObjectId, required: true, ref: "Connection" },
}, {
  timestamps: true,
});

In this situation, you would also need to be certain that you're explicitly creating both models (a Device model, and a Connection model), and to ensure the connection model is loaded with the device model, you would then run: DeviceModel.findOne(/*whatever*/).populate('connection') rather than your normal 'find' method. This is getting a bit far afoot from the original question though.

3. You don't know the exact structure of the connection object

Here, you can use the built in 'Mixed' type as you were already, but it comes with the caveat that you can't define any further children, and mongoose won't automatically detect changes to that portion of the document, meaning device.save() won't update anything in your connection object automatically.

const deviceSchema = new Schema({
  deviceName: { type: String, required: true, trim: true },
  macAddress: { type: String, required: true, unique: true, trim: true },
  blacklisted: {type: Boolean, required: true},
  connection: { type: Object, required: true },
}, {
  timestamps: true,
});

In order to tell mongoose that the connection field has been updated, we would have to call device.markModified('connection') before device.save()

Upvotes: 1

Related Questions