0

Thought I'd check out node a couple days back and decided to create a simple API as an introduction to the platform. I am using node with express and mongoose. My issues at the moment revolve around trying to get a single subdocument from an array of subdocuments in my primary document.

My app has a simple structure. I have three models: place, address, and people. Each place embeds many addresses, and each address embeds many people (I picked this to get practice on subdocuments, and sub-subdocuments). The models themselves are separated into their own files as such:

./models/place.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AddressSchema = require('./address')

var PlaceSchema = new Schema({
    name: String,
    addresses: [AddressSchema.Schema]
});

module.exports = mongoose.model('Place', PlaceSchema);

.models/address.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PersonSchema = require('./person');

var AddressSchema = new Schema({
    street: String,
    city: String,
    state: String,
    zip: Number,
    people: [PersonSchema.Schema]
});

module.exports = mongoose.model('Address', AddressSchema);

.models/person.js

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

var PersonSchema = new Schema({
    name: String,
});

module.exports = mongoose.model('Person', PersonSchema);

As we can see, a place can contain an array of addresses, and each address can contain an array of people.

Now as far as the my actual routes are concerned, I have no issue adding places, and adding addresses to places (I haven't drilled down to the person level as of yet). The issue I am having is trying to get a single address. I am thinking once I receive a single address, I can next assign a bunch of people to that address.

Here is the route that is causing trouble:

router.route('/places/:place_id/addresses/:address_id')

    .get(function(req, res) {
        Place.findById(req.params.place_id, function(err, place) {
            if (err)
                res.send(err);
            res.json(place.addresses.id(req.params.address_id));
        });
    });

From the mongoose docs, it looks like the .id() method is the goto in this situation, but I get a "TypeError" when doing this, with logs saying that the object has no method 'id'.

On the other hand, I am having zero issue getting all the addresses of a single place:

router.route('/places/:place_id/addresses') 

    .get(function(req, res) {
        Place.findById(req.params.place_id, function(err, place) {
            if (err)
                res.send(err);
            res.json(place.addresses);
        });
    })

I also have zero issue creating and saving places, creating and saving addresses to a place, etc.

I've tried to dig and find others with a similar issue. I've found others who have experienced the same type error, but typically it has had to do with the order of initialization of their models. Most seem to be initializing their models in one big document, in which order of initialization is important. I do not understand how that would be an issue here?

Hopefully this is just me doing something stupid as I'm pretty stumped. Any help is very much appreciated.

EDIT----------------------------------------------------------------------------------------------

I'm thinking my problems are revolving around the id value of my :address_id parameter in the GET request above.

Taking some inspiration from this post:

MongoDB, Mongoose: How to find subdocument in found document?

I updated my get function to the following (though I do not know why what I had initially isn't working):

router.route('/places/:place_id/addresses/:address_id')

    .get(function(req, res) {
        Place.findById(req.params.place_id, function(err, place) {
            if (err)
                res.send(err);

            var address = place.addresses.filter(function (address) {
                    return address._id === req.params.address_id;
             }).pop();

            res.json(address);
        });
    });

If instead of ._id I use .city for example to filter my addresses, then I return the correct addresses in the total array. Obviously this is not what I'm looking for. I'd like the return an address based on its ID.

Any help is appreciated.

Community
  • 1
  • 1
jarvis_11
  • 3
  • 1
  • 3
  • Are you sure `address` has an `_id`? How is `address._id` generated? – Josh C. Jul 24 '14 at 04:02
  • I was under the impression that an _id was automatically generated when declaring a separate schema for a subdocument? I could be wrong though, as its obvious the id issue is tripping this up. That being said, when getting all addresses in a place, each address is assigned an _id automatically, so it seems to have one. – jarvis_11 Jul 24 '14 at 04:14
  • Ah yes. Mongoose defines each schema an _id if it doesn't have one. In Mongo, only the root document gets an _id. – Josh C. Jul 24 '14 at 04:20
  • Yep. According to the mongoose docs this should be very simple: var doc = parent.children.id(id)....yet the TypeError means its not treating addresses as a child of places for whatever reason – jarvis_11 Jul 24 '14 at 04:27

1 Answers1

1

In Mongoose there is a difference between the MongooseArray type and the MongooseDocumentArray type, which has the id() method you are looking for.

I was able to get your code working by moving the AddressSchema into the same file as PlaceSchema.

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

var AddressSchema = Schema({
    street: String,
    city: String,
    state: String,
    zip: Number
});

var PlaceSchema = Schema({
    name: String,
    addresses: [AddressSchema]
});

module.exports = mongoose.model('Place', PlaceSchema);

Now the callback from Place.findById(...) shows place.addresses as a DocumentArray and the individual subdocuments are listed as an EmbeddedDocument.

I also moved the schemas back to their separate files. This time I made sure to export the schema instead of the model. This also works.

Inspecting the code further I have found the problem!

You are referencing Address.Schema. This should be Address.schema (note the lower case). Since Address.Schema is undefined, mongoose was accepting anything.

Josh C.
  • 4,303
  • 5
  • 30
  • 51
  • Shouldn't our address in this case be part of a MongooseDocumentArray? It is a subdoc casted with its own schema. Unfortunately though I can't get your suggestion to work....still get a TypeError. Do you think this could be a modeling issue? – jarvis_11 Jul 24 '14 at 05:06
  • Bingo.....Thanks Josh. Stupid mistake, lower case fixed the issue. I actually got everything working by putting all my schemas into the same file as well. This to me seems messy. Out of curiosity, do you know what the preferred standard is? Separate schemas or put everything going into a collection into a single file? Thanks again. – jarvis_11 Jul 24 '14 at 15:35
  • Separate files is better in my opinion. – Josh C. Jul 24 '14 at 16:46