0

When the user visits a certain page of my App, the Component dispatches an action to fetch information. Namely, the action performs the following operations:

Base.find({req.params.id})
BaseUser.find({ baseId: req.params.id }) **returns multiple docs**
Message.find({ baseId: req.params.id }) **returns multiple docs**

The operation happens in this order. I could query the first one via .findById, but for uniformity of the problem I chose .find(). The problem now is that the results of

    Promise.all([
      Base.find({ _id: req.params.id }),
      BaseUser.find({ baseId: req.params.id }),
      Message.find({ baseId: req.params.id })
    ])

come in an array, like so:

[ 
  [ { created: 2018-08-29T23:59:35.380Z,
      _id: 5b8741151985662f10d04fdb,
      creatorId: 5b86f7970cd98b2004969bf0,
      title: 'testBase1',
  } ],
  [ { created: 2018-08-30T00:57:57.764Z,
      acceptedMembership: true,
      isCreator: true,
      _id: 5b8741151985662f10d04fdc,
      userId: 'tester1',
      baseId: 5b8741151985662f10d04fdb }
  ],
  [ { created: 2018-08-30T00:58:09.182Z,
      _id: 5b8741211985662f10d04fdd,
      baseId: 5b8741151985662f10d04fdb,
      content: 'testMessage1' } 
  ] 
]

This quite obviously causes problems when further trying to map/filter/res.json() the data. Is there any known way to return this in a single array, or even better, pass it to the front-end (redux action) as an object? Does anyone know of a better solution which handles this problem slightly differently, and prevents me from fetching each of those methods on subcomponents ?

update:

I have now constructed this, which is fairly ugly to look at:

let completeObject = {
    base: {},
    users: [],
    messages: []
};

Base.findById(req.params.id)
    .then(data => {
        completeObject.base = data;
        return data;
    })
    .then(data => {
        BaseUser.find({ baseId: req.params.id })
            .then(data => {
                completeObject.users = data;
                return data;
            })
            .then(data => {
                Message.find({ baseId: req.params.id }).then(data => {
                    completeObject.messages = data;
                    return res.json(completeObject);
                });
            });
    })
Cœur
  • 37,241
  • 25
  • 195
  • 267
HJW
  • 1,012
  • 2
  • 13
  • 32
  • 1
    Why not use `findOne` instead of `find`? – JohnnyHK Aug 30 '18 at 01:41
  • wow.. i can't believe it, hence I'm currently banging my head against the wall. To add to the legitimacy of your answer, can you please show me exactly where you would find in the DOCS an explanation as to how these methods have different returns (e.g. array vs. object return)? – HJW Aug 30 '18 at 01:46
  • You should use `async await` with some aggregation trick here – Ashh Aug 30 '18 at 03:28
  • If you have mongodb 3.6 then you could try something like this https://stackoverflow.com/questions/20056903/search-on-multiple-collections-in-mongodb/51348446#51348446 – Ashh Aug 30 '18 at 05:30

1 Answers1

0

Why don't you setup ref in the Base model to the BaseUser and Message and then use populate to fill those arrays and get one object as result filled with the arrays of BaseUser and Message?

From what I see you key on the req.params.id which means you have a cross-reference between those collections anyway.

Here is an example:

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

var BaseSchema = Schema({
  _id: Schema.Types.ObjectId,
  creatorId: Schema.Types.ObjectId,
  title: String,    
  users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
  messages: [{ type: Schema.Types.ObjectId, ref: 'Message' }],

});
var UserSchema = Schema({
  _id: Schema.Types.ObjectId,
  acceptedMembership: Boolean,
  isCreator: Boolean,
  userId: String,
  baseId: Schema.Types.ObjectId
});
var MessageSchema = Schema({
  _id: Schema.Types.ObjectId,
  baseId: Schema.Types.ObjectId,
  content: String
});

var Base = mongoose.model('Base', BaseSchema);
var User = mongoose.model('User', UserSchema);
var Message = mongoose.model('Message', MessageSchema);

Now that the schemas are defined (and you added some records) you could find a Base by _id and populate users and messages:

Base.
  findOne({ _id: req.params.id }).
  populate('users').
  populate('messages').
  exec(function (err, base) {
    if (err) return handleError(err);
    console.log(base);
  });

You should check the mongoose documentation on how to save / populate references etc.

Akrion
  • 18,117
  • 1
  • 34
  • 54
  • I read about `ref` and `populate` today. Do you have an example with the above? – HJW Aug 30 '18 at 02:44
  • Added more information. Hopefully that gives you an idea. This way you would only get 1 document (object) and inside you would have an array of `users` as a property and an array of `messages` as another. – Akrion Aug 30 '18 at 03:04