Reputation: 2181
I want to query firebase like so but it doesn't allow me. Is there a way to dynamically query firebase? I need to handle very specific cases - for example if the user selects "Doesn't Matter" then I need to remove that preference filter from the query, some of the fields are array datatypes and some are strings and some are numbers. I need to be able to handle all forms of datatypes and populate the where clause appropriately. Manually checking each field and calling out for each field will be get taxing on the system (there are over 20 fields to query on). Any advise would be great!
for(ddp in docDataPreferences){
if(docDataPreferences[ddp] == "Doesn't Matter"){
allDoesntMatterPreferences.push(ddp);
} else if(docDataPreferences[ddp] != "Doesn't Matter" && docDataPreferences[ddp] != '' && docDataPreferences[ddp] != undefined) {
keysWithValues.push(ddp);
whereClause += ".where('preferences."+ ddp + "', 'array-contains-any', " + JSON.stringify(docDataPreferences[ddp]) +")"
}
}
var usersMatchesCollection = config.db.collection("Users");
var query = usersMatchesCollection + whereClause;
await query.get().then( function (matchesQuerySnapshot) {
matchesQuerySnapshot.forEach(function(doc) {
//...do something
})
})
TypeError: query.get is not a function. (In 'query.get()', 'query.get' is undefined)]
I can see that the where clause is printing out correctly but I assume you can't concat the object and the string. When I manually add the returned whereClause like so:
var query = usersMatchesCollection.where('preferences.datingPrefDistance', '==', 22).where('preferences.datingPrefDrinking', '==', "No").where('preferences.datingPrefEducation', '==', "Doctorate").where('preferences.datingPrefKids', '==', "No").where('preferences.datingPrefMarried', '==', "No").where('preferences.datingPrefSmokingCig', '==', "No").where('preferences.datingPrefSmokingPot', '==', "No").where('preferences.prefDrinking', '==', "No").where('preferences.prefEducation', '==', "Doctorate").where('preferences.prefIdentifyAs', '==', "Male").where('preferences.prefKids', '==', "No").where('preferences.prefMarried', '==', "No").where('preferences.prefSmokingPot', '==', "No")
it works. So it is the concatenation that is causing an issue.
Is there a work around?
Upvotes: 1
Views: 1814
Reputation: 3499
Olivia, I use a variation of Frank's pattern constantly - I pass in an array where each entry is an object {fieldref: string, opstr: string, value: value}. My code .reduce's through the array, passing the resulting extended query, like so:
/********************************************************
* @function collectRecordsByFilter returns an array of documents from Firestore
*
* @param table a properly formatted string representing the requested collection
* - always an ODD number of elements
* @param filterArray an array of argument object, each of the form:
* {
* fieldRef: string, // field name, or dotted field name
* opstr: string // operation string representation
* value: value // value for query operation
* }
* @param ref (optional) allows "table" parameter to reference a sub-collection
* of an existing document reference (I use a LOT of structered collections)
*
* The array is assumed to be sorted in the correct order -
* i.e. filterArray[0] is added first; filterArray[length-1] last
* returns data as an array of objects (not dissimilar to Redux State objects)
* with both the documentID and documentReference added as fields.
*/
export const collectRecordsByFilter = (table, filterArray, ref = null) => {
const db = ref ? ref : firebase.firestore();
//assumes filterArray is in processing order
let query = db.collection(table);
query = filterArray.reduce((accQuery, filter) => {
return accQuery.where(filter.fieldRef, filter.opStr, filter.value);
}, query);
return query
.get() //get the resulting filtered query results
.then(querySnapshot => {
return Promise.resolve(
querySnapshot.docs.map(doc => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
})
);
})
.catch(err => {
return Promise.reject(err + ":collectRecordsByFilter");
});
};
The key is that "query" is not a method, but a query object that has a method "where" which effectively APPENDS the new condition to the query object - it is not over-written, but extended.
in use:
export const fetchTourByDateAndArtist = (dateString, artist) => {
//for each artist, only *one* tour bridges any particular data (except the default)
const filterArray = [
{ fieldRef: "dateStart", opStr: "<=", value: dateString }
];
return collectRecordsByFilter("Tours", filterArray, artist.ref).then(
tours => {
return Promise.resolve(
tours.find(tour => {
return moment(tour.dateEnd).isSameOrAfter(dateString);
})
);
}
);
};
This approach abstracts the Firestore for me, and allows a lot of re-use.
Tracy Hall (LeadDreamer)
Upvotes: 3
Reputation: 599716
It seems that you're trying to construct a query by concatenating a CollectionReference
and a string, which won't work. If you console.log(query)
, you'll see that it's a string, instead of a Query
, and since there's no get()
method on a string, that explains the error you get.
To build a query with dynamic condition, you should follow this pattern:
var usersMatchesCollection = config.db.collection("Users");
var query = usersMatchesCollection;
for(ddp in docDataPreferences){
if(docDataPreferences[ddp] == "Doesn't Matter"){
allDoesntMatterPreferences.push(ddp);
} else if(docDataPreferences[ddp] != "Doesn't Matter" && docDataPreferences[ddp] != '' && docDataPreferences[ddp] != undefined) {
keysWithValues.push(ddp);
query = query.where('preferences.'+ ddp, 'array-contains-any', docDataPreferences[ddp]))
}
}
await query.get().then( function (matchesQuerySnapshot) {
matchesQuerySnapshot.forEach(function(doc) {
//...do something
})
})
The crux here is that query = query.where
keeps changing replacing the query with another query for each condition, adding the new condition. Then, once all conditions are processed, you can execute the final query.
I tried to ensure the code matches yours, but there might be a syntax error or two in that line as I had to guess what docDataPreferences
contains.
Upvotes: 3