i am getting an error of
Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "WkJymEhTtvgtIzQZZxs3VUTbmLh2quan" at /Products to your security rules for better performance.
this is my code:
firebase.database().ref("Products").orderByChild(user.uid + "quan").startAt(0).on('value', function (s) {
var cartNum = 0;
s.forEach(function (d) {
console.log(d.child(user.uid + "quan").val());
cartNum += d.child(user.uid + "quan").val();
});
$("#cartCount").text(cartNum);
});
am trying to query products that has user.uid+ 'quan' in my firebase database
and this is the structure of my JSON ---->>>
many thanks if someone can help me out
As described in the documentation on indexing data and in the error message, you will need to add an index to the Firebase rules for your database in the console:
{
"rules": {
"Products": {
".indexOn": "WkJymEhTtvgtIzQZZxs3VUTbmLh2quan"
}
}
}
This will solve the error message for the current user. The problem is that you will need to declare such an index explicitly for each UID, which is not feasible unless you have a specific set of users in mind.
The underlying problem is that your current data structure makes it easy to find a user for a given product, but it does not make it easy to find the products for a specific user. To allow the latter use-case, you'll want to add an additional data structure for that inverted mapping, sometimes referred to as a reverse or inverted index:
"Users": {
"uid1": {
"productId1": true,
"productId2": true
},
"uid2": {
"productId3": true,
"productId4": true
}
}
While this duplicates some data, it allows you to look up the related data in both directions. This type of data duplication to allow use-cases is quite common in NoSQL databases.
Also see:
Many to Many relationship in Firebase
Firebase Query Double Nested
Firebase query if child of child contains a value
Related
I have a small realtime firebase database that's set up like this:
database
-messages
--XXXXXXXXXXXX
---id : "XXX-XXX"
---content : "Hello world!"
It's a very simple message system, the id field is basically a combination of users id from my mysql database. I'm trying to return all messages that match one of the ids, either sender or receiver. But I can't do it, seems like firebase only support exacts querys. Could you give me some guidanse?
Here's the code I'm working with
firebase.database().ref("messages").orderByChild("id").equalTo(userId).on("value", function(snapshot)
I'm looking for something like ".contains(userId)"
Firebase supports exact matches (with equalTo) and so-called prefix queries where the value starts with a certain value (by combining startAt and endAt). It does not support querying for values that end with a certain value.
I recommend keeping a mapping from each user IDs to their messages nodes, somewhere separately in their database.
So say that you have:
messages: {
"XXXXXXXXXXXX": {
id : "YYY-ZZZ",
content : "Hello world!"
}
}
You also have the following mappings:
userMessages: {
"YYY": {
"XXXXXXXXXXXX": true
},
"ZZZ": {
"XXXXXXXXXXXX": true
}
}
Now with this information you can look up the messages for each user based on their ID.
For more on the modeling of this type of data, I recommend:
Best way to manage Chat channels in Firebase
Many to Many relationship in Firebase
this artcle on NoSQL data modeling
Is there any way to search with array in realtime database as cloud database provides us.
Cloud example:
.collection("bids")
.where("title", "array-contains-any", ["xx", "yy", "zz"])
In above query I got all bids where title is xx, yy, zz.
How do we can search like this in firebase realtime database.
Actually in real-time database there is no any thing like "array-contains". There for if you have an array you have to deal with just like a regular JavaScript array.
In your case you can try this.
var ref = firebase.database().ref('/bids/title');
ref.once('value').then(function(snap) {
var array = snap.val();
for (var i in array) {
var value = array[i]
console.log(value);
if (value == 'xx') { ... }
}
});
There is no equivalent operation in the Realtime Database.
The common approach for this type of use-case, is to set up an additional data structure mapping from bid titles to bids as shown here: Firebase query if child of child contains a value
You can then read the bid IDs for each title with a separate query, look up the additional properties for each bid with another call, and then merge all results in your client-side application code. This is not nearly as slow as you may initially expect, as Firebase pipelines the requests over a single websocket connection.
Nowadays, in Firebase's real-time database, you don't have to do that anymore! These ugly codes to save arrays nowadays are left behind, try to use the module: Fireray.
These codes make Firebase references very ugly, for example:
"dir": {
"arrays": {
"users": {
"0": {},
"1": {},
"2": {}
}
},
"others": {}
}
It is a Brazilian module, but there is a documentation in English. Give her a chance, she's really very good!
Links:
Npm package
Documentation in English
Repository
Programmer
Sincerely: Lucas
I have a real time firebase app with chatrooms and comments. A comment belongs to a single chatroom and a chatroom can have many comments. I'd like retrieve just the comments for a given room, but right now I'm only able to get all of them.
Every time a comment is saved, I also save its id to the room to which it belongs. That way, every room has a list of its comment ids. I can retrieve a chatroom's list of child comment ids using chatRooms/${id}/commentIds.
// data structure is basically like this:
chatRooms: {
ROOMID123: {
title: "Room A",
commentIds: {
COMMENTIDABC: true,
COMMENTIDXYZ: true
}
}
},
comments: {
COMMENTIDABC: {
message: "some message",
parentRoomId: ROOMID123
},
COMMENTIDXYZ: {
message: "Another message",
parentRoomId: ROOMID123
}
}
I can get the comment ids for a given room, based on the room's id, like this:
firebase.database().ref(`chatRooms/${chatRoomId}/commentIds`).on('value',
snapshot => {
const commentsObject = snapshot.val();
const commentIdsList = Object.keys(commentsObject);
});
Would it be better for me to
a) use that list of commentIds to retrieve only the comments for a given room? If so, what query should I use?
b) use the chatRoom's id to retrieve every comment with a matching parentRoomId? If so, I don't know how to do this despite reading through the docs.
Thank you!
I'd propose a third option: store the comments for each chat room in a separate parent node. So something like:
commentsPerRoom: {
ROOMID123: {
COMMENTIDABC: {
message: "some message",
},
COMMENTIDXYZ: {
message: "Another message",
}
}
}
With the above structure you can retrieve the comments for a specific room with a single direct lookup:
firebase.database().ref(`commentsPerRoom/${chatRoomId}`).on('value',
Reasons I'd use this data structure over your current one:
Storing the comments as a single list means you'd have to query that list. And while Firebase Realtime Database scales quite well, querying for data is always going to have scalability limits. That's why the above data structure allows a direct look up, instead of requiring a query.
Loading the individual comments through the comment IDs is definitely also possible, and not nearly as slow as some developers think due to the fact that Firebase pipelines the requests over a single connection. But it seems unneeded here, since the comments already have a strong 1:n association with the room they belong to. Each comment only belongs to one room, so you might as well model that relationship in how you structure the data, and save yourself all the lookups.
Retrieving comments for a specific room is by far the most common use-case, and this data structure allows that use-case the most efficiently.
I have a nested firebase database with a structure like this:
posts: {
0: {
title: "...",
content: '...',
comments: [{
by: "someone"
}, {
by: "anotherone"
}]
},
1: {
...
}
}
Now I want to get the first comments on each post so I tried
firebase.database().ref('/posts/{postId}/comments/0').once('value',function(snapshot){
snapshot.forEach(function(child){ console.log(child.val());});
})
But don't know why the only thing I got in the console is false. So are there anyone knows what's wrong or is it impossible to query like that?
The Firebase Database SDKs will also read entire nodes. It is not possible to retrieve a subset of a node's data or to get just the keys.
To get the first comment of each post, you must know the key of each post already. Since you can't read just the keys of the posts, this means that you must read all data to get just the first comment of each post:
firebase.database().ref('/posts').once('value',function(snapshot){
snapshot.forEach(function(child){
console.log(child.val().comments[0]);
});
})
While this gives the result you need, it is quite wasteful in bandwidth: the client is ready way more data than you need. As usual in NoSQL databases, a better solution may require you to change your data model to fit your use-case. For example: consider storing the latest comment for each post in a separate top-level list:
latest-comments: {
0: {
by: "someone"
},
1: {
...
}
}
You will need to update this list (in addition to your original comments list) whenever a new comment is posted. But in return, reading the latest comment for each post is now very cheap.
Thanks for looking at my question. It should be easy for anyone who has used Meteor in production, I am still at the learning stage.
So my meteor setup is I have a bunch of documents with ownedBy _id's reflecting which user owns each document (https://github.com/rgstephens/base/tree/extendDoc is the full github, note that it is the extendDoc branch and not the master branch).
I now want to modify my API such that I can display the real name of each owner of the document. On the server side I can access this with Meteor.users.findOne({ownedBy}) but on the client side I have discovered that I cannot do this due to Meteor security protocols (a user doesnt have access to another user's data).
So I have two options:
somehow modify the result of what I am publishing to include the user's real name on the server side
somehow push the full user data to the clientside and do the mapping of the _id to the real names on the clientside
what is the best practice here? I have tried both and here are my results so far:
I have failed here. This is very 'Node' thinking I know. I can access user data on clientside but Meteor insists that my publications must return cursors and not JSON objects. How do I transform JSON objects into cursors or otherwise circumvent this publish restriction? Google is strangely silent on this topic.
Meteor.publish('documents.listAll', function docPub() {
let documents = Documents.find({}).fetch();
documents = documents.map((x) => {
const userobject = Meteor.users.findOne({ _id: x.ownedBy });
const x2 = x;
if (userobject) {
x2.userobject = userobject.profile;
}
return x2;
});
return documents; //this causes error due to not being a cursor
}
I have succeeded here but I suspect at the cost of a massive security hole. I simply modified my publish to be an array of cursors, as below:
Meteor.publish('documents.listAll', function docPub() {
return [Documents.find({}),
Meteor.users.find({}),
];
});
I would really like to do 1 because I sense there is a big security hole in 2, but please advise on how I should do it? thanks very much.
yes, you are right to not want to publish full user objects to the client. but you can certainly publish a subset of the full user object, using the "fields" on the options, which is the 2nd argument of find(). on my project, i created a "public profile" area on each user; that makes it easy to know what things about a user we can publish to other users.
there are several ways to approach getting this data to the client. you've already found one: returning multiple cursors from a publish.
in the example below, i'm returning all the documents, and a subset of all the user object who own those documents. this example assumes that the user's name, and whatever other info you decide is "public," is in a field called publicInfo that's part of the Meteor.user object:
Meteor.publish('documents.listAll', function() {
let documentCursor = Documents.find({});
let ownerIds = documentCursor.map(function(d) {
return d.ownedBy;
});
let uniqueOwnerIds = _.uniq(ownerIds);
let profileCursor = Meteor.users.find(
{
_id: {$in: uniqueOwnerIds}
},
{
fields: {publicInfo: 1}
});
return [documentCursor, profileCursor];
});
In the MeteorChef slack channel, #distalx responded thusly:
Hi, you are using fetch and fetch return all matching documents as an Array.
I think if you just use find - w/o fetch it will do it.
Meteor.publish('documents.listAll', function docPub() {
let cursor = Documents.find({});
let DocsWithUserObject = cursor.filter((doc) => {
const userobject = Meteor.users.findOne({ _id: doc.ownedBy });
if (userobject) {
doc.userobject = userobject.profile;
return doc
}
});
return DocsWithUserObject;
}
I am going to try this.