Firebase - snapshot based on ids - firebase

I've been looking everywhere for the logic to build a specific request, but I don't completely get the firebase philosophy. This is my data structure:
`users:{
u1_id:{...}
u2_id:{...}
...
},
contacts:{
u1_id:{
contact1_id,
contact2_id,
...
}
}`
My first option would be a request that gets the data regarding the user id, the second one (the actual one) is to store the data I need from the contacts when they're added but I would need to run a big update for each contacts of each user and that doesn't seem to be the right solution. (I've check questions, firebase doc and videos)
The closest solution I found is this (from their youtube):
function getUserContacts(key, cb){
const rootRef = firebase.database().ref()
const contactsRef = rootRef.child('contacts')
const usersRef = rootRef.child('users')
contactsRef.child(key).on('child_added', snap => {
let userRef = usersRef.child(snap.key)
userRef.once('value', cb)
})
}
but it gives the users 1 by 1 and not an object with all my users
Edit: my temporary solution ->
firebase.database().ref('users')
.on('value', snaps => {
firebase.database().ref(`contacts/${currentUser.uid}`)
.on('value', snapshot => {
let userContacts = []
snapshot.forEach(snapc => {
snaps.forEach(snapu => {
let duser = snapu
if(snapc.key == snapu.key)
{
userContacts.push(snapu.val())
}
})
})
dispatch({
type: CONTACTS_FETCH,
payload: userContacts
})
})
})

The question is still a bit unclear but let me present three options:
If we want to populate a list with user_1's contacts, we read in the users contacts node by value. In this case, user_1 has two contacts; user_id2 and user_id3
contacts
user_1
user_id2: true
user_id3: true
Value reads in the parent node (user_1) with all of the child data as well. Because we have the entire node of data (a snapshot) we can iterate over the child values of user_1 to obtain each user_idx (which is the key of each child node). As you iterate over the children, each will present a key, to which you can then read in that specific user node.
A second option is to change the structure.
contacts
user_2
name: "John"
rank: "Captain"
contact_of: "user_1"
user_3
name: "William"
rank: "Second Officer"
contact_of: "user_1"
with this structure, if user 1 wants to load their contacts, a simple query on the contacts node where contact_of = "user_1". That would load each contact and all of their data.
This second example works when a user has specific contacts that are not shared.
If multiple users can have multiple contacts, here's another option.
contacts
user_2
name: "Jean Luc"
rank: "Captain"
contact_of
user_1: true
user_2: true
user_3
name: "William"
rank: "Second Officer"
contact_of:
user_1: true
user_2: true
in this case, perform a deep query on the contacts node where contact_of/user_1 = true, which will present user_2 and user_3/

Related

Where to store Record meta data with Redux and Immutable JS

I switched over to a Redux + Immutable JS project from Ember a few months ago and am overall enjoying the experience.
One problem I still have not found a nice solution for when working with Records is storing meta data for that Record.
For example, let's say I have a User record:
const userRecord = Immutable.Record({
id: null,
name: '',
email: ''
});
For the User, I may also wish to store properties like isLoading or isSaved. The first solution would be to store these in the userRecord. Although this would be the easiest solution by far, this feels wrong to me.
Another solution might be to create a User Map, which contains the User Record, as well as meta data about the User.
Ex.
const userMap = Immutable.Map({
record: Immutable.Record({
id: null,
name: '',
email: ''
}),
isLoading: false,
isSaved: true
});
I think this is more elegant, but I don't like how all the user properties become even more deeply nested, so accessing User properties becomes very verbose.
What I miss most about Ember is being able to access Model properties easily.
Ex. user.get('isSaved') or user.get('name')
Is it possible to recreate something like this with Redux and Immutable? How have you approached this situation before?
I might be misunderstanding the problem, because
What I miss most about Ember is being able to access Model properties easily.
user.get('isSaved') or user.get('name')
This does work for Immutable records.
If you don't want to add too many properties to your record, you could have a single status property and add some getters (assuming your statuses are mutually exclusive):
const STATUS = {
INITIAL: 'INITIAL',
LOADING: 'LOADING',
SAVING: 'SAVING
};
class UserRecord extends Immutable.Record({
id: null,
name: '',
email: '',
status: STATUS.INITIAL}) {
isLoading() {
return this.get('status') === STATUS.LOADING;
}
isSaving() {
return this.get('status') === STATUS.SAVING;
}
}
new UserRecord().isLoading()); // returns false
new UserRecord({status: STATUS.LOADING}).isLoading(); // returns true
new UserRecord().set('status', STATUS.LOADING).isLoading(); // returns true

NoSQL Firebase denormalization

