Q:

MongoDB Conditional validation on arrays and embedded documents

I have a number of documents in my database where I am applying document validation. All of these documents may have embedded documents. I can apply simple validation along the lines of SQL non NULL checks (these are essentially enforcing the primary key constraints) but what I would like to do is apply some sort of conditional validation to the optional arrays and embedded documents. By example, lets say I have a document that looks like this:
{
  "date": <<insertion date>>,
  "name" : <<the portfolio name>>,
  "assets" : << amount of money we have to trade with>>
}

Clearly I can put validation on this document to ensure that date name and assets all exist at insertion time. Lets say, however, that I'm managing a stock portfolio and the document can have future updates to show an array of stocks like this:
{
  "date"      : <<insertion date>>,
  "name"      : <<the portfolio name>>,
  "assets"    : << amount of money we have to trade with>>
  "portfolio" : [
                  { "stockName"  : "IBM",
                    "pricePaid"  : 155.39,
                    "sharesHeld" : 100
                  },
                  { "stockName"  : "Microsoft",
                    "pricePaid"  : 57.22,
                    "sharesHeld" : 250
                  }
                ]
}

Is it possible to to apply a conditional validation to this array of sub documents? It's valid for the portfolio to not be there but if it is each document in the array must contain the three fields "stockName", "pricePaid" and "sharesHeld".

A:

MongoShell
db.createCollection("collectionname",
{
  validator: {
    $or: [
      {
        "portfolio": {
          $exists: false
        }
      },
      {
        $and: [
          {
            "portfolio": {
              $exists: true
            }
          },
          {
            "portfolio.stockName": {
              $type: "string",
              $exists: true
            }
          },
          {
            "portfolio.pricePaid": {
              $type: "double",
              $exists: true
            }
          },
          {
            "portfolio.sharesHeld": {
              $type: "double",
              $exists: true
            }
          }
        ]
      }
    ]
  }
})

With this above validation in place you can insert documents with or without portfolio.
After executing the validator in shell, then you can insert data of following 
db.collectionname.insert({
        "_id" : ObjectId("58061aac8812662c9ae1b479"),
        "date" : ISODate("2016-10-18T12:50:52.372Z"),
        "name" : "B",
        "assets" : 200
})

db.collectionname.insert({
        "_id" : ObjectId("58061ab48812662c9ae1b47a"),
        "date" : ISODate("2016-10-18T12:51:00.747Z"),
        "name" : "A",
        "assets" : 100,
        "portfolio" : [
                {
                        "stockName" : "Microsoft",
                        "pricePaid" : 57.22,
                        "sharesHeld" : 250
                }
        ]
})

If we try to insert a document like this 

db.collectionname.insert({
  "date"      : new Date(),
  "name"      : "A",
  "assets"    : 100,
  "portfolio" : [
                  { "stockName"  : "IBM",

                    "sharesHeld" : 100
                  }
                ]
})

then we will get the below error message
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 121,
                "errmsg" : "Document failed validation"
        }
})

Using Mongoose
Yes it can be done, Based on your scenario you may need to initialize the parent and the child schema.
Shown below would be a sample of child(portfolio) schema in mongoose.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var portfolioSchema = new Schema({
    "stockName" : { type : String, required : true },
    "pricePaid" : { type : Number, required : true },
    "sharesHeld" : { type : Number, required : true },
}

References:
http://mongoosejs.com/docs/guide.html
http://mongoosejs.com/docs/subdocs.html
Can I require an attribute to be set in a mongodb collection? (not null)
Hope it Helps!

