bevacqua
bevacqua

Reputation: 48476

Mongoose Complex Query by Subdocument

I need to find a Project by either Owner, Manager, or one of the Team Members. Here's how the schema looks like:

var project = new mongoose.Schema({
  title: { type: String, require: true },
  slug: { type: String, require: true },
  description: { type: String, require: true },
  descriptionHtml: { type: String, require: true },
  nextVanityId: { type: Number, default: 1 },
  owner: { type: ObjectId, ref: 'Member', require: true },
  teams: [{ type: ObjectId, ref: 'Team' }],
  managers: [{ type: ObjectId, ref: 'Member' }]
};

var team = new mongoose.Schema({
  name: { type: String, require: true },
  slug: { type: String, require: true },
  project:  { type: ObjectId, require: true, ref: 'Project' },
  created: { type: Date, require: true, default: Date.now },
  members: [{ type: ObjectId, default: null, ref: 'Member' }]
};

My query method looks like this:

function findByMember (member, done) {
  var id = member._id;

  Project
    .find()
    .or([
      { owner: id },
      { managers: id },
      { 'teams.members': id }
    ])
    .exec(done);
}

Currently it works for both owners and managers, but I'm drawing blanks when querying the members collection of each team. What should I use?

Upvotes: 0

Views: 445

Answers (1)

Filipe Pinheiro
Filipe Pinheiro

Reputation: 1102

In order for your query to work you can approach the problem in 2 ways.

Solution 1

Have your teams as an embedded document in projects:

var team = new mongoose.Schema({
  name: { type: String, require: true },
  slug: { type: String, require: true },
  project:  { type: ObjectId, require: true, ref: 'Project' },
  created: { type: Date, require: true, default: Date.now },
  members: [{ type: ObjectId, default: null, ref: 'Member' }]
};

var project = new mongoose.Schema({
  title: { type: String, require: true },
  slug: { type: String, require: true },
  description: { type: String, require: true },
  descriptionHtml: { type: String, require: true },
  nextVanityId: { type: Number, default: 1 },
  owner: { type: ObjectId, ref: 'Member', require: true },
  teams: [team],
  managers: [{ type: ObjectId, ref: 'Member' }]
};

This way your project document will have the team information and the teams.members makes sense.

Solution 2

Denormalise your Team data to have only the relevant information embedded:

var team = new mongoose.Schema({
  name: { type: String, require: true },
  slug: { type: String, require: true },
  project:  { type: ObjectId, require: true, ref: 'Project' },
  created: { type: Date, require: true, default: Date.now },
  members: [{ type: ObjectId, default: null, ref: 'Member' }]
};

var project = new mongoose.Schema({
  title: { type: String, require: true },
  slug: { type: String, require: true },
  description: { type: String, require: true },
  descriptionHtml: { type: String, require: true },
  nextVanityId: { type: Number, default: 1 },
  owner: { type: ObjectId, ref: 'Member', require: true },
  teams: [{
    _id: { type: ObjectId, ref: 'Team' },
    members: [{ type: ObjectId, default: null, ref: 'Member' }]
  }],
  managers: [{ type: ObjectId, ref: 'Member' }]
};

In this second approach you need to keep the Team document and the denormalised data in sync.

Upvotes: 2

Related Questions