Finding subfields in mongoDB - mongodb

How would I select the subdocument author, in the below shown object, without directly accessing it, ie authors[0], so that the result that I get back is {"_id":"4f44af6a024342300e000002", visible: true}?
The object:
{
_id: "4f44af6a024342300e000001",
title: "A book",
created: "2012-02-22T14:12:51.305Z"
authors: [{"_id":"4f44af6a024342300e000002", visible: true}]
}
I have been able to update it by doing this, but selecting it seems like a completely different story:
books.update({_id: "4f44af6a024342300e000001",'authors._id': "4f44af6a024342300e000002"},
{$set: {'books.$.visible': true}}, function(err) {
// ...
});

It is currently not possible (see a similar question with extra details)
In MongoDB, how does on get the value in a field for an embedded document, but query based on a different value
In the next version, the aggreation framework could give some step in this direction, but the real solution are the virtual collection.
For the time being (in short), use an embedded document only if you go through the parent.

Related

Mongodb Stitch realtime watch

What I intend to achieve is some sort of "live query" functionality.
So far I've tried using the "watch" method. According to the documentation:
You can open a stream of changes that match a filter by calling
collection.watch(delegate:) with a $match expression as the argument.
Whenever the watched collection changes and the ChangeEvent matches
the provided $match expression, the stream’s event handler fires with
the ChangeEvent object as its only argument
Passing the doc ids as an array works perfectly, but passing a query doesn't work:
this.stitch.db.collection<Queue>('queues')
.watch({
hospitalId: this.activehospitalid
}));
I've also tried this:
this.stitch.db.collection<Queue>('queues')
.watch({
$match: {
hospitalId: this.activehospitalid
}
},
));
Which throws an error on the console "StitchServiceError: mongodb watch: filter is invalid (unknown top level operator: $match)". The intention is watch all documents where the field "hospitalId" matches the provided value, or to effectively pass a query filter to the watch() method.
After a long search I found that it's possible to filter, but the query needs to be formatted differently
this.stitch.db.collection<Queue>('queues')
.watch({
$or: [
{
"fullDocument.hospitalId": this.activehospitalid
}
]
},
));
For anyone else who might need this, please note the important fullDocument part of the query. I havent found much documentation relating to this, but I hope it helps

How do I return a value from a specific field in a different collection in Meteor?

