Structuring data in Firebase mixing fields as ID - firebase

I'm new using Firebase and NoSQL databases. I wonder if it's possible to do this:
Having an user database I want to fetch data by email or UID. Is it optimal to mix both fields as ID and later filter with queryStartingAtValue or queryEndingAtValuemethods?
{
"users": {
"user1#gmail.comDjDJSADfgg": {
dataUser1: ----- },
"user2#gmail.comfmadaDkK": {
dataUser2: ----- },
"user3#gmail.com3Dkdjakdja4": {
dataUser3: ----- },
"user4#gmail.comKdsadASD": {
dataUser4: ------ },
}
}

Although you can use queryStartingAtValue and queryEndingAtValue, this solution is not so clear, imagine that someone else reads your code (or even you after 6 months), would he/she knows what you meant by queryStartingAtValue? Probably not.
I think is clearer to use orderByChild and equalTo
e.g.
var ref = new Firebase("yourdb");
ref.orderByChild("email").equalTo("blahblah#gmail.com")
.on( anEvent, function(snapshot) {
console.log(snapshot.key());
});

Related

Update Firebase Realtime Data Base Specific Value in an Object

I am learning Flutter with Firebase. I have some dummy data in Firebase Realtime Data base as:
{
"postsnode": {
"posts": [
{
"postId": "u1",
"postName": "p1"
}
]
},
"usersnode": {
"users": [
{
"userId": "u1",
"userName": "bla bla 1"
},
{
"userId": "u2",
"userName": "bla bla 2"
}
]
}
}
The screen shot of the console database structure is:
I have successfully performed a query on my usersnode object to get the specific users:
void queryDB(BuildContext context) async {
AppUtil.showLoader(context: context);
FirebaseDatabase.instance.ref().child('usersnode/users').orderByChild('userId').equalTo("u1").get().then((snapshot) {
if (snapshot.exists) {
print("user:::" + snapshot.value.toString());
final jsonResponse = json.encode(snapshot.value);
List<dynamic> list = json.decode(jsonResponse);
List<Users> users = [];
list.forEach((element) {
Users usersModel = Users.fromJson(element);
users.add(usersModel);
});
users.forEach((element) {
debugPrint("UseX:::" + element.userName);
});
} else {
print('No data available.1');
}
AppUtil.dismissLoader(context: context);
// }).onError((error, stackTrace) {
// print('No data available.2');
// AppUtil.dismissLoader(context: context);
});
}
I just want to update my specific object suppose the users object with the userId=u1 only!
Can somebody help me getting this, update using the query! or I'm doing the wrong way!
Actually I just simple want to update the object based on some condition:
Update Users -> where userId=u1
Thanks in Advance!
Welcome to Flutter Firebase!
First, you should probably change your data structure. There's no need to have a random incremental ID for users. You can use their unique ID as the key for the object:
{
"posts": {}
...
"users": {
"u1": {
"userName": "bla bla 1"
},
"u2": {
"userName": "bla bla 2"
}
}
}
Then, you can do this:
final String userId = 'u1';
final userDoc = FirebaseDatabase.instance.reference().child('users/$userId');
If you're planning on storing a lot of data in posts or users, I highly recommend that you check Cloud Firestore. It offers better structure and more advanced data types.
You're starting with a very flimsy foundation to your database if you go down the route you're taking. A proper schema would look like this:
{
"posts": {
"$postID": {
"postName": "Some Name"
// ...
}
}
"users": {
"$userID": {
"userName": "Some Username"
}
}
}
You would be wise to avoid arrays entirely when using firebase database. This isn't a firebase standard, it's a NoSQL practice.
In short, NoSQL databases use key value pairs to structure data. Since you won't have any two users with the same ids, nor two posts with the same ids, using key-value pairs in your database let you easily accomplish what you're trying to do by design.
I just want to update my specific object suppose the users object with
the userId=u1 only! Can somebody help me getting this, update using
the query! or I'm doing the wrong way!
The way to do this in the user nodes is as simple as this:
String userID = "$userID"; // where $userID is your targeted user
String newUserName = "$newUsername"; // this is the new data you're trying to change
DatabaseReference userRef = FirebaseDatabase.instance.ref().child('users').child(userID);
userRef.update({
"userName": newUserName,
"timestamp": ServerValue.timestamp,
});
I have successfully performed a query on my usersnode object to get the specific users:
Obviously your old implementation won't work when you update your schema to be key-value paired. But that's a good thing because now you can do proper queries which you can then convert into arrays in the app. You would effectively parse through the DatabaseSnapshot value, which is a Map where the keys are the userIDS and the values are corresponding data.
The way you would load the users into your app with is as follows:
DatabaseReference usersRef = FirebaseDatabase.instance.ref().child('users');
usersRef.orderByChild("userName").once(DatabaseEventType.value).then((DatabaseEvent databaseEvent) {
// (You should update to the latest package which has breaking changes like this you need to adjust to)
// First get the snapshot from the new DatabaseEvent object
DataSnapshot snapshot = databaseEvent.snapshot;
// Convert the value into a map you can parse through
Object? snapshotValue = snapshot.value;
Map<dynamic, dynamic> valueMap = snapshotValue is Map<dynamic, dynamic> ? snapshotValue : {};
// Print the results for debugging
if(valueMap.isEmpty) {
debugPrint("No users found");
} else {
debugPrint("${valueMap.length} user${valueMap.length == 1 ? '' : 's'} found");
}
// Loop through the keys of this map (which are the userIDS)
List<Users> users = [];
for(String userID in valueMap.keys) {
Users userModel = Users.fromJson(valueMap[userID]);
users.add(usersModel);
debugPrint("UseX:::" + userModel.userName);
}
AppUtil.dismissLoader(context: context);
});
Lastly, if you allow users to create posts, you might want to consider adding the following node to your schema in addition to just "posts":
"user_posts" {
"$userID": {
"$postID": POST_OBJECT,
// ...
}
}
Then when you want to load the posts created by the user, rather than only being able to query the "posts" node for posts created by the user (which you'll have a limited querying ability), you query the posts under this specific user's posts (by timestamp for example).
If you do it this way, you'd store a copy of the same object under the user's posts node and posts' node.
You seem new to firebase and have a limited understanding of how the database works. It can get very messy very fast and the most important thing to do is understand how you're going to use the data, how it's going to be queried, and most importantly, how you want to be able to sort it (by alphabetical order, by ranking, by number of likes, by date created, etc.).
Understanding these concepts now is very important because it's directly tied to your database security rules as well (which you wouldn't be able to protect with the way you were doing it before).
My advice would be to look up a quick start guide on NoSQL databases (maybe just the firebase documentation starting guide), look at the firebase database security rules guide, understand what fanning out data is, and then start looking into cloud functions so you can do useful things when data is created, updated, or deleted.
It's very easy to neglect this stuff early on while only focusing only on the app, but this is equally important despite not being something your end user would spend a second thinking about. Without a solid database, you'll run into endless issues in the future.
Good luck :)

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

