3

If I have two schemas, one which will be embedded in the other:

var mongoose = require("mongoose");
var Schema = mongoose.Schema;

// Will embed this in the personSchema below
var addressSchema = new Schema({
    street: String,
    city: String,
    state: {
        type: String,
        uppercase: true
    },
    zip: Number
});

var personSchema = new Schema({
    firstName: {
        type: String,
        required: true
    },
    lastName: {
        type: String,
        required: true
    },
    emailAddress: {
        type: String,
        lowercase: true
    },
    phoneNumber: Number,
    address: addressSchema
});

module.exports = mongoose.model("Person", personSchema);

I can't seem to get the uppercase: true to work for embedded documents - no error is thrown, but it simply doesn't uppercase the state property. Or any kind of option like that.

I've been searching the Mongoose docs, but maybe I'm just not finding where it mentions that settings these kinds of additional options on subDocuments won't work.

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
bobbyz
  • 4,946
  • 3
  • 31
  • 42
  • You say: "I can't seem to get the uppercase: true to work for embedded documents" but what is the error? – nbro Dec 04 '15 at 21:11
  • One thing you could use is the `Parent.pre("save", function(next){ "make it uppercase here!" })` hook and do it "manually" (if you don't find the solution in the meantime). Check this post: http://stackoverflow.com/questions/14023937/mongoose-getter-setters-for-normalizing-data – nbro Dec 04 '15 at 21:17
  • Sorry, I wasn't very explicit. There is no error being thrown, it's simply not uppercasing the "state". I tested it on the parent schema and it works just fine, and I tried `lowercase` on the child schema as well to no avail. – bobbyz Dec 04 '15 at 21:17
  • Apart from the possibility I am suggesting above, read this [post](http://stackoverflow.com/questions/23797457/mongoose-js-schema-options-for-embedded-documents-are-not-working), which might explain why it does not become uppercase... – nbro Dec 04 '15 at 21:22
  • @nbro The pre-save hook isn't working, and I think it's because I'm not saving a new `Child` in it's own collection in Mongo, I'm just saving the `Parent`. So I think the `Child.pre("save")` event is never fired. Any other thoughts? Is there another hook I should be using instead of `save`? – bobbyz Dec 04 '15 at 22:10
  • Could you pleas show me how you are doing it (maybe by editing your question)? – nbro Dec 04 '15 at 22:17
  • Sure, I'll use my real code instead of the dummy code above. – bobbyz Dec 04 '15 at 22:22
  • Oh. Nevermind the hook **is** working. I had just typed in the "toLowerCase" under email address and was changing the state toLowerCase too. Just changed it toUpperCase and it's working :) So is that the best way to do it? – bobbyz Dec 04 '15 at 22:26
  • Nice! Honestly I don't know if it is the best way to do it, but apparently the `uppercase` option does not work recursively...I had used the `pre("save")` to store/encrypt users' passwords, etc... You could provide now the solution as an answer, so that other people with a similar problem can have hints... – nbro Dec 04 '15 at 22:29
  • @nbro If you're curious, see my updated answer. Basically, I'm finding that its best to just use object literals to embed the data unless you absolutely _need_ an `_id` for the embedded data, in which case you're probably better off using references instead of embedding anyway. Check out the conversation I had with JohnnyHK in his answer, too, if you're interested. – bobbyz Dec 07 '15 at 17:31

2 Answers2

2

Not positive if this is the best way to do it or not, but I added a pre-save hook (per the suggestion of @nbro in the comments) and that seems to be working:

var addressSchema = new Schema({
    street: String,
    city: String,
    state: {
        type: String,
        uppercase: true
    },
    zip: Number
});

addressSchema.pre("save", function (next) {
    this.state = this.state.toUpperCase();
    next();
});

var personSchema = new Schema({
    firstName: {
        type: String,
        required: true
    },
    lastName: {
        type: String,
        required: true
    },
    emailAddress: {
        type: String,
        lowercase: true
    },
    phoneNumber: Number,
    address: addressSchema
});

Update #1:

I seem to be able to find lots of cases of people embedding simple schemas without any additional validation (required: true) or alteration (uppercase: true) occurring. While the above solution does work, it seems kind of unnecessary. What I should probably be doing is just putting in the object literal to embed the info:

var personSchema = new Schema({
    ...
    address: {
        street: String,
        city: String,
        state: {
            type: String,
            uppercase: true
        },
        zip: Number
    }
});

It seems like the only good reason to use a separate Schema is if you absolutely need the embedded data to have an _id attribute and you don't need to add additional validation or alteration options to any of the properties. If you need an _id, I'm guessing you should probably not be embedding the data, but saving it as a separate object and making a reference.

I'll keep updating this as I discover new information and best practices.

Update #2:

If you want to include validation to the embedded document, such as making the address property required, you're going to have to do it separately, as outlined in this very good blog post about it.

bobbyz
  • 4,946
  • 3
  • 31
  • 42
1

Up until recently, Mongoose would throw an exception if you tried to directly embed one schema within another like you're doing. It looks like it's partially supported now, but apparently not for cases like this.

You can get this to work by using just the definition object from addressSchema instead of the schema itself in the definition of the address field of personSchema.

var addressObject = {
    street: String,
    city: String,
    state: {
        type: String,
        uppercase: true
    },
    zip: Number
};
var addressSchema = new Schema(addressObject);

var personSchema = new Schema({
    firstName: {
        type: String,
        required: true
    },
    lastName: {
        type: String,
        required: true
    },
    emailAddress: {
        type: String,
        lowercase: true
    },
    phoneNumber: Number,
    address: addressObject
});
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • I'm not so sure it's really that new. Here's the [docs from version 2.7](http://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html) talking about embedding documents by creating a separate schema and using that schema to embed. From what I can find, the only benefit to doing this (assuming you're not using it as a ref w/ an ObjectId) is that it gives your embedded document its own `_id` property. But apparently you lose the additional validation. I just checked and your way definitely works, but now I'm confused why one would embed a schema instead of an object literal, except the id thing – bobbyz Dec 07 '15 at 16:44
  • @bobbyz Those docs only apply to embedded document _arrays_, not a single sub-document like you're doing here. – JohnnyHK Dec 07 '15 at 16:59
  • Ah, that makes sense. And I just noticed the newest mongoose docs do say that embedding a single schema instance is new to 4.2. So it seems like the only reason to not do object literals for embedded data is if you need that embedded data to have `_id`s? And if you _need_ `_id`s it almost seems like you probably need to set the relationship up as a reference to another collection. Does that sound like a pretty reasonable conclusion to come to? – bobbyz Dec 07 '15 at 17:07
  • 1
    @bobbyz Yeah, I'd agree with that. Or you just want to expose the schema in your modules for embedding, or you only have access to the schema, or something like that. – JohnnyHK Dec 07 '15 at 17:15