meteor.js & mongoDB - query with multiple fields - javascript

CONTEXT
I am trying to create a search functionality allowing users to fill in multiple fields, submit, and see a list of matching items from one collection. I do this using a form on the front end, which updates session variables on back-end, which are then passed as query to a mongodb collection.
HOW IT SHOULD WORK
If a user submits a venue size, then venues of that size are shown. If only a location is typed in, then venues within that location are shown. If both a size and a location are submitted, then venues that match both criteria are shown.
HOW IT ACTUALLY WORKS
If nothing is filled in, pressing search yields all items in the collection. Submitting both location and size yields venues that match both criteria. However, filling in only one field and leaving the other empty yields nothing in results. I'm wondering why this might be - it's almost as if the query is searching for a field that literally contains ''... but then why don't I see this behavior when leaving both fields empty? Help much appreciated!
CODE SNIPPET
//Search Form Helper
Template.managevenues.helpers({
venue: function () {
var venueNameVar = Session.get('venueNameVar');
var venueLocationVar = Session.get('venueLocationVar');
if(venueNameVar || venueLocationVar){
console.log(venueNameVar);
console.log(venueLocationVar);
return Venues.find({
venueName: venueNameVar,
'venueAddress.neighbourhood': venueLocationVar
});
} else {
return Venues.find({});
}
});

The answer lies in your query
Venues.find({
venueName: venueNameVar,
'venueAddress.neighbourhood': venueLocationVar
});
If you don't have one of your vars set it will look like this...
{
venueName: undefined,
'venueAddress.neighbourhood':'someVal'
}
So it would match any venue that doesn't have a name and is in some neighborhood.
A better approach would be to only set query criteria if there's a value to search...
var query = {};
if(Session.get('venueNameVar')) {
query.venueName = Session.get('venueNameVar');
}
if(Session.get('venueLocationVar') {
query.venueAddress = {
neighbourhood : Session.get('venueLocationVar');
}
}
return Venues.find(query);
I think this will work a bit better for you!

Related

Returning a single child's value on Firebase query using orderByChild and equalTo

I am trying to pull a URL for an image in storage that is currently logged in the firebase real time database.
This is for a game of snap - there will be two cards on the screen (left image and right image) and when the two matches the user will click snap.
All of my image urls are stored in the following way:
Each one has a unique child called "index" - I also have another tree that is just a running count of each image record. So currently I am running a function that checks the total of the current count, then performs a random function to generate a random number, then performs a database query on the images tree using orderByChild and an equalTo that contains the random index number.
If I log the datasnap of this I can see a full node for one record (So index, score, url, user and their values) however if I try to just pull the URL I get returned a value of Null. I can, rather annoyingly, return the term "URL" seemingly at my leisure but I can't get the underlying value. I've wondered if this is due to it being a string and not a numeric but I can't find anything to suggest that is a problem.
Please bare in mind I've only been learning Javascript for about a week at max, so if I'm making obvious rookie errors that's probably why!
Below is a code snippet to show you what I mean:
var indRef = firebase.database().ref('index')
var imgRef = firebase.database().ref('images')
var leftImg = document.getElementById('leftImg')
var rightImg = document.getElementById('rightImg')
document.addEventListener('DOMContentLoaded', function(){
indRef.once('value')
.then(function(snapShot){
var indMax = snapShot.val()
return indMax;
})
.then(function(indMax){
var leftInd = Math.floor(Math.random()* indMax + 1)
imgRef.orderByChild('index').equalTo(leftInd).once('value', function(imageSnap){
var image = imageSnap.child('url').val();
leftImg.src=image;
})
})
})
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your code needs to cater for that list, by looping over Snapshot.forEach():
imgRef.orderByChild('index').equalTo(leftInd).once('value', function(imageSnap){
imageSnap.forEach(function(child) {
var image = child.child('url').val();
leftImg.src=image;
})
})

Filter through Angular Collection fast

I know it's trivial but I have a doubt to be clarified.
I have a collection vm.groups that has almost 1000 objects. Now, each object has name, id, links etc. property fields.
Grid only displays name and id.
Now, there is a text box where we enter some text and we have to filter data, according to whatever we entered but filtering must happen only within name and id fields of the objects not the other fields that are present within the objects.
So, both things can be done,
1) I create a temp collection from the original collection objects with only name and id fields and then bind the collection to view.
// vm.groups.forEach(function(element) {
// vm.displayedFieldGroups.
// push({name: element.name,id: element.id,transformedId: element.transformedId});
// });
2) Or, I create a custom filter vm.customSearch :
vm.customSearch = function(searchVal) {
if(vm.filter.length) {
if(vm.filter.toLowerCase().indexOf(searchVal.name.toLowerCase) !== -1 ||
vm.filter.toLowerCase().indexOf(searchVal.name.toLowerCase) !== -1) {
console.log(searchVal);
return true;
}else {
return false;
}
} else {
console.log(searchVal);
return true;
}
};
And in the view vm.groups | filter: vm.customSearch.
But I think second method is slower because each value in the collection would be passed to the filter which is obviously tedious for huge collection.
Am I right?
Which is the right way to do?
UPDATE
This was for a legacy application. There was a filter already implemented but that just goes through all the fields in the object. I need to only filter for name and id fields of objects.
Hence, I need to either create a custom filter or choose the first approach that I depicted.
You're right in your assumption that the filter would go over every item in the source array and run your filter function each time there's a digest cycle.
You should proceed with the first approach.

