mongoose save multi level nested objects

﹥>﹥吖頭↗ 提交于 2020-04-16 05:03:11

问题


I try to implement my first mongoose based REST API.

I tried now for days but cannot get this up and running. I would like to save the survey with an array of controls and for each control an array of controlProperties.

In different scenarios I got it to save survey with controls array but without controlProperties and sometime with not even controls array.

Can someone please help me understand my error?

Thanks a lot.

The structure is as follows:

Survey -- Array of control -- Array of controlProperty

My schema files are:

survey.js

const mongoose = require('mongoose');
const Control = require('./control');

const surveySchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    name: {
        type: String,
        required: true,
        min: 4,
        max: 255
    },
    description: {
        type: String,
        required: false,
        max: 1000
    },
   closeDate: {
       type: Date,
       required: false
   },
   controls: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Control' }]
});

module.exports = mongoose.model('Survey', surveySchema);

control.js

const mongoose = require('mongoose');
const Survey = require('./survey');

const controlSchema = new mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    survey: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'},
    controlType: {
        type: String,
        required: true
    },
    name: {
        type: String, 
        required: true
    },
    isInput: {
        type: Boolean,
        required: true

    },
    order: {
        type: Number,
        required: true
    },
    controlProperties: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ControlProperty' }]
});


module.exports = mongoose.model('Control', controlSchema);

controlProperty.js

const mongoose = require('mongoose');
const Control = require('./control');

mongoose.Schema.Types.String.checkRequired(v => v != null);

const controlPropertySchema = new mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    control: { type: mongoose.Schema.Types.ObjectId, ref: 'Control' },
    propertyName: {
        type: String,
        required: true
    },
    propertyValue: {
        type: String,
        required: true
    },
    order: {
        type: Number,
        required: true
    }

})

module.exports = mongoose.model('ControlProperty', controlPropertySchema);

My node.js code to receive the post data is this one:

/routes/survey.js

router.post("/", (req, res, next) => {


        Survey.find({ _id: req.body._id })
            .exec()
            .then(result => {
                if (result.length >= 1) {
                    return res.status(409).json({
                        message: "Survey exists"
                    });
                } else {

                    const survey = new Survey({
                        _id: new mongoose.Types.ObjectId(),
                        name: req.body.name,
                        description: req.body.description,
                        closeDate: req.body.closeDate,
                        order: req.body.order
                    });


                    let controlData = req.body.controls;
                    let arControls = [];

                    if(controlData != null) {

                        for (var i = 0, clen = controlData.length; i < clen; i++) {
                            let c = controlData[i];
                            let control = new Control({
                                _id: new mongoose.Types.ObjectId(),
                                controlType: c.controlType,
                                name: c.name,
                                isInput: c.isInput,
                                order: c.order
                            })

                            let controlPropertyData = c.controlProperties;
                            let arControlProperty = [];

                            if(controlPropertyData != null) {

                                for (var j = 0, cplen = controlPropertyData.length; j < cplen; j++) {
                                    let cp = controlPropertyData[j];
                                    let controlProperty = new ControlProperty({
                                        _id: new mongoose.Types.ObjectId(),
                                        propertyName: cp.propertyName,
                                        propertyValue: cp.propertyValue, 
                                        order: cp.order                                
                                    })

                                    arControlProperty.push(controlProperty);
                                }

                                ControlProperty.insertMany(arControlProperty, forceServerObjectId=true,function (err,data) {
                                    if(err!=null){
                                        return console.log(err);
                                    }
                                    console.log(" " + j + " controlProperties for control " + i +  " saved");

                                    control.controlProperties = data;
                                    console.log(data);
                                });


                            } 
                            arControls.push(control);


                        }

                        Control.insertMany(arControls, forceServerObjectId=true,function (err,data) {
                            if(err!=null){
                                return console.log(err);
                            }
                            survey.controls = data;

                            console.log("controls saved");
                            console.log(data);
                        });
                    }

                    survey
                        .save()
                        .then(result => {
                            console.log("survey saved");
                            res.status(201).json(survey);
                        })
                        .catch(err => {
                            console.log(err);
                            res.status(500).json({
                                error: err
                            });
                        });
                }

        });
    });

sample post data:

