Parent.save() not working when sub document / deeply nested document is modified

好久不见. 提交于 2020-01-16 08:41:39

问题


I find my document from whole collection like this:

    const account = await Account.findOne({ "buildings.gateways.devices.verificationCode": code })
    const buildings = account.buildings
    const gateways = buildings[0].gateways;
    const devices = gateways[0].devices;
    const device = _.filter(devices, d => d.verificationCode === code);

now I want to change one of the property "patientLastName" and then save the whole document. I am doing as below.

device.patientLastName = lastName;
const updated = await account.save();

This simply does not change anything. I have tried many solutions given but none of them working. not sure if I can save parent document just like that?

I have few other calls where same code works but only change for this is that this is in my post call while working ones are in put call.

My Schema:

const accountSchema = new mongoose.Schema({
  email: { type: String, unique: true, required: true },
  password: { type: String, required: true },
  userName: { type: String, unique: true, required: true },
  companyName: { type: String, required: true },
  apiKey: { type: String, unique: true, required: true },
  apiCallCount: { type: Number, default: 0 },
  solutionType: String,
  parentCompany: String,
  buildings:
    [
      new mongoose.Schema({
        buildingName: String,
        address: String,
        suite: String,
        floor: String,
        timeZone: String,
        gateways:
        [
          new mongoose.Schema({
            gatewayName: String,
            gatewayKey: { type: String, sparse: true },
            suite: String,
            devices: [
              new mongoose.Schema({
                serialNumber: { type: String, sparse: true },
                area: String,
                connectionStatus: Number,
                gatewayKey: String,
                applicationNumber: Number,
                firmwareVersion: String,
                needsAttention: Boolean,
                verificationCode: String,
                patientRiskStatus: String,
                patientFirstName: String,
                patientLastName: String
              }, { timestamps: true })
            ]
          }, { timestamps: true })
        ]
      }, { timestamps: true })
    ]
}, { timestamps: true });

Update: I am trying this: it gives me error message - "message": "Converting circular structure to JSON"

  const updated = account.update(
    {
        "_id" : ObjectId(accountId),
        "buildings.gateways.devices.verificationCode": code
    }, 
    {
        "$set": { 
            "buildings.$.gateways.0.devices.0.patientFirstName": "name1", 
            "buildings.$.gateways.0.devices.0.patientLastName": "name2", 
        }
    }
)

Your help is appreciated. Thanks

UPDATED -

complete call for your reference.

    // Register User
    loginRouter.post('/register', async (req, res, next) => {
        try {

            var { email, userName, password, firstName, lastName, role, deviceIds, code } = req.body;
            console.log(req.body)
            // checking if email or username already exist before registering. 
            const verifyEmail = await User.find({
                $or: [
                    { 'email': email },
                    { 'userName': userName },
                ]
            })
            if (verifyEmail.length > 0) {
                throw new BadRequestError('DuplicateEmailOrUserName', {
                    message: 'Email or Username already exists'
                });
            }
            // finding accountId for verification code first
            const account = await Account.findOne({ "buildings.gateways.devices.verificationCode": code })
            //console.log(account)
            if (account.length === 0) {
                console.log("Invalid registration code")
                throw new BadRequestError('InvalidVerificationCode', {
                    message: 'Invalid registration code'
                });
            }
            var accountId = account ? account._id : null

            const buildings = account.buildings
            const gateways = buildings[0].gateways;
            const devices = gateways[0].devices;

            //console.log("devices", devices)

            // finding deviceId to insert for user from that account
            const device = _.filter(devices, d => d.verificationCode === code);

           // console.log("device", device)
            if (!deviceIds) {
                deviceIds = device.map(item => item._id)
            //    console.log("deviceIds", deviceIds)
            }
            const hashedPassword = await hasher.hashPassword(password);
            const newUser = new User({
                accountId: accountId ? accountId : undefined,
                userName: userName,
                password: hashedPassword,
                email: email,
                firstName: firstName,
                lastName: lastName,
                role: role,
                refreshToken: uuidv4(),
                refreshTokenExpiryDate: moment().add(process.env.REFRESH_TOKEN_EXPIRY_IN_DAYS, 'days'),
                deviceIds: deviceIds ? deviceIds : [],
                isActive: true,
            });
            const newlySavedUser = await newUser.save();

            const {
                refreshToken,
                refreshTokenExpiryDate,
                password: pwd,
                ...userWithoutSensitiveInfo
            } = newlySavedUser.toObject();


**// solutions by @SuleymanSah** <----


try {
        let result = await Account.findByIdAndUpdate(
         accountId,
          {
            $set: {
              "buildings.$[building].gateways.$[gateway].devices.$[device].patientFirstName": "userName"
            }
          },
          {
            arrayFilters: [

              { "building._id": ObjectId("5d254bb179584ebcbb68b712") },
              { "gateway._id": ObjectId("5d254b64ba574040d9632ada") },
              { "device.verificationCode": "4144" }
            ],
            new: true
          }
        );

        if (!result) return res.status(404);
    console.log(result)
        //res.send(result);
      } catch (err) {
        console.log(err);
        res.status(500).send("Something went wrong");
      }

        res.json(newlySavedUser);
        next();
    } catch (err) {
        next(err);
    }
});

