Return only 1 Sub-document in Minimongo?


#1

I’m trying to get only the subdocument with specific criteria to be returned in a query from mini-mongo.

Structured like

{
	"_id" : "vNEZYJqBB5Ww43W26",
	"quickCallNo" : 2,
	"callNo" : "20171002-032704",
	"location" : "8021 SAMUEL L CLEMENS PKWY, DALLAS, TX",
	"type" : "Fire",
	"priority" : "High",
	"priorityColor" : "#cb9c19",
	"callers" : [ ],
    "firstOnScene" : ISODate("2017-10-02T20:40:55.544Z"),
	"transport" : [
		{
			"unitNo" : "PUMPER1",
			"startType" : "In Custody",
			"startDest" : "100 MAIN",
			"startDesc" : "Hospital",
			"startMileage" : 12,
			"endMileage" : 9,
			"active" : false,
			"startedBy" : "bmcgonag",
			"startedOn" : ISODate("2017-10-25T17:16:26.399Z")
		},
		{
			"unitNo" : "PUMPER1",
			"startType" : "Med to Safe Zone",
			"startDest" : "808 Samson DR, Biloxi, MS",
			"startDesc" : "Medical transport to safe zone.",
			"startMileage" : 14,
			"endMileage" : "",
			"active" : true,
			"startedBy" : "bmcgonag",
			"startedOn" : ISODate("2017-10-25T20:23:06.247Z")
		},
		{
			"unitNo" : "TT1",
			"startType" : "Med to Safe Zone",
			"startDest" : "2nd Hospital run",
			"startDesc" : "Back to Hospital",
			"startMileage" : 23,
			"endMileage" : 12,
			"active" : false,
			"startedBy" : "bmcgonag",
			"startedOn" : ISODate("2017-10-25T20:38:09.498Z")
		},
		{
			"unitNo" : "TT1",
			"startType" : "Med to Safe Zone",
			"startDest" : "med",
			"startDesc" : "med",
			"startMileage" : 43,
			"endMileage" : "",
			"active" : true,
			"startedBy" : "bmcgonag",
			"startedOn" : ISODate("2017-10-25T20:42:58.377Z")
		}
	],
}

I’d like to get back on the sub-document where a callSign matches, and active = true.

I’ve been trying

Calls.findOne({ "active": true, "transport": { "$elemMatch": { "unitNo": callSign, "active": true }}}, { fields: { "transport.startDest":1, "transport.startMileage":1 }});

That brings back only the sub-documents, but not only the one I want.

So then I found this on stack-overflow

Calls.findOne({ "active": true, "transport": { "$elemMatch": { "unitNo": callSign, "active": true }}}, { fields: { "transport.$":1 }});

In meteor mongo this works perfectly, but in MiniMongo i get the error

Uncaught Error: Minimongo doesn’t support $ operator in projections yet.
at MinimongoError (http://localhost:3000/packages/minimongo.js?hash=71a1410dbbd0ac70fea76b0a1103dfc61259a791:93:11)

Is there some way to get this done in Minimongo?

I’m needing the start mileage value of a specific unitNo with active true, and there could be multiple different cunitno with active true, just not the same unitNo with active true more than once.

As always, any help is appreciated.


#2

I never had to do this and avoid these situations by modeling the database to never contain collections that have sub collections. The trick with modelong in mongo lies in determining how you wish to query it. In this case its a sign that you might want to reconsider the approach of storing a subcollection as opposed to having it in a separate collection.

You almost always want tp query subcollections later on so better avoid them. Not only do you want this to simplify your queries, but also to prevent having to transform dpcuments during read. This practice can quickly become a performance bottleneck since collections can make a document quite big

But ofcourse there are cases were you want it like this or that you simply don’t have the control over the format. Im that case I qould try the transform method in combination with lodash’s filter function to transform each document that is published like below:

Meteor.publish('docs', function() {
  return Collection.find({}, {
    transform(doc) { 
      // return doc with only the active transport items
      return {
        ...doc
        transport: _.filter(doc.transport, item => active)
      };
    })
  });
});

#3

Nope, I hear you. I was really trying to get into the mindset that it’s not relational, and the more i break things apart, the more I feel like i’m making it be relational, and the more I feel like I’m trying to make multiple queries for everything.

I’ll give the transform a shot, and if that isn’t working for me, I’ll definitely go with a separate collection.

Thanks so much


#5

Good luck! Just keep in mind that document database does not mean the opposite of relational database. Its just the way that both database deal (or dont deal) with relations in data that differs them. Mongo lets the developer do it on application level. You basically fetch data multiple collections and then decide with your preferred language if it needs to be joined or not. My experience is that basic relations in MongoDB are actually quite convenient! Look at this example using minimongo:

// Example where relations make sense in mongo
const author = Authors.findOne(authorId);
const authorBooks = Books.findOne({
  authorId, 
  releaseDate: {
    $lt: new Date()
  }}, {
  sort: {
    releaseDate: -1
  }
});

Below is a situation where you don’t want separate collections. This example has a collection of players. Each player has a status that cannot live without the player record, making it an ideal candidate for a subdocument

const player = Players.findOne({'status.className': 'good'});
/*
{
  _id: "irejfosijfaofa",
  name: "Cloudspider",
  "status": {
    "name": "fit", Name of my status
    "className": "good" My status means that everything is good
  }
}
*/

#6

You’re not going to be able to do this in Minimongo, because support for projections and other fancy non-find things is very limited. If you don’t care about reactivity, you can just write a Meteor.method to do it using rawCollection(). On the other hand, if you need to maintain reactivity on the client, you’re going to have to narrow it down as much as you can using supported features and then get the one you want from that filtered client-side collection.


#7

Thanks all. I appreciate it. Still learning, but learning with each mistake. I think I’ll go back and separate into separate collections, and give that a go. Seems to be the most basic supported way of doing it, so unlikely to break with a change later.

And in this case, I’m definitely looking for reactivity.