Vuejs & Firestore - How to Update when Data Changes in Firestore

I've gone through a bunch of tutorials and docs but cannot seem to be able to update on page when data changes in Firestore (NOTE: not Firebase)
Heres what I have currently which is working fine except if data changes in the DB it is not reflected on the page itself unless I refresh. Code below is within script tags:
import { recipeRef } from '../../firebase';
export default {
data() {
return {
recipes: []
}
},
firestore: {
recipes: recipeRef
},
created() {
db.collection('recipes').get().then((onSnapshot) => {
this.loading = false
onSnapshot.forEach((doc) => {
let data = {
'id': doc.id,
'name': doc.data().name
}
this.recipes.push(data)
})
})
}
I'm not using Vuex. Adding data, editing and reading works fine. Just not reflecting changes once data has changed. Maybe there is a life cycle hook Im supposed to be using? For "onSnapshot" - Ive tried "snap", "querySnapshot" etc. No luck.
Thanks in advance.
Remove the get() and just replace with snapshot - like so
created() {
db.collection('recipes').onSnapshot(snap => {
let foo = [];
snap.forEach(doc => {
foo.push({id: doc.id, name: doc.data().name})
});
}
});
I am not familiar with the firestore API, but glancing through the docs, it looks like calling get() is how you query a single time. Where you have onSnapshot should really be querySnapshot -- that is, the results of a one query. See:
https://firebase.google.com/docs/firestore/query-data/get-data
versus:
https://firebase.google.com/docs/firestore/query-data/listen
So to get live updates, it looks like you need to create a listener, like so:
db.collection('recipes')
.onSnapshot(function(snapshot) {
snapshot.forEach(function(doc) {
// Find existing recipe in this.recipes
// and swap in the new data
});
}, function(error) {
// handle errors
});
I think you will need to add that listener in addition to the get() query you are currently doing. Hope this helps!

Query List of Maps in DynamoDB

I am trying to filter list of maps from a dynamodb table which is of the following format.
{
id: "Number",
users: {
{ userEmail: abc#gmail.com, age:"23" },
{ userEmail: de#gmail.com, age:"41" }
}
}
I need to get the data of the user with userEmail as "abc#gmail.com". Currently I am doing it using the following dynamodb query. Is there any another efficient way to solve this issue ?
var params = {
TableName: 'users',
Key:{
'id': id
}
};
var docClient = new AWS.DynamoDB.DocumentClient();
docClient.get(params, function (err, data) {
if (!err) {
const users = data.Item.users;
const user = users.filter(function (user) {
return user.email == userEmail;
});
// filtered has the required user in it
});
The only way you can get a single item in dynamo by id if you have a table with a partition key. So you need to have a table that looks like:
Email (string) - partition key
Id (some-type) - user id
...other relevant user data
Unfortunately, since a nested field cannot be a partition key you will have to maintain a separate table here and won't be able to use an index in DynamoDB (neither LSI, nor GSI).
It's a common pattern in NoSQL to duplicate data, so there is nothing unusual in it. If you were using Java, you could use transactions library, to ensure that both tables are in sync.
If you are not going to use Java you could read DynamoDB stream of the original database (where emails are nested fields) and update the new table (where emails are partition keys) when an original table is updated.

Server side reactive Meteor publication

Assume the following current case:
I do have a collection "Tables"
A table is an object with properties like {private:0, private1,private2,…} (seats 0,1,2…)
I publish the collection with 2 arguments, one the tableId the second one the seat.
Given the seat, the publication will filter (hide) properties the client must not be able to see.
For now the tableID and seat were taken from the client's session so everything was reactive.
I have a "takeSeat(seatNb)" method. If a client invoke this method and is allowed to seat at the table, the seat number is sent back to client which then put it into it's session under the seat key. This will therefore update the subscription and filter the table's seats content correctly.
I'm not satisfied by this design because I realised that the client might be cheating by subscribing to a seat by itself. Also (and more important) I'm using another DDP client in c++ and would like to keep this logic part in the server side. i.e. not to have to subscribe with another seat once I get one, if I do take a seat at a table I would like the server to show the right fields on the table by itself.
After several searches I decided to add a collection aside for "Players" so that I might easily get notified within my "tables" collection a "player" is added or removed to a table. But this is only half of the problem. I do have to actually change the handler of the publication itself so that the filter will become reactive. This is where I'm stuck, here is some simplified code to understand the case:
Meteor.publish("current-table", function(table_id)
{
var self = this;
var handle = Players.find({"tableID": table_id}).observeChanges(
{
added: function(id)
{
console.log("A player joined the table added");
self.changed("tables", table_id);
},
removed: function(id) {
console.log("A player left the table");
self.changed("tables", table_id);
}
});
self.onStop(function() {
handle.stop();
});
// PUBLISH THE TABLE BUT HIDE SOME FIELDS BEFORE
var player = Players.findOne({"userID": this.userId, "tableID": table_id}) || {"seat": -1};
var seat = player.seat;
var privateFilter = {"private0": false, "private1": false, "private2": false, "private3": false};
delete privateFilter["private" + seat];
return Tables.find(table_id, {fields: privateFilter});
});
How to proceed ? Is there a more elegant way to achieve this ?
You could store the seat in the user's profile. Then your publication would watch for changes to the user's profile and adjust as appropriate.
For example:
Meteor.publish("current-table", function() {
var self = this;
var getFilteredTableForSeat = function(seat_id) {
// Get the table for the given seat_id, filtering the fields as appropriate
};
var handle = Meteor.users.find({_id: this.userId}).observeChanges({
changed: function (id, fields) {
if(fields.profile)
self.changed("tables", 'current-table', getFilteredTableForSeat(fields.profile.seat_id));
}
});
self.added("tables", 'current-table', getFilteredTableForSeat(Meteor.users.findOne(this.userId).profile.seat_id));
self.ready();
self.onStop(function() {
handle.stop();
});
});
If the user's seat changes then the current-table document of the Tables collection will update.
This example makes some assumptions, and will require adjustment if these aren't true for you:
You can find a table given a seat_id (if you can't, you may need to store the table id in the user's profile as well)
A seat_id always belongs to the same table (if it doesn't, you'll need to add a changed handler to wherever that information is stored)
The table information returned by the publication doesn't change (if it does, you'll need to add a changed handle to the Table collection, similar to the user handle)
Thanks to jrullmann's answer I decided to make a custom filtered publication using the reactivity of 2 collections. Here is my final code:
Meteor.publish("current-table", function(table_id)
{
var self = this;
function getFilteredTable()
{
var player = Players.findOne({"userID": self.userId, "tableID": table_id}) || {"seat": -1};
var seat = player.seat;
var privateFilter = {"prv": false, "prv0": false, "prv1": false, "prv2": false, "prv3": false};
delete privateFilter["prv" + seat];
return Tables.findOne(table_id, {fields: privateFilter});
}
var tableHandler = Tables.find(table_id).observeChanges(
{
added: function()
{
self.added('tables', table_id, getFilteredTable());
},
removed: function()
{
self.removed('tables', table_id);
},
changed: function()
{
self.changed('tables', table_id, getFilteredTable());
}
});
self.ready();
self.onStop(function() {
tableHandler.stop();
});
var handle = Players.find({"tableID": table_id}).observeChanges(
{
added: function(collection, id, fields)
{
self.changed('tables', table_id, getFilteredTable());
console.log("added");
},
removed: function(collection, id, fields)
{
// Little trick to avoid meteor use the former deleted (hidden) properties
self.removed('tables', table_id);
self.added('tables', table_id, getFilteredTable());
console.log("removed");
}
});
self.onStop(function() {
handle.stop();
});
});
I had a similar problem and wrote these two atmosphere packages which solve the problem:
https://atmosphere.meteor.com/package/server-deps
https://atmosphere.meteor.com/package/reactive-publish
Install the second package with meteorite, use "Meteor.reactivePublish" instead of "Meteor.publish" and it will automatically update when the results of any queries with the option {"reactive": true} change.
This example from the readme will publish precisely those items which the user's team can see, and will update when either the user changes team or the team's visible items change.
Meteor.reactivePublish(null, function() {
if (this.userId) {
var user = Meteor.users.findOne({_id: this.userId}, {reactive: true});
if (user.team) {
var team = Collections.teams.findOne({_id: user.team}, {reactive: true});
var visibleItems = _.compact(team.visibleItems);
return Collections.items.find({_id: {$in: visibleItems}});
}
}
});

Resources