I've bought the Discover Meteor book and gone through the tutorial. I am still unsure of a few sections and have run into a problem which I can't get working.
I have two collections.
Computers = new Mongo.Collection('computers');
Apps = new Mongo.Collection("apps");
On the server I am publishing:
Meteor.publish('computers', function() {
return Computers.find();
});
Meteor.publish('apps', function() {
return Apps.find();
});
On the client subscribing using Iron Router:
Router.configure({
waitOn: function() {
return [Meteor.subscribe('computers'),
Meteor.subscribe('apps'),
Meteor.subscribe('users')];
}
});
In one collection I have referenced a document with the id of another document in another collection.
Computers.insert({
_id: sd9f9sdf699,
name: 'Mac1'
});
Apps.insert({
_id: ewf4y34349f,
name: 'App One',
version: '1.0',
computerId: sd9f9sdf699
});
I am then using an {{#each}} block to iterate through the documents in the apps collection
{{#each apps}}
{{> appItem}}
{{/each}
<template name="appItem">
<tr>
<td><input type="checkbox" name="checked" class="ui checkbox"></td>
<td>{{name}}</td>
<td>{{version}}</td>
<td>{{computerName}}</td>
</tr>
</template>
and when I get to the computerId field, I would like to match the document in the computers collection and then return the name of the computer instead of the id.
Here is my app_item.js code:
Template.appItem.helpers({
computerName: function() {
var id = this.computerId;
var compName = Computers.find({_id: id}, {fields: {name: 1} }).fetch();
return compName;
}
});
I am obviously missing something here but I can't seem to wrap my head around what it is.
If possible I would please prefer an answer that would teach me what I am doing wrong instead of a copy & paste solution on how to fix it. I seem to have a problem understanding this and would appreciate any advice what programming theory I should be reviewing.
Thanks for reading!
Piotr
Your helper code should use findOne() instead of find() function. find() returns coursor that has function fetch() which returns array. findOne() returns one document.
Template.appItem.helpers({
computerName: function() {
var comp = Computers.findOne(this.computerId, {fields: {name: 1} });
return comp.name;
}
});
This is one way how to handle joins in Meteor. I've found better way: http://meteor.hromnik.com/blog/joins-in-meteorjs-and-mongodb
One thing you could consider is changing your datamodel to more of a mongo style document structure where you are storing the computerid + computername both on in the apps collection. This prevents extra queries and the only downside is you need to update 2 fields instead of 1 when youre changing the computer values in a specific app document.
You can then simply display the computername with {{computerName}} without the need for a helper.
You could also store each app-computer relation in an array in the computer collection so you can easily request all installed apps on a certain computer without the need to run extra queries for each and every app. But this is not really needed in this case since both collection contain so few fields and you could just get all apps with a simple mongo query as well. Another usefull field (just a guess) could be something like installedAppsAmount in the computer document so you can list the amount of apps on each coputer without extra queries.
All of this is based on assumptions tho and really depends on your usecase.
Hope this is usefull for you.
Im not really sure why your example is not working tho based on this info: is this.computerId correct in your helper and what is compname returning if you display it in your console?

Mongoose - Querying a collection for a new property with a default value without having to resave every element

We recently added a new property to one of our Mongoose schemas that defines takes a String with an enum validator and a default value. We now need to query for documents using that property but it isn't set for pre-existing documents until after the query happens. Is there any way to get around this without having to re-save every document in that collection that existed before this change, or if not is there a best practice for how to do that cleanly?
The new property:
sales_category: {
type: String,
required: true,
enum: ["Prospect", "Subscriber", "Activated Trial", "Expired Subscriber", "Expired Free Trial"],
default: "Prospect"
}
The query:
Account.find({sales_category: "Prospect"}).populate("account_holder").exec(function(err, accounts) {
Edit: I just found https://stackoverflow.com/a/14288276/8324 which seems to imply that there is no clean way to do this, the suggestion to leave it as it is and just invert any query for {sales_category: "Prospect"} to a query for not any of the other categories seems like the best solution provided the enum never changes. I'm not sure if we can guarantee that it in this use case though so I think I might end up falling back to the "re-save everything" solution, even if it doesn't feel great.
I'll leave this open for now in case someone has a better solution.
What about using an OR conditional with an $exists operator?
Account.find().or([{sales_category: "Prospect"}, { sales_category: { $exists: false }}]).populate("account_holder").exec(function(err, accounts) {...});
One note: this doesn't use an index which would only be a concern if this is used on a large collection.

Mongo find by regex: return only matching string

My application has the following stack:
Sinatra on Ruby -> MongoMapper -> MongoDB
The application puts several entries in the database. In order to crosslink to other pages, I've added some sort of syntax. e.g.:
Coffee is a black, caffeinated liquid made from beans. {Tea} is made from leaves. Both drinks are sometimes enjoyed with {milk}
In this example {Tea} will link to another DB entry about tea.
I'm trying to query my mongoDB about all 'linked terms'. Usually in ruby I would do something like this: /{([a-zA-Z0-9])+}/ where the () will return a matched string. In mongo however I get the whole record.
How can I get mongo to return me only the matched parts of the record I'm looking for. So for the example above it would return:
["Tea", "milk"]
I'm trying to avoid pulling the entire record into Ruby and processing them there
I don't know if I understand.
db.yourColl.aggregate([
{
$match:{"yourKey":{$regex:'[a-zA-Z0-9]', "$options" : "i"}}
},
{
$group:{
_id:null,
tot:{$push:"$yourKey"}
}
}])
If you don't want to have duplicate in totuse $addToSet
The way I solved this problem is using the string aggregation commands to extract the StartingIndexCP, ending indexCP and substrCP commands to extract the string I wanted. Since you could have multiple of these {} you need to have a projection to identify these CP indices in one shot and have another projection to extract the words you need. Hope this helps.

Using Sails.js is it possible to sort with waterline by true/false?

I'm using Sails.js and thus, Waterline and I'm wondering if the following is possible?
teamFind: function(req,res,next){
User.find().skip(req.param('listPage')).limit(26).sort('school').sort('officialUser').exec(function(err,users){
res.view({
teams:users,
nextPage: parseInt(req.param('listPage'),10)+26
});
});
},
What I'm wanting to do is return the users sorted by school and then move the "Official Users" to the top of that list. Currently this just returns everything in alphabetic order. Is the issue that 'officialUser' is a boolean?
Solved! The main issue was that I had objects I wanted to sort in the wrong order. Also, I needed to add 'desc' to the 'officialUser' sort.