{   
    "name": "TestSurvey",
    "description": "This is a test survey",
    "controls":  [
        {       
            "controlType": "Label",
            "name": "Label1",
            "isInput": false,
            "order": 1,
            "controlProperties": [
                {
                    "propertyName": "FontSize", 
                    "propertyValue": "Large",
                    "order": 1
                },
                {
                    "propertyName": "BackgroundColor", 
                    "propertyValue": "Darkgreen",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes", 
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "Fill",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment", 
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "TextColor", 
                    "propertyValue": "White",
                    "order": 6
                },
                {
                    "propertyName": "Text", 
                    "propertyValue": "Paris Work-Life Balance",
                    "order": 7
                }
            ]
        },
        {       
            "controlType": "Label",
            "name": "Label2",
            "isInput": false,
            "order": 2,
            "controlProperties": [
                {
                    "propertyName": "FontSize", 
                    "propertyValue": "Medium",
                    "order": 1
                },
                {
                    "propertyName": "Margin", 
                    "propertyValue": "20,0,20,0",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes", 
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "StartAndExpand",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment", 
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "Text", 
                    "propertyValue": "Dear [[FirstName]], \nwas your workload on the case 12345 - 67(Company) compliant to the BCG Work Life Balance Ground Rules over the past week ?",
                    "order": 6
                }
            ]
        },
        {
            "controlType": "PWLBControl",
            "name": "PWLB1",
            "isInput": true,
            "order": 3,
            "controlProperties": [
                {
                        "propertyName": "Margin", 
                        "propertyValue": "20,0,20,0",
                        "order": 1
                }
            ]
        },      
        {
            "controlType": "Button",
            "name": "button1",
            "isInput": false,
            "order": 4,
            "controlProperties": [
                {
                    "propertyName": "Text", 
                    "propertyValue": "Submit",
                    "order": 1
                },
                        {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "StartAndExpand",
                    "order": 2
                },
                {
                    "propertyName": "IsSubmitButton",
                    "propertyValue": true,
                    "order": 3
                }
            ]
        },
        {
            "controlType": "Image",
            "name": "image1",
            "isInput": false,
            "order": 5,
            "controlProperties": [
                {
                    "propertyName": "Source",
                    "propertyValue": "",
                    "order": 1
                },
                {
                    "propertyName": "VerticalOptions",
                    "propertyValue": "End",
                    "order": 2
                }
            ]
        }
    ]
}

回答1:


There is no need to keep seperate collections for controlProperties and controls. You can embed controlPropertySchema inside controlSchema, and embed controlSchema inside surveySchema. So at the end we will have only one collection for survey.

This will make possible to create a survey in one insert operation. And also you will be able to get all survey info in one read operation.

Also there are a few suggestions:

  1. It is better to not add _id fields to the schemas, mongodb will handle it.
  2. I see you use it if a survey exists with the given _id. Better to use name field to check if a survey already exists.
  3. min and max options are used for Number type, for String type minlength and maxlength are used. Docs

So the surveySchema must look like this:

const mongoose = require("mongoose");

const controlPropertySchema = new mongoose.Schema({
  // _id: mongoose.Schema.Types.ObjectId,
  // control: { type: mongoose.Schema.Types.ObjectId, ref: "Control" },
  propertyName: {
    type: String,
    required: true
  },
  propertyValue: {
    type: String,
    required: true
  },
  order: {
    type: Number,
    required: true
  }
});

