Meteor, MongoDB get part of array through subscription - arrays

I have a question about how to just get a certain element of an array using MongoDB and MeteorJS. I have the following schema for the user document:
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
},
{
id: "09876543456789098767"
name: "65789876t8",
type: "debit"
}
]
I first subscribe to only part of the fields in the array, specifically I gather a list of all the ids. Then I have an edit screen that should subscribe to all of the fields for a specific element in the array with a matching id. I do not want to expose the rest of the array just the single element. Currently, I use the following to first gather a list of just the ids:
Meteor.users.find({_id: this.userId},
{fields:{'bankList.id': 1}});
And the following publication-subscription method to get just a specific element's information:
Publication:
Meteor.publish("userBankAdvanced", function(bankId){
check(bankId,String);
if(this.userId){
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
}else{
this.ready();
}
});
Subscription:
this.route('edit_account', {
path: '/edit/account/',
waitOn: function(){
if(Session.get("bankId")){
return Meteor.subscribe('userBankAdvanced',Session.get("bankId"));
}
return null;
},
data: function(){
if(Session.get("bankId")){
return Meteor.users.findOne();
}
return null;
},
onBeforeAction: function(){
beforeHooks.isRevise(Session.get("bankId"));
}
});
The subscription method returns all of the elements of the array with all of the information.
I want, for example, just this (not the entire list with all of the information):
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
}]

It looks like you're just missing the "fields" specifier in your "userBankAdvanced" publish function. I wrote a test in meteorpad using your example and it seems to work fine. The bank id is hardcoded for simplicity there.
So instead of
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
try using
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {fields: {'bankList.$': 1}});