Let me know if you need more information. Thanks


回答1:


You can use the filtered positional operator $ for this.

Note that we also need to have the buildingId and gatewayId to make it work dynamically.

router.put("/account/:accountId/:buildingId/:gatewayId", async (req, res) => {
  const { patientFirstName, verificationCode } = req.body;
  try {
    let result = await Account.findByIdAndUpdate(
      req.params.accountId,
      {
        $set: {
          "buildings.$[building].gateways.$[gateway].devices.$[device].patientFirstName": patientFirstName
        }
      },
      {
        arrayFilters: [
          { "building._id": req.params.buildingId },
          { "gateway._id": req.params.gatewayId },
          { "device.verificationCode": verificationCode }
        ],
        new: true
      }
    );

    if (!result) return res.status(404);

    res.send(result);
  } catch (err) {
    console.log(err);
    res.status(500).send("Something went wrong");
  }
});

TEST

Let's have this document:

{
    "_id" : ObjectId("5e0da052b4b3fe5188602e11"),
    "apiCallCount" : 1,
    "email" : "abc@def.net",
    "password" : "123123",
    "userName" : "username",
    "companyName" : "companyName",
    "apiKey" : "apiKey",
    "solutionType" : "solutionType",
    "parentCompany" : "parentCompany",
    "buildings" : [
        {
            "_id" : ObjectId("5e0da052b4b3fe5188602e12"),
            "buildingName" : "buildingName 1",
            "address" : "address",
            "suite" : "suite",
            "floor" : "floor",
            "timeZone" : "String",
            "gateways" : [
                {
                    "_id" : ObjectId("5e0da052b4b3fe5188602e13"),
                    "gatewayName" : "gatewayName 1",
                    "gatewayKey" : "gatewayKey",
                    "suite" : "suite",
                    "devices" : [
                        {
                            "_id" : ObjectId("5e0da052b4b3fe5188602e15"),
                            "serialNumber" : "serialNumber 1",
                            "area" : "area",
                            "connectionStatus" : 0,
                            "gatewayKey" : "gatewayKey",
                            "applicationNumber" : 11,
                            "firmwareVersion" : "firmwareVersion",
                            "needsAttention" : true,
                            "verificationCode" : "123456",
                            "patientRiskStatus" : "patientRiskStatus",
                            "patientFirstName" : "patientFirstName",
                            "patientLastName" : "patientLastName",
                            "createdAt" : ISODate("2020-01-02T10:48:34.287+03:00"),
                            "updatedAt" : ISODate("2020-01-02T10:48:34.287+03:00")
                        },
                        {
                            "_id" : ObjectId("5e0da052b4b3fe5188602e14"),
                            "serialNumber" : "serialNumber 2",
                            "area" : "area",
                            "connectionStatus" : 0,
                            "gatewayKey" : "gatewayKey",
                            "applicationNumber" : 22,
                            "firmwareVersion" : "firmwareVersion",
                            "needsAttention" : true,
                            "verificationCode" : "987654",
                            "patientRiskStatus" : "patientRiskStatus",
                            "patientFirstName" : "patientFirstName",
                            "patientLastName" : "patientLastName",
                            "createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
                            "updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
                        }
                    ],
                    "createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
                    "updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
                }
            ],
            "createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
            "updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
        }
    ],
    "createdAt" : ISODate("2020-01-02T10:48:34.289+03:00"),
    "updatedAt" : ISODate("2020-01-02T10:48:34.289+03:00"),
    "__v" : 0
}

