S. Schenk S. Schenk - 1 month ago 10
Javascript Question

Update properties that reference another model without duplication

I am trying to create an event list where users can add and remove themselves from events and specify if they are bringing guests with them to that event.

So I have an event schema and a user schema, where the event schema is referencing the user schema. So when a new event is created users can add themselves to that event with their ids.

Now I'm trying to make it so that users can also include guests. How Do I achieve that?

Here's an example

User Schema:

let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{
type: String,
require: true
},
password: {
type: String,
required: true
},
...


Event Schema:

let EventSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title: {
type: String,
require: true
},
// Guest property is ignored
attending: [{
type: mongoose.Schema.Types.ObjectId,
guest: Number, //This is being ingored and never updated
ref: 'User'
}]
})


Second way of defining the relavant part in the schema:

...
//In this example the guest will be added but duplicates will occur
user:[{
guest: Number, // To make it clear this is not referencing anything it's just a number
attending: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]


How do I update the type and guest properties with addToSet (to prevent duplication) in the above configuration?

Event.findOneAndUpdate({_id:req.body.eventId}, query)

Answer

I don't think you understand how mongoose schemas work, you might want to spend some more time on their documentation.

What you have provided as code is what appears to be a field called Events in your Schema which is an array of objects, each object of which has a single field called attending, which itself is required to be an ObjectId type and reference the 'User' collection. There is also a guest property on the field definition which will be ignored by Mongoose as it doesn't understand what you're asking for.

Realize that what this data structure is, is instructions to Mongoose on how to validate and persist your data. It won't generally be updated at runtime for most applications and will not store data directly, again its purpose is to give clues to Mongoose as to how you want the data stored.

/** Edit based on comments and updated question **/

As I said before, you can't directly embed another field into the definition of a field. What you can do is create a mixed type which has both pieces of information, but that will require you to manage things yourself to some degree.

let EventSchema = new mongoose.Schema({
  date: {
    type: Date,
    unique: true,
    timestamps: true,
    required: true
  },
  title: {
    type: String,
    require: true
  },
// Guest property is ignored
  attending: [{
    user : {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    guests : Number
  }]
})

Anytime anyone is added to the attending list, you'll need to call event.markModified() to make sure it gets saved. If you don't want to allow duplicate users, you'll also need to check that. One way to make sure that happens is to populate() that field when you fetch the event, then just check locally for matches.

/** Edit #2 **/

You can also explicitly create another schema to 'hold' your user and # guests information, which will then create models that Mongoose will watch, and you can apply validation to them via normal Mongoose methods and not worry about dirty checking. That'd look like this:

// in ./models/attendee.js
let AttendeeSchema = new Schema({
    user : {
      type: mongoose.Schema.Types.ObjectId, 
      ref: 'User',
      unique : true 
      },
    guests : Number
}

mongoose.model('Attendee', AttendeeSchema);

// in your Events definition
    let Attendee    = mongoose.model('Attendee');
    let EventSchema = new mongoose.Schema({
      date: {
        type: Date,
        unique: true,
        timestamps: true,
        required: true
      },
      title: {
        type: String,
        require: true
      },
      attending: [Attendee]
    })