No luck, in meteor the "fields" option works only one level deep. In other words there's no builtin way to include/exclude subdocument fields.
But not all is lost. You can always do it manually
Meteor.publish("userBankAdvanced", function (bankId) {
var self = this;
var handle = Meteor.users.find({
_id: self.userId, "bankList.id": bankId
}).observeChanges({
added: function (id, fields) {
self.added("users", id, filter(fields, bankId));
},
changed: function (id, fields) {
self.changed("users", id, filter(fields, bankId));
},
removed: function (id) {
self.removed("users", id);
},
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
function filter(fields, bankId) {
if (_.has(fields, 'bankList') {
fields.bankList = _.filter(fields.bankList, function (bank) {
return bank.id === bankId;
});
}
return fields;
}
EDIT I updated the above code to match the question requirements. It turns out though that the Carlos answer is correct as well and it's of course much more simple, so I recommend using that one.

Related

Push/Pull values on nested array mongoose

Im trying to update a value in a nested Array in a mongoose schema using express. I have the code required in place what i figuered i needed to update it, but the array doesn't get updated.
So the idea is to be able to have an array of data base schema objects with two fields, schemaName and schemaFields. I want to be able to update (add/remove)the values from schemaFields field of a specific schema object as needed.
I've already tried a bunch of stuff on here and elsewhere on the internet but nothing appears to work. I tried using findOneAndUpdate, findByIdAndUpdate etc.
My mongoose schema is as follows,
let databaseSchema = new Schema({
schemaName: { type: String },
schemaFields: [String]
});
let databaseSchemas = new Schema(
{
dbSchemas: [databaseSchema]
},
{
collection: 'databaseSchemas'
}
);
my update function is as follows,
schemasModel.mongo
.update(
{
_id: req.body.documentId,
'dbSchemas._id': req.body.schemaId
},
console.log('preparin to push the field \n'),
{
$push: {
'dbSchemas.$.schemaFields': req.body.newField
}
}
)
.then(() => {
res.send('new field added successfully');
});
So I solved it by removing the console.log() as a second argument to the model.update() function. Apparently this has to be the object with the operation.
The working code for the Model.update function is as follows,
schemasModel.mongo
.update(
{
_id: req.body.documentId,
'dbSchemas.schemaName': req.body.schemaToSearch
},
{
$push: {
'dbSchemas.$.schemaFields': req.body.newField
}
}
)
.then(() => {
res.send('new field added successfully');
});

Exclude items from first-level filter when children-levels are empty at Loopback find()

When using Strongloop Loopback, we can make a data request (with relations) to the database these ways:
(1) Using lb-service (at front-end)
Model.find({
filter: {
where: {id: 1},
include: {
relation: 'relationship',
scope: {where: {id: 2}}
}
}
}, function (instances) {
}, function (err) {
});
(2) Using node.js (at server-side)
Model.find({
where: {id: 1},
include: {
relation: 'relationship',
scope: {where: {id: 2}}
}
}, function (err, instances) {
});
What I need: Exclude items from first filter whether another filter fails.
There is one obvious solution: filtering the response, this way:
instances = instances.filter(function(instance){
return typeof(instance.relationship) !== "undefined";
});
But... Using filter() to eliminate is not a good scalable solution, because it will always iterate over the array. Using this solution at the front-end is not good, because the size of the array will slow down the performance. Bringing it to the server-side could be a solution. But... each model will have a particular set of relations... and it is not scalable again!
Main question: Is there some way to overcome this situation, excluding items from the first filter whether second (third, or more) fails simultaneously (or not)?
Something like, defining it on filter object:
var filter = {
where: {id: 1},
include: {
relation: {name: 'relationship', required: true}, // required means this filter *needs* to be satisfied
scope: {where: {id: 2}}
}
};
Requirements:
(1) SQL query is not an option ;)
(2) I am using MySQL as database. So things like
{ where: { id: 1, relationship.id: 2 } }
will not work as desired.
I don't know of a way to do this within the filter syntax itself. I think you would have to write a custom remote method to do the filtering yourself after the initial query was complete. Here's what that might look like:
// in /common/models/model.js
Model.filterResults = function filterResults(filter, next) {
Model.find(filter, function doFilter(err, data) {
if (err) { return next(err); }
var filteredData = data.filter(function(model) {
return model.otherThings && model.otherThings().length;
});
next(null, filteredData);
});
};
Model.remoteMethod(
'filterResults',
{
accepts: { arg: 'filter', type: 'object', http: { source: 'query' } },
returns: { arg: 'results', type: 'array' },
http: { verb: 'get', path: '/no-empties' }
}
);
Now you can hit: .../api/Models/no-empies?filter={"include":"otherThings"} and you will only get back Models that have a related OtherThing. Note that this is for a one-to-many relationship, but hopefully you can see how to change it to fit your needs.

Mongo - Return information from array

Currently I am working on a polling app for the FreeCodeCamp proyect using Meteor and have a small issue.
I am trying to create a helper that goes through a collection of polls and only returns the polls a user hasn't voted on. The users that have voted on a poll are stored in an array defined in this SimpleSchema:
voters: {
type: [String],
defaultValue: [],
minCount: 0,
autoform: {
type: "hidden"
}
}
My current helper looks like this:
Template.DoPolls.helpers({
polls: function() {
var user = Meteor.user()
return Polls.find({voters: {$not: user}});
}
});
This returns all items in the collection and doesn't seem to be excluding the polls where the user is find in the voters array.
Correct way to solve this issue is as follows:
Template.DoPolls.helpers({
polls: function() {
var user = Meteor.userId()
return Polls.find({voters: {$ne: user}});
}
});
where $ne look over the voters array and if it finds the userId then doesn't show that poll.
To achieve the opposite (so only polls where the user has voted the helper would be:
Template.PollsDone.helpers({
polls: function() {
var user = Meteor.userId()
return Polls.find({voters: {$in: [ user ] }});
}
});

Handling Subsidiary Views in Backbone.js

I have a basic Backbone application which obtain an array of JSON objects from a remote service and displays them: all good so far. However, each JSON object has an array of tags and I want to display the tags in a separate area of the webpage.
My question is: what is the most Backbone-friendly way of doing this? I could parse the existing data again in a second view, which is cleaner but takes up more computation (processing the entire array twice).
An alternative is gathering up the tag information in the primary view as it is working through the array and then passing it along to the subsidiary view, but then I'm linking the views together.
Finally, I'd like to filter based on those tags (so the tags will become toggle buttons and turning those buttons on/off will filter the information in the primary view); does this make any difference to how this should be laid out?
Bonus points for code snippets.
Hm. I'm not sure if this is the Backbone-friendly way, but I'll put the logic to retrieve a list of tags (I think that's what you meant by "parse") in the collection.
Both the main view and the subview will "listen" to the same collection, and the subview will just call collection.getTags() to get a list of tags it needs.
// Model that represents the list data
var ListDataModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
tags: []
};
}
});
// Collection of list data
var ListDataCollection = Backbone.Collection.extend({
model: ListDataModel,
initialize: function() {
var me = this;
// Expires tag collection on reset/change
this.on('reset', this.expireTagCache, this);
this.on('change', this.expireTagCache, this);
},
/**
* Expires tag cache
* #private
*/
expireTagCache: function() {
this._cachedTags = null;
},
/**
* Retrieves an array of tags in collection
*
* #return {Array}
*/
getTags: function() {
if (this._cachedTags === null) {
this._cachedTags = _.union.apply(this, this.pluck('tags'));
}
return this._cachedTags;
},
sync: function(method, model, options) {
if (method === 'read') {
var me = this;
// Make an XHR request to get data for this demo
Backbone.ajax({
url: '/echo/json/',
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
json: JSON.stringify([
{ id: 1, name: 'one', tags: [ 'number', 'first', 'odd' ] },
{ id: 2, name: 'two', tags: [ 'number', 'even' ] },
{ id: 3, name: 'a', tags: [ 'alphabet', 'first' ] }
]),
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
var listColl = new ListDataCollection();
listColl.fetch({
success: function() {
console.log(listColl.getTags());
}
});
I guess two reasons for handling this in the collection:
It keeps the View code cleaner (This is given that we are not doing very complex logic in the tag extraction - It's just a simple _.pluck() and _.union().
It has 0 business logic involved - It can arguably belong to the data layer.
To address the performance issue:
It does go through the collection twice - However, if the amont of data you are consuming is too much for the client to process even in this case, you may want to consider asking the Backend to provide an API endpoint for this. (Even 500 pieces of data with a total of 1000 tags shouldn't bee too much for a somewhat modern browser to handle nowadays.)
Hmm. Does this help?
JSFiddle to go with this with the collection and the model: http://jsfiddle.net/dashk/G8LaB/ (And, a log statement to demonstrate the result of .getTags()).

Unique array values in Mongoose

Currently trailing out Mongoose and MongoDB for a project of mine but come across a segment where the API is not clear.
I have a Model which contains several keys and documents, and one of those keys os called watchList. This is an array of ID's that the user is watching, But I need to be sure that these values stay unique.
Here is some sample code:
var MyObject = new Mongoose.Schema({
//....
watching : {type: Array, required: false},
//....
});
So my question is how can I make sure that the values pushed into the array only ever store one, so making the values unique, can i just use unique: true ?
Thanks
To my knowledge, the only way to do this in mongoose is to call the underlying Mongo operator (mentioned by danmactough). In mongoose, that'd look like:
var idToUpdate, theIdToAdd; /* set elsewhere */
Model.update({ _id: idToUpdate },
{ $addToSet: { theModelsArray: theIdToAdd } },
function(err) { /*...*/ }
);
Note: this functionality requires mongoose version >= 2.2.2
Take a look at the Mongo documentation on the $addToSet operator.
Mongoose is an object model for mongodb, so one option is to treat the document as a normal javascript object.
MyModel.exec(function (err, model) {
if(model.watching.indexOf(watchId) !== -1) model.watching.push(watchId);
model.save(...callback);
});
Although, I do agree that mongoose should have some support for this built in the form of a validator for the collection document reference feature-- especially because most of the time you want to add only unique references.
That's how you can do it using Mongoose,
IF your upcoming value is an Array
Model
.findOneAndUpdate({ _id: yourID },
{ $addToSet: { watching: { $each: yourWatchingArr } } },
function(err) { /*...*/ }
);
IF your upcoming value is a string
Model
.findOneAndUpdate({ _id: yourID },
{ $addToSet: { watching: yourStringValue } },
function(err) { /*...*/ }
);

Resources