So I just recently learn about denormalization and I am just trying to figure out the best way to structure my firebase database. What I currently have is I am displaying a users class schedule with the classes course number and intructor name. Firebase looks like this for me:
-root
-users
-uid1
-uid2
-schedules
-uid1 : true (match with users uid)
-uid2 : true (match with users uid)
-class
-classKey1
-users
-uid1 : true
-uid2 : true
-courseKey1 : true
-intructorKey1 : true
-classKey1
-users
-uid1
-courseKey2 : true
-intructorKey2 : true
-courses
-courseKey1
-intructors
-intructorKey1 : true
-intructorKey2 : true
-courseKey2
-similar to above
-intructors
-intructorKey1
-courses
-courseKey1: true
-courseKey2: true
-intructorKey2
-similar to above
Now, that is the basic structure of what I am working with, excluding all the unneeded information. Lets say I want to display all of the the schedule of the currently logged in user I will need to do the following.
_af.auth.subscribe(authState => {
let userID = authState.uid;
_af.database.list('/schedule/' + userID ).subscribe(schedule =>{
this.schedule = schedule; //global variable
this.filterClasses(); call function
});
});
_af.database.list('/class').subscribe(classes => {
this.masterClasses = classes; //gloabal variable
this.filterClasses();
});
Now because its all done asynchronously the only way I could think to do this is call the filterClasses function inside of each subscription.
filterClasses(): void {
if (this.scheduleKeys != null && this.masterClasses.length > 0) {
this.outputClasses = this.masterClasses.filter(value => {
let bool = false;
for (let i = 0; i < this.scheduleKeys.length; i++) {
if (this.scheduleKeys[i].$key === value.$key) {
bool = true;
break;
}
}
return bool;
});
this.outputClasses.forEach((value, i) => {
_af.database.object('courses/' + value.courseKey).subscribe(v => {
this.outputClasses[i]['courseNum'] = v.course;
})
_af.database.object('intructors/' + value.intructorKey).subscribe(v => {
this.outputClasses[i]['intructorName'] = v.name;
})
})
}
}
As you can see when I am done filtering my master list of classes into just the ones in my schedule I also now need to go and grab the course number firebase and the intructors name. Both of which require me to call firebase again. So to try and reduce researching firebase which seems to be causing some problems due to being async should I instead be denormalizing my data? Should in the class root of firebase instead of storing just courseKey1 should I store all the data associated to couseKey1? However, this will cause rooting in firebase because if I structure this way now in my courses root when I say the intructors key instead of just saving the key I would save everything which is just another level deeper firebase goes.
This is kind of an indirect answer:
Denormalizing data is a good thing but in same cases it may not be necessary.
For example in this case a student wants to know his courses, so you know the direct path to the student; users/uid_0 and then maybe a query for the semester you are interested in.
Here's a structure that goes with your courses and instructors
users
uid_0
schedules
-Yiaiajsdaoiojasd
semester: 1
courses
course1: true
course3: true
-Jyuhus99jsijskkss
semester: 2
courses
course2: true
course4: true
uid_1
schedules
-Yjs909k0s0s0ks0s
semester: 1
courses
course1: true
course3: true
-Jyuhus99jsijskkss
semester: 2
courses
course2: true
course4: true
This really isn't very deep since once you read in the user node, you have specific paths to the other data you need: /courses/courseKey1/instructors for example, then followed by instructors/instructor1/name.
Where you get into trouble is if you DON'T know the direct path to the data and have to pile up queries on queries - that's where denormalizing is most effective. Also, queries have lot of overhead vs just observing a node at a path you know.

How to get multiple objects in list at a point in time

