I'm stuck on this issue of how to retrieve pushed data from firebase. I've got it set up with authentication and I had two users push a little bit of data:
{
"deck" : {
"-JkpwAnieKjQVsdtPD4m" : {
"deckName" : "Deck 1",
"color" : "Red",
"user" : "simplelogin:1"
},
"-Jkq4unexm-qwhO_U2YO" : {
"deckName" : "Deck 2",
"color" : "Blue",
"user" : "simplelogin:1"
},
"-Jkq5-II1q5yM6w3ytmG" : {
"deckName" : "Deck 3",
"color" : "Green",
"user" : "simplelogin:6"
}
}
}
Then I run:
deckRef.once('value', function(dataSnapshot) {
console.log(dataSnapshot.val());
});
Which returns the 3 pushed with their keys generated by push().
It seems there there a way with firebase to say retrieve all color entries that are made by "simplelogin:1" (so Red and Blue) but I just can't figure it out.
You're probably looking for Firebase's queries, which allow you to:
deckRef.orderByChild('user').equalTo('simplelogin:1').on(...
Don't forget to add user to the .indexOn in your security rules.
See:
Firebase documentation on queries
Firebase documentation on indexing data
query your data like this
deckRef.orderByKey().once('child_added', function(dataSnapshot) {
console.log(dataSnapshot.val());
});
Note: orderByKey() works with child_added eventType
Reference
Related
So I have a collection called "contributors." Within that collection the objects look like this:
{
"_id" : ObjectId("5ef1f75f7e6fb579ed3e5e8a"),
"#attributes" : {
"cand_name" : "name",
"cid" : "",
"cycle" : "",
"origin" : "",
"source" : "",
"notice" : ""
},
"contributor" : [
{
"#attributes" : {
"org_name" : "National Beer Wholesalers Assn",
"total" : "20000",
"pacs" : "20000",
"indivs" : "0"
}
},
{
"#attributes" : {
"org_name" : "Alabama Power",
"total" : "15000",
"pacs" : "10000",
"indivs" : "5000"
}
},
... with 10 "#attributes" objects in the "contributor" array.
What I need to do is query this whole collection to find each cand_name object that includes a certain org_name, which I'm already uncertain of how to do (since every time I've tried to query with a string it replaces the spaces in my string with %20).
But then I need to display a table with all the cand_name objects that included that org_name with the total, pacs, and indivs numbers that correspond to that cand_name & org_name.
To be super clear here I'm trying to make a table that displays every politician that a company has given money to (and how much).
So through all this I'm looking in multiple levels of each object and need to display data from all those levels together in one table. I may just be overthinking this, but it seems like it's going to be a complicated query...
Help?
Figured this out! Ended up working with the backend guy on our project and he did it all on backend.
Making a chatbot that recommends a movie based on genre and other factors. Trying to iterate through an object retrieved from a firebase realtime database in DialogFlow. I'm using forEach but when triggered it displays "Not Available".
Preferably, the function would display a random item from the list but I have not reached that part yet - any advice on this too would be appreciated.
Below is my code.
function displayData(agent) {
return ref.orderByChild("genre1").equalTo("Comedy").on("child_added", function(snapshot) {
let obj = {};
obj = snapshot.val();
obj.forEach(function(childSnapshot) {
var childData = childSnapshot.val();
agent.add(childData.name);
});
});
}
Below is a sample database
{
"movies" : {
"movie1" : {
"genre1" : "Sci-Fi",
"genre2" : "Horror",
"name" : "Alien",
"rating" : 84,
"year" : 1979
},
"movie2" : {
"genre1" : "Comedy",
"genre2" : "Parody",
"name" : "Airplane",
"rating" : 97,
"year" : 1980
},
"movie3" : {
"genre1" : "Comedy",
"genre2" : "Teen",
"name" : "Superbad",
"rating" : 88,
"year" : 2007
}
}
}
The base problem is that the Dialogflow library requires you to return a Promise, however you're using the callback version of the call instead of the Promise version of the call. You need to change this to something more like
function displayData(agent) {
return ref.orderByChild("genre1").equalTo("Comedy").on("child_added")
.then( snapshot => {
// Do something with the snapshot
});
}
(You probably also want to use once("value") instead of on("child_added"), since you don't need updates to be sent in realtime.)
While the forEach() is probably working correctly, there is a minor problem that not all agents will return all the text that has been .add()ed. So you probably only want to call .add() once.
Since you really only want once of these children, picked randomly, you can just treat this as a JavaScript object if you have enough memory. So you could just get the keys with Object.keys(obj), pick one of the keys by random, and then get the full value of the result.
I have been unable to reach into my MongoDB collection and change a value in a complex document. I have tried more variations than the one example shown below, all sorts of variations, but they fail.
I want to change the Value of the Key "air" from "rain" to "clear". In real life, I will not know that the current Value of the Key "air" is "rain".
Note, I am not using the MongoDB _id Object and would like to accomplish this without using it.
3 documents in the weatherSys collection:
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"SanFrancisco" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c6"),
"LosAngeles" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
{
"_id" : ObjectId("58a638fb1831c61917f921c7"),
"SanDiego" : [
{ "sky" : "grey" },
{ "air" : "rain" },
{ "ground" : "wet" }
]
}
var docKey = "LosAngeles";
var subKey = "air";
var newValue = "clear";
var query = {};
//var queryKey = docKey + ".$";
query[query] = subKey; // query = { }
var set = {};
var setKey = docKey + ".0." + subKey;
set[setKey] = newValue; // set = { "weather.0.air" : "clear" }
db.collection('weatherSys').update(query, { $set: set }, function(err, result) {
if (err) throw err;
});
UPDATE-1:
Ok, so I was hoping I could find a layout a bit simpler than you had suggested but I failed. Everything I tried was not addressable at the "air" Key level. So I copy and pasted your exact JSON into my collection and ran it. I'm using MongoChef to manipulate and test the collection.
Here is my new layout drived from pasting your JSON in 3 times to create 3 documents:
When I then attempted to update the "San Francisco" document's "air" key I got an unexpected result. Rather than updating "air":"dry" it created a new "air" key in the "San Francisco" Object:
So I thought ok, lets try the update again and see what happens:
As you can see it updated the "air" key that it had previously created. I could fight this out and try to make it work "my" way but I just want it to work so I reconfigure my collection layout again, along the lines of what is "working":
And run the update again:
Then I verify it by running the update again:
It works, I am updating properly in a multi-document environment. So this is my current working collection layout:
I have a couple of questions about this-
I am using the top level Key "weather" in every document. It adds nothing to the information within the document. Is there a layout design change that would not necessitate that Key and the overhead it brings along?
Lets say I have to use the "weather" key. Its value is an array, but that array only has one element, the Object which contains the Keys: city, sky, air, and ground. Does addressing necessitate the use of an array with only one element? Or could I get rid of it. Instead of "weather":[{}] could the design be "weather":{} or would I get into non addressability issues again?
It appears I can now update() any of the Values for the Keys: air, sky, and ground, but what is the find() structure to say READ the Value of the Key "ground" in one of the documents?
----> OK, I think I've got this question #3-
db.weatherSys.find({ "weather.city" : "San Francisco" }, { "weather.ground": 1 })
In the original collection layout that you had suggested, could you explain to me why it did not update as you and I had expected but instead created a new the "city" object?
A lot here. I appreciate your sticking with it.
You can't use positional operator for querying the array by its key.
You can access the weather array by index, but that means you know the array index.
For example if you want to update air element value in weather array.
db.collection('weatherSys').update( {}, { $set: { "weather.1.air" : "clear"} } );
Update:
Unfortunately, I can't see any way to update the values without knowing the array index for key.
You don't need query object as your keys are unique .
db.collection('weatherSys').update( {}, { $set: { "SanFrancisco.1.air" : "clear"} } );
or
Other variant if you want to make sure the key exists.
db.collection('weatherSys').update( { "SanFrancisco": { $exists: true } }, { $set: { "SanFrancisco.1.air" : "clear"} } );
Not sure if you can but if you can update your structure to below.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"weather" : [
{
"city": "LosAngeles",
"sky" : "grey" ,
"air" : "rain" ,
"ground" : "wet"
}
]
}
You can now use $positional operator for update.
db.collection('weatherSys').update( {"weather.city":"LosAngeles"}, { $set: { "weather.$.air" : "clear"} } );
I am using the top level Key "weather" in every document. It adds
nothing to the information within the document. Is there a layout
design change that would not necessitate that Key and the overhead it
brings along?
The only layout that I can think of is promoting all the embedded properties to the top level. Sorry, not sure why I didn't think of this the first time around. Sometimes you just need a right question to get the right answer.
{
"_id" : ObjectId("58a638fb1831c61917f921c5"),
"city": "LosAngeles",
"sky" : "grey",
"air" : "rain",
"ground" : "wet"
}
All the updates will be simply top level updates.
db.collection('weatherSys').update( {"city":"LosAngeles"}, { $set: { "air" : "clear"} } );
Lets say I have to use the "weather" key. Its value is an array, but
that array only has one element, the Object which contains the Keys:
city, sky, air, and ground. Does addressing necessitate the use of an
array with only one element? Or could I get rid of it. Instead of
"weather":[{}] could the design be "weather":{} or would I get into
non addressability issues again?
N/A if you are okay with first suggestion.
It appears I can now update() any of the Values for the Keys: air,
sky, and ground, but what is the find() structure to say READ the
Value of the Key "ground" in one of the documents?
db.weatherSys.find({ "city" : "San Francisco" }, { "ground": 1 })
In the original collection layout that you had suggested, could you
explain to me why it did not update as you and I had expected but
instead created a new the "city" object?
That is a copy paste error. I meant to suggest the working layout you have right now. Updated my previous layout.
I'm attempting to filter returned data sets with Meteor's find().fetch() to contain just a single object, it doesn't appear very useful if I query for a single subdocument but instead I receive several, some not even containing any of the matched terms.
I have a simple mixed data collection that looks like this:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"name" : "Entertainment",
"items" : [
{
"_id" : ObjectId("57a38b5f2bd9ac8225caff06"),
"slug" : "this-is-a-long-slug",
"title" : "This is a title"
},
{
"_id" : ObjectId("57a38b835ac9e2efc0fa09c6"),
"slug" : "mc",
"title" : "Technology"
}
]
}
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e8"),
"name" : "Sitewide",
"items" : [
{
"_id" : ObjectId("57a38bc75ac9e2efc0fa09c9"),
"slug" : "example",
"name" : "Single Example"
}
]
}
I can easily query for a specific object in the nested items array with the MongoDB shell as this:
db.categories.find( { "items.slug": "mc" }, { "items.$": 1 } );
This returns good data, it contains just the single object I want to work with:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"items" : [
{
"_id" : ObjectId("57a38b985ac9e2efc0fa09c8")
"slug" : "mc",
"name" : "Single Example"
}
]
}
However, if a similar query within Meteor is directly attempted:
/* server/publications.js */
Meteor.publish('categories.all', function () {
return Categories.find({}, { sort: { position: 1 } });
});
/* imports/ui/page.js */
Template.page.onCreated(function () {
this.subscribe('categories.all');
});
Template.page.helpers({
items: function () {
var item = Categories.find(
{ "items.slug": "mc" },
{ "items.$": 1 } )
.fetch();
console.log('item: %o', item);
}
});
The outcome isn't ideal as it returns the entire matched block, as well as every object in the nested items array:
{
"_id" : ObjectId("570d20de3ae6b49a54ee01e7"),
"name" : "Entertainment",
"boards" : [
{
"_id" : ObjectId("57a38b5f2bd9ac8225caff06")
"slug" : "this-is-a-long-slug",
"name" : "This is a title"
},
{
"_id" : ObjectId("57a38b835ac9e2efc0fa09c6")
"slug" : "mc",
"name" : "Technology"
}
]
}
I can then of course filter the returned cursor even further with a for loop to get just the needed object, but this seems unscalable and terribly inefficient while dealing with larger data sets.
I can't grasp why Meteor's find returns a completely different set of data than MongoDB's shell find, the only reasonable explanation is both function signatures are different.
Should I break up my nested collections into smaller collections and take a more relational database approach (i.e. store references to ObjectIDs) and query data from collection-to-collection, or is there a more powerful means available to efficiently filter large data sets into single objects that contain just the matched objects as demonstrated above?
The client side implementation of Mongo used by Meteor is called minimongo. It currently only implements a subset of available Mongo functionality. Minimongo does not currently support $ based projections. From the Field Specifiers section of the Meteor API:
Field operators such as $ and $elemMatch are not available on the client side yet.
This is one of the reasons why you're getting different results between the client and the Mongo shell. The closest you can get with your original query is the result you'll get by changing "items.$" to "items":
Categories.find(
{ "items.slug": "mc" },
{ "items": 1 }
).fetch();
This query still isn't quite right though. Minimongo expects your second find parameter to be one of the allowed option parameters outlined in the docs. To filter fields for example, you have to do something like:
Categories.find(
{ "items.slug": "mc" },
{
fields: {
"items": 1
}
}
).fetch();
On the client side (with Minimongo) you'll then need to filter the result further yourself.
There is another way of doing this though. If you run your Mongo query on the server, you won't be using Minimongo, which means projections are supported. As a quick example, try the following:
/server/main.js
const filteredCategories = Categories.find(
{ "items.slug": "mc" },
{
fields: {
"items.$": 1
}
}
).fetch();
console.log(filteredCategories);
The projection will work, and the logged results will match the results you see when using the Mongo console directly. Instead of running your Categories.find on the client side, you could instead create a Meteor Method that calls your Categories.find on the server, and returns the results back to the client.
I am working on building a simple app with React and Firebase - a way to manage income and expenditure, and my Firebase is set up with the following structure:
"cashbook" : {
"expenditure" : {
"expenditure-1" : {
"amount" : "500",
"category" : "insurance",
"date" : "date",
"name" : "Life Insurance",
"type" : "recurring"
}
},
"income" : {
"salary" : {
"amount" : "500",
"category" : "salary",
"date" : "date",
"type" : "recurring"
}
}
}
I have React set up and working perfectly with just the expenditure, using Re-base:
componentDidMount: function() {
// Two way data binding
base.syncState('cashbook/expenditure', {
context: this,
state: 'expenditure'
});
},
I can then reference using: this.state.expenditure
However, when I try to expand the app to access the Income data, things go pear shaped. I amended the componentdidmount to:
componentDidMount: function() {
// Two way data binding
base.syncState('cashbook', {
context: this,
state: 'cashbook'
});
},
And try to access with this.state.cashbook.expenditure and this.state.cashbook.income, but no joy, I get the error: Uncaught TypeError: Cannot read property 'expenditure' of undefined.
Not quite sure what to try, any pointers would be divine brown.
Thanks in advance :)
Turns out I was being a moron :)
I just needed to use syncstate on cashbook instead of cashbook/expenditure and call this.state.cashbook.expenditure.