const controlSchema = new mongoose.Schema({
  //_id: mongoose.Schema.Types.ObjectId,
  //  survey: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'},
  controlType: {
    type: String,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  isInput: {
    type: Boolean,
    required: true
  },
  order: {
    type: Number,
    required: true
  },
  controlProperties: [controlPropertySchema]
  //controlProperties: [{ type: mongoose.Schema.Types.ObjectId, ref: "ControlProperty" }]
});

const surveySchema = mongoose.Schema({
  // _id: mongoose.Schema.Types.ObjectId,
  name: {
    type: String,
    required: true,
    minlength: 4,
    maxlength: 255
  },
  description: {
    type: String,
    required: false,
    maxlength: 1000
  },
  closeDate: {
    type: Date,
    required: false
  },
  controls: [controlSchema]
  // controls: [{ type: mongoose.Schema.Types.ObjectId, ref: "Control" }]
});

module.exports = mongoose.model("Survey", surveySchema);

Now we can create a survey with this post route: (Please note that we don't make any conversions, since our request body's structure is the same as surveySchema)

router.post("/surveys", async (req, res) => {
  try {
    let survey = await Survey.findOne({ name: req.body.name });

    if (survey) {
      return res.status(400).send("A survey already exists with that name");
    }

    const result = await Survey.create(req.body);
    res.send(result);
  } catch (err) {
    console.log(err);

    if (err.name === "ValidationError") {
      return res.status(400).send(err.errors);
    }
    res.status(500).send("Something went wrong");
  }
});

In your request body, there was an empty propertyValue so I changed it to "propertyValue": "I was empty", and also a boolean value instead of string, so I changed it to "propertyValue": "I was true"

You can use this corrected request body:

{
    "name": "TestSurvey",
    "description": "This is a test survey",
    "controls": [
        {
            "controlType": "Label",
            "name": "Label1",
            "isInput": false,
            "order": 1,
            "controlProperties": [
                {
                    "propertyName": "FontSize",
                    "propertyValue": "Large",
                    "order": 1
                },
                {
                    "propertyName": "BackgroundColor",
                    "propertyValue": "Darkgreen",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes",
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "Fill",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment",
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "TextColor",
                    "propertyValue": "White",
                    "order": 6
                },
                {
                    "propertyName": "Text",
                    "propertyValue": "Paris Work-Life Balance",
                    "order": 7
                }
            ]
        },
        {
            "controlType": "Label",
            "name": "Label2",
            "isInput": false,
            "order": 2,
            "controlProperties": [
                {
                    "propertyName": "FontSize",
                    "propertyValue": "Medium",
                    "order": 1
                },
                {
                    "propertyName": "Margin",
                    "propertyValue": "20,0,20,0",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes",
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "StartAndExpand",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment",
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "Text",
                    "propertyValue": "Dear [[FirstName]], \nwas your workload on the case 12345 - 67(Company) compliant to the BCG Work Life Balance Ground Rules over the past week ?",
                    "order": 6
                }
            ]
        },
        {
            "controlType": "PWLBControl",
            "name": "PWLB1",
            "isInput": true,
            "order": 3,
            "controlProperties": [
                {
                    "propertyName": "Margin",
                    "propertyValue": "20,0,20,0",
                    "order": 1
                }
            ]
        },
        {
            "controlType": "Button",
            "name": "button1",
            "isInput": false,
            "order": 4,
            "controlProperties": [
                {
                    "propertyName": "Text",
                    "propertyValue": "Submit",
                    "order": 1
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "StartAndExpand",
                    "order": 2
                },
                {
                    "propertyName": "IsSubmitButton",
                    "propertyValue": "I was true",
                    "order": 3
                }
            ]
        },
        {
            "controlType": "Image",
            "name": "image1",
            "isInput": false,
            "order": 5,
            "controlProperties": [
                {
                    "propertyName": "Source",
                    "propertyValue": "I was empty",
                    "order": 1
                },
                {
                    "propertyName": "VerticalOptions",
                    "propertyValue": "End",
                    "order": 2
                }
            ]
        }
    ]
}



回答2:


Thanks for all the valuable input.

Finally I found the reason why my code did not work from the beginning. Only thing I needed to change was to do the assignment of controlProperties array to control and the controls array to survey not in the success function of the insertMany but after the whole inserMany command.

So I changed:

  ControlProperty.insertMany(arControlProperty, forceServerObjectId=true,function (err,data) {
      if(err!=null){
          return console.log(err);
      }
      console.log(" " + j + " controlProperties for control " + i +  " saved");

      control.controlProperties = data;
      console.log(data);
  });

to:

  ControlProperty.insertMany(arControlProperty, forceServerObjectId=true,function (err,data) {
      if(err!=null){
          return console.log(err);
      }
      console.log(" " + j + " controlProperties for control " + i +  " saved");

      console.log(data);
  });

  control.controlProperties = arControlProperty;

Thanks for you support.



来源:https://stackoverflow.com/questions/60661883/mongoose-save-multi-level-nested-objects

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