I want to provide my users with an API (pointing to my server) that will fetch data from Firebase and return it to them. I want it to be a 'normal' point-in-time request (as opposed to streaming).
My data is 'boxes' within 'projects'. A user can query my API to get all boxes for a project.
My data is normalised, so I will look up the project and get a list of keys of boxes in that project, then go get each box record individually. Once I have them all, I will return the array to the user.
My question: what is the best way to do this?
Here's what I have, and it works. But it feels so hacky.
const projectId = req.params.projectId; // this is passed in by the user in their call to my server.
const boxes = [];
let totalBoxCount = 0;
let fetchedBoxCount = 0;
const projectBoxesRef = db
.child('data/projects')
.child(projectId)
.child('boxes'); // a list of box keys
function getBox(boxSnapshot) {
totalBoxCount++;
db
.child('data/boxes') // a list of box objects
.child(boxSnapshot.key())
.once('value')
.then(boxSnapshot => {
boxes.push(boxSnapshot.val());
fetchedBoxCount++;
if (fetchedBoxCount === totalBoxCount) {
res.json(boxes); // leap of faith that getBox() has been called for all boxes
}
});
}
projectBoxesRef.on('child_added', getBox);
// 'value' fires after all initial 'child_added' things are done
projectBoxesRef.once('value', () => {
projectBoxesRef.off('child_added', getBox);
});
There are some other questions/answers on separating the initial set of child_added objects, and they have influenced my current decision, but they don't seem to relate directly.
Thanks a truck-load for any help.
Update: JavaScript version of Jay's answer below:
db
.child('data/boxes')
.orderByChild(`projects/${projectId}`)
.equalTo(true)
.once('value', boxSnapshot => {
const result = // some parsing of response
res.json(result);
});
This may be too simple a solution but if you have projects, and each project has boxes
your projects node
projects
project_01
boxes
box_id_7: true
box_id_9: true
box_id_34: true
project_37
boxes
box_id_7: true
box_id_14: true
box_id_42: true
and the boxes node
boxes
box_id_7
name: "a 3D box"
shape: "Parallelepiped"
belongs_to_project
project_01: true
box_id_14
name: "I have unequal lenghts"
shape: "Rhumboid"
belongs_to_project
project_37: true
box_id_34
name: "Kinda like a box but with rectangles"
shape: "cuboid"
belongs_to_project
project_01: true
With that, just one (deep) query on the boxes node will load all of the boxes that belong to project_01, which in this case is box_id_7 and box_id_34.
You could go the the other way and since you know the box id for each project in the projects node, you could do a series of observers to load in each project via it's specific path /boxes/box_id_7 etc. I like the query better; faster and less bandwidth.
You could expand on this if a box can belong to multiple projects:
box_id_14
name: "I have unequal lenghts"
shape: "Rhumboid"
belongs_to_project
project_01: true
project_37: true
Now query on the boxes node for all boxes that are part of project_01 will get box_id_7, box_id_14 and box_id_34.
Edit:
Once that structure is in place, use a Deep Query to then get the boxes that belong to the project in question.
For example: suppose you want to craft a Firebase Deep Query to return all boxes where the box's belongs_to_project list contains an item with key "project_37"
boxesRef.queryOrderedByChild("belongs_to_project/project_37"
.queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})
OK I think I'm happy with my approach, using Promise.all to respond once all the individual 'queries' are returned:
I've changed my approach to use promises, then call Promise.all() to indicate that all the data is ready to send.
const projectId = req.params.projectId;
const boxPromises = [];
const projectBoxesRef = db
.child('data/projects')
.child(projectId)
.child('boxes');
function getBox(boxSnapshot) {
boxPromises.push(db
.child('data/boxes')
.child(boxSnapshot.key())
.once('value')
.then(boxSnapshot => boxSnapshot.val())
);
}
projectBoxesRef.on('child_added', getBox);
projectBoxesRef.once('value', () => {
projectBoxesRef.off('child_added', getBox);
Promise.all(boxPromises).then(boxes => res.json(boxes));
});

Meteor Framework Subscribe/Publish according to document variables

I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.

What is the correct way to selectively publish in Meteor based on information in the database?

For instance, when your permissions are group-based, and your user document has a list of groups that the user belongs to. I'm publishing docs in an Items collection, and you should only be able to view items with a groupOwner field matching a group you belong to.
It would be nice if you could autorun inside a publish, but I doubt you can:
Meteor.publish 'screened-items', ->
Deps.autorun ->
user = Users.findOne #userId
return Items.find {groupOwner: {$in: user.groups}}
If you can't, this is the best I can come up with, but it's going to be slow and memory-intensive. Is this the only way to do it?
Meteor.publish 'screened-items', ->
user = Users.findOne #userId
# (hope that the db doesn't change between this line and the observeChanges)
saved_items = Items.find({groupOwner: {$in: user.groups}}).fetch()
# call #added on each item
handle = Users.findOne(#userId).observeChanges {
changed: (_, fields) =>
if fields.groups
new_items = Items.find({groupOwner: {$in: fields.groups}}).fetch()
# compare new_items to saved_items, and call #added() or #removed() for each difference
}
#ready()
#.onStop ->
handle.stop()
You can achieve this two ways:
Use the publish-with-relations package, for example:
Meteor.publish 'screend-items', ->
# select the current user
Meteor.publishWithRelations
handle: this
collection: Meteor.users
filter:
_id: #userId
options:
fields:
groups: 1
mappings: [
key: 'groupOwner' # and map to the `groupOwner` field on Items
collection: Items
]
Denormalize the relationship, providing a succinct list of users to use for publishing
Items._ensureIndex(userIds: 1) # best to index this field
# basic publications
Meteor.publish 'screend-items', ->
# don't expose `userIds` to the client
return Items.find({userIds: #userId}, {fields: userIds: false})
If you want the published docs to change when the userId changes, that is the default behaviour.
However, if the logged-in user changes, the publish function is rerun with the new value. - from docs.meteor.com.
Deps.autorun() only works on the client while Meteor.publish() only works on the server. So you can not autorun inside of publish.
If you are okay to let the client see the 'groups' they're in, the code is a bit simpler because you can start and stop the subscription when the groups change. Like this:
//on client
Deps.autorun( function() {
Meteor.subscribe( 'items', Meteor.user().groups );
});
//on server
Meteor.publish( 'items', function( groups ){
var self = this;
var user = Meteor.users.findOne( {_id: self.userId});
if ( ! (user && user.groups === groups) )
return;
return Items.find({groupOwner: {$in: groups}});
});
Otherwise you would need use two observers inside the publish function - one to watch user for changes to groups and another to manage publishing items that are in the group. See this example of doing a join of collections that way.

Resources