CouchDB find paired documents and list remaining unpaired documents

I'm relatively new to NoSQL, but I have been enjoying the journey very much! I am however finding the map-reduce way of life a bit tricky! I need some help with a problem!
I have a database with two types of documents, opening transactions and closing transactions. For replication and offline functionality reasons I cannot merge the data into one document. The opening transaction document looks something like :
{
_id: "transaction-open-randomgeneratedstring",
type: "transactions-open",
vehicle: "vehicle-id",
created: "date string"
}
The closing documents looks something like:
{
_id: "transaction-close-randomgeneratedstring",
type: "transactions-close",
openid: "transaction-open-randomgeneratedstring",
created: "date string"
}
The randomgeneratedstring of a closing transactions match the randomgeneratedstring of the corresponding opening transaction.
I need a map-reduce to give me the list of open transactions that does not have a corresponding closing transaction. This will basically give me a list of outstanding transactions.
This is the map-reduce I have thus far, but it is not doing the job.
{
"map": function(doc) {
if(doc.type == "transactions-open") {
emit([doc._id, 0], "OPEN");
}
if(doc.type == "transactions-close"){
emit([doc.openid, 1], "CLOSE");
}
},
"reduce": function(keys, values, rereduce) {
var unique_labels = {};
var open = {};
keys.forEach(function(label) {
if(!unique_labels[label[0]]) {
unique_labels[label[0]] = true;
} else {
open[label[0]] = true;
}
});
return open;
}
}
I am open for changes in the _id naming / structure, but I cannot combine the two documents into one.
Thanks!
EDIT
Based on response from Hod, I changed the reduce to look like:
function(keys, values, rereducer)
{
if(values.length == 1)
return true;
}
This is certainly a step in the right direction, but the unwanted transactions are still in the result set, the value is only null. Is there no way to get those out of the result set?
As described - what you would do with a Join in SQL you do with a reduce in CouchDB. Code something like this - not tested:
{
"map": function(doc) {
if(doc.type == "transactions-open") {
emit([doc._id], 1);
}
if(doc.type == "transactions-close"){
emit([doc.openid], -1);
}
},
"reduce": "_sum";
}
So we emit a 1 for an open transaction under an ID and a -1 for a close under the same ID. Now when you reduce you will get a result for each ID of:
-1 = Closed with no record of an open (error condition).
0 = Opened and Closed
1 = Open and not yet closed.
The problem is with the keys parameter in your reduce function. The reduce phase is not called once with all possible keys. It's called per distinct key, and based on the group_level you specify.
Looking at your code, if you haven't specified any group_level, your reduce function is going to get called for every document separately.
Because you're emitting the id of the open transaction doc for both open and close markers, if you grouped at the first level, you'd get open or open/close pairs. You're still only getting a reduction on a limited set of docs at a time.
You could fix this either in your logic calling the query, or by emitting a key that let's you reduce on the entire set at once. (I imagine there are other ways too. These are the ones that come to mind.)
If you use the key approach, you'd need to emit something that looked like ["transaction", doc._id, 0]. Then a first level grouping would give you the whole transaction set like you're current code expects.
EDIT (Adding information based on edit of question.)
The reduce function is going to get called with whatever grouping you set up. It's always going to return something, even if it's just no results emitted (i.e. null).
If you don't want to handle that in the logic that's running the queries and processing the results, you need to use an approach that will allow you to group all the transaction documents together, instead of just the documents for a single transaction.
Based on what you've done so far, another approach would be to forgo the reduce phase and just look at the number of results returned by a query that's limited to the unique doc id.

