Chris Hansen
Chris Hansen

Reputation: 8669

mongoose: how to choose one of two types

I want to have two types of users on my site - normal users and web designers.

How do I specify in the mongoose schema a type that can be two values - "normal" or "designer"

Upvotes: 1

Views: 4698

Answers (1)

Blakes Seven
Blakes Seven

Reputation: 50416

The "choice" between two types depends entirely on what you mean.

In the simple case where you just want multiple values to be valid for a property from a list of choices then you can use enum to make the only values one of the possibilities:

var userSchema = new Schema({
  "type": { "type": String, "enum": ["normal", "developer"] }
});

That allows basic validation that the property must contain one of those values and only one of those values. Trying to set to another value or omitting the value will result in a validation error.

If however, you want something more advanced such as essentially different objects with extended fields for each "type", then "discriminators" are likely for you.

This allows you to have different properties between a normal "user" and a "developer", such as a list of the projects for the developer. They will all be saved in the same collection, but they are first-class objects with all their own schema definition and rules:

var util = require('util'),
    async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

function BaseSchema() {
  Schema.apply(this,arguments);

  this.add({
    name: String,
    createdAt: { type: Date, default: Date.now }
  });
}

util.inherits(BaseSchema,Schema);

var userSchema = new BaseSchema();
var developerSchema = new BaseSchema({
  projects: [String]
});

var User = mongoose.model('User',userSchema),
    Developer = User.discriminator( 'Developer', developerSchema );

async.series(
  [
    function(callback) {
      User.remove({},callback);
    },
    function(callback) {
      async.eachSeries(
        [
          { "role": "User", "name": "Bill" },
          { "role": "Developer", "name": "Ted", "projects": [
            "apples", "oranges" ] }
        ],
        function(item,callback) {
          mongoose.model(item.role).create(item,callback);
        },
        callback
      );
    },
    function(callback) {
      User.find().exec(function(err,docs) {
        console.log(docs)
        callback(err);
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

For which the output shows the two objects in the same collection:

[ { _id: 55ff7b22f1ff34f915cc8312,
    name: 'Bill',
    __v: 0,
    createdAt: Mon Sep 21 2015 13:36:02 GMT+1000 (AEST) },
  { _id: 55ff7b22f1ff34f915cc8313,
    name: 'Ted',
    __v: 0,
    __t: 'Developer',
    createdAt: Mon Sep 21 2015 13:36:02 GMT+1000 (AEST),
    projects: [ 'apples', 'oranges' ] } ]

Note that "Ted" the "Developer" has another field stored as __t which holds the discriminator value for the model. This allows mongoose to apply magic when it is read that casts to the correct object.

It also means that each model can be used independently, with operations like:

Developer.find().exec(function(err,developers) { });

Developer.create(newDev,function(err,developer) { });

Which respectively "magically insert" the "type" so that only matching __t: 'Developer' objects are either found or created by each operation.

So enum will give you a restriction on possible values and discriminator() allows you to set up completely independent definitions for objects as a whole. It depends on which form you mean, but both are nice to use.

Upvotes: 12

Related Questions