0

Consider these 3 schemas and hierarchy: A Project has multiple Stages, a Stage has multiple Events. For pushing a new Stage into a Project, I do this:

Project.findOneAndUpdate(
      { slug: projectSlug },
      { $push: { stages: myNewStage } },
    ).then((post) => res.status(201).json({
      message: 'stage created successfully',
      data: post,
    })).catch((error) => {
      return res.status(500).json({
        code: 'SERVER_ERROR',
        description: 'something went wrong, Please try again',
      });
    });

But, how can I push a new event into a Stage? As far as I've seen, a subdocument does not have the same properties as a document (such as find, findAndUpdate).

My actual schemas:

PROJECT SCHEMA

const mongoose = require('mongoose');
const Stage = require('./Stages').model('Stages').schema;

const projectSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
  description: {
    type: String,
  },
  slug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
    unique: true,
  },
  clientSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  stages: [Stage],
  startDate: {
    type: Date,
    trim: true,
    required: true,
  },
  endDate: {
    type: Date,
    trim: true,
    required: false,
  },
},
  {
    timestamps: true,
  });

module.exports = mongoose.model('Projects', projectSchema);

STAGE SCHEMA

const mongoose = require('mongoose');
const Event = require('./Events').model('Events').schema;

const stageSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
  description: {
    type: String,
  },
  slug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
    unique: true,
  },
  clientSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  projectSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  events: [Event],
},
  {
    timestamps: true,
  });

module.exports = mongoose.model('Stages', stageSchema);

EVENT SCHEMA

const mongoose = require('mongoose');
const Comment = require('./Comments').model('Comments').schema;

const eventSchema = new mongoose.Schema({
  _id: {
    type: String,
    trim: true,
    lowercase: true,
  },
  userEmail: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  text: {
    type: String,
  },
  imgUrl: {
    type: String,
  },
  documentUrl: {
    type: String,
  },
  stageSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  clientSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  projectSlug: {
    type: String,
    trim: true,
    required: true,
    lowercase: true,
  },
  comments: [Comment],
},
  {
    timestamps: true,
  });

module.exports = mongoose.model('Events', eventSchema);
Multitut
  • 2,089
  • 7
  • 39
  • 63

1 Answers1

1

To push a new event into your stages array given that you have the projectSlug and stageSlug, you can do this:

Project.findOneAndUpdate(
  {
    $and: [
      { slug: projectSlug },
      { 'stages.slug': stageSlug },
    ]
  },
  {
    $push: { 'stages.$.events': newEvent }
  }
)
.then(() => {})
.catch(() => {});
Tunmee
  • 2,483
  • 1
  • 7
  • 13
  • Thanks man, it worked like a charm. Only, one question, can I use more than a $ at a time ('stages.$.events')? For instance, for adding a Comment into an Event. – Multitut Nov 05 '19 at 23:56
  • As far as I know, you can use it just once(one level deep). However, if you need to update a nested array as you described, you can make use of the [arrayFilters update option](https://docs.mongodb.com/manual/reference/method/db.collection.update/#specify-arrayfilters-for-array-update-operations), you can specify multiple positional matching with that. – Tunmee Nov 06 '19 at 21:43