Firebase JavaScript Child Comparison

I need to compare some values in my Firebase Database using JavaScript, but I'm having a hard time doing that. See image above.
The way that is suppose to work is I need to check if the user likes the same id as the other user. So for example:
User XbsX0IskrHVcaEmEBgyeok9isiM2 liked 4 items with unique ID's. Now I need to check if user jBc2Ls32DgMUSgzKUkVSw38UjQD2 liked the same thing to see if it's a match.
I have this code:
var check = ref.child('likes').child(uid2).child(uid2);
but it's not working.
You could iterate over the likes of the first user and make sure that everyone exists on the list of likes of the second user:
// Gets the list of likes from both users
var likes_user_1 = ref.child('likes').child(uid1);
var likes_user_2 = ref.child('likes').child(uid2);
// Iterates over the list of likes of the first user
var isAMatch = Object.keys(likes_user_1).every(function(like) {
// Returns true if user_2 has this like
return likes_user_2[like];
});

Checking for equivelance

OK, I'm missing something here and I just can't seem to find it because the logic seems correct to me, but I'm certain I'm not seeing the error.
var VisibleMarkers = function() {
var filtered = _.reject(Gmaps.map.markers, function(marker) {
return marker.grade != $('.mapDataGrade').val() && !_.contains(marker.subjects,$('.mapDataSubjects').val())
});
return filtered
}
I'm using underscore.js and jQuery to simplify my javascript work.
So right now, I'm checking by means of selects which data gets to be rejected and then I display the filtered markers on the (google) map (if it helps at all, this is using gmaps4rails which is working perfectly fine, its this bit of javascript that's making me lose the last of the hairs on my head).
Currently, the code functions 100% correctly for the ".mapDataGrade" select, but the ".mapDataSubjects" isn't. Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
Can someone see what I'm doing wrong?
If there's more info that needs to be included, please let me know.
This is on plain javascript on a RoR application using gmaps4rails
Now the markers object has a json array of the subjects (this is for students) and each item in the array has its ID. Its this ID that I am supposed to be checking.
_.contains compares a values, but it sounds like you want your iterator to compare a value to an object's "id" property. For that, _.some would work; it's like contains, except that, instead of comparing values, you can write the comparison as a function:
Returns true if any of the values in the list pass the iterator truth test.
Here's how you'd use it:
!_.some(marker.subjects, function(subject) {
return subject.id == $('.mapDataSubjects').val();
})
If I'm right, the whole line should be like this:
return marker.grade != $('.mapDataGrade').val() &&
// check that none of the subjects is a match
!_.some(marker.subjects, function(subject) {
// the current subject is a match if its ID matches the selected value
return subject.id == $('.mapDataSubjects').val();
});

Categories