To update the device patientFirstName with verificationCode 123456, we need to send a PUT request to the url http://..../account/5e0da052b4b3fe5188602e11/5e0da052b4b3fe5188602e12/5e0da052b4b3fe5188602e13

5e0da052b4b3fe5188602e11 is accountId.

5e0da052b4b3fe5188602e12 is buildingId.

5e0da052b4b3fe5188602e13 is gatewayId.

Request body:

{
    "verificationCode": "123456",
    "patientFirstName": "UPDATED!!!"
}

Result will be like this:

{
    "apiCallCount": 1,
    "_id": "5e0da052b4b3fe5188602e11",
    "email": "abc@def.net",
    "password": "123123",
    "userName": "username",
    "companyName": "companyName",
    "apiKey": "apiKey",
    "solutionType": "solutionType",
    "parentCompany": "parentCompany",
    "buildings": [
        {
            "gateways": [
                {
                    "devices": [
                        {
                            "_id": "5e0da052b4b3fe5188602e15",
                            "serialNumber": "serialNumber 1",
                            "area": "area",
                            "connectionStatus": 0,
                            "gatewayKey": "gatewayKey",
                            "applicationNumber": 11,
                            "firmwareVersion": "firmwareVersion",
                            "needsAttention": true,
                            "verificationCode": "123456",
                            "patientRiskStatus": "patientRiskStatus",
                            "patientFirstName": "UPDATED!!!",
                            "patientLastName": "patientLastName",
                            "createdAt": "2020-01-02T07:48:34.287Z",
                            "updatedAt": "2020-01-02T07:48:34.287Z"
                        },
                        {
                            "_id": "5e0da052b4b3fe5188602e14",
                            "serialNumber": "serialNumber 2",
                            "area": "area",
                            "connectionStatus": 0,
                            "gatewayKey": "gatewayKey",
                            "applicationNumber": 22,
                            "firmwareVersion": "firmwareVersion",
                            "needsAttention": true,
                            "verificationCode": "987654",
                            "patientRiskStatus": "patientRiskStatus",
                            "patientFirstName": "patientFirstName",
                            "patientLastName": "patientLastName",
                            "createdAt": "2020-01-02T07:48:34.288Z",
                            "updatedAt": "2020-01-02T07:48:34.288Z"
                        }
                    ],
                    "_id": "5e0da052b4b3fe5188602e13",
                    "gatewayName": "gatewayName 1",
                    "gatewayKey": "gatewayKey",
                    "suite": "suite",
                    "createdAt": "2020-01-02T07:48:34.288Z",
                    "updatedAt": "2020-01-02T07:48:34.288Z"
                }
            ],
            "_id": "5e0da052b4b3fe5188602e12",
            "buildingName": "buildingName 1",
            "address": "address",
            "suite": "suite",
            "floor": "floor",
            "timeZone": "String",
            "createdAt": "2020-01-02T07:48:34.288Z",
            "updatedAt": "2020-01-02T07:48:34.288Z"
        }
    ],
    "createdAt": "2020-01-02T07:48:34.289Z",
    "updatedAt": "2020-01-02T09:10:25.200Z",
    "__v": 0
}

And if you always want to update in the first building's in the first gateway, you may use this:

router.put("/account/:accountId", async (req, res) => {
  const { patientFirstName, verificationCode } = req.body;
  try {
    let result = await Account.findByIdAndUpdate(
      req.params.accountId,
      {
        $set: {
          "buildings.0.gateways.0.devices.$[device].patientFirstName": patientFirstName
        }
      },
      {
        arrayFilters: [{ "device.verificationCode": verificationCode }],
        new: true
      }
    );

    if (!result) return res.status(404);

    res.send(result);
  } catch (err) {
    console.log(err);
    res.status(500).send("Something went wrong");
  }
});

Now you need to send only the accountId in the url like this: http://../account/5e0da052b4b3fe5188602e11



来源:https://stackoverflow.com/questions/59557959/parent-save-not-working-when-sub-document-deeply-nested-document-is-modified

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!