I'm having some trouble structuring my database.
Currently I have users and groups structured as following:
-groups
-groupId
-groupname: "...."
-users
-userId: true
-users
-userId
-username: "..."
-groups
-groupId: true
Now I want to add variables that are dependent on both groups and user. I tried making another tree called groupUser containing both a groupId and userId, but it's hard to query on two conditions with Firebase. So what would be the best way to structure the database?
Thanks in advance!
I would structure your data like so:
{
groups: {
groupIdOne: {
groupname: "...",
users: {
userIdOne: true
}
}
},
users: {
userIdOne: {
username: "...",
groups: {
groupIdOne: true
}
}
},
groupUsers: {
groupIdOne: {
userIdOne: {
//data for group users
}
}
},
//and/or
userGroups: {
userIdOne: {
groupIdOne: {
//data for user groups, same as above
}
}
}
}
This way if you have a group id and a user id you just query that node directly by doing something like .child('groupUsers').child(groupId).child(userId)
Related
Using Firestore Security rules, I was wondering if there is anyway to directly view a parent document using path? For example, for any path in a subcollection, I would want to do the following:
match /myRootCollection/{myRootDoc=**} {
allow write: if request.path.split('/')[0] === "myRootDoc1":
}
I understand I can also just have the reference of myRootDoc1 in the subcollection's documents as so:
{
myRootCollection: {
{myRootDoc1}: {
mySubCollection: {
{mySubCollectionDoc1}: {
name: "someUser0",
parent: "myRootDoc1",
},
{mySubCollectionDoc2}: {
name: "someUser1",
parent: "myRootDoc1",
},
{mySubCollectionDoc3}: {
name: "someUser2",
parent: "myRootDoc1",
},
}
{myRootDoc2}: {
mySubCollection: {
{mySubCollectionDoc1}: {
name: "someUser3",
parent: "myRootDoc2",
},
{mySubCollectionDoc2}: {
name: "someUser4",
parent: "myRootDoc2",
},
{mySubCollectionDoc3}: {
name: "someUser5",
parent: "myRootDoc2",
},
}
}
}
}
Here, I would be able to read the mySubCollectionDocs if they are under a specific parent. The above works because I have the field directly in each doc, but is it possible to just read a mySubCollectionDoc and get its parent using path from Firestore Security without the specified field?
If I am able to, how would I able to query using node.js (as Firebase protects against any potential leaky queries)?
Thanks in advance.
Try this:
match /myRootCollection/{myRootDoc}/mySubCollection/{id} {
allow write: if myRootDoc == "xxx";
}
I just want to Publish the relational Data for a Publication to client, but the issue is my Relational Data field is array of ID's of a Different Collection, I tried Different Packages but all works with single Relational ID but not working with Array of relational ID's, let assume I have two Collection Companies and Meteor.users below is my Company Document Looks like
{
_id : "dYo4tqpZms9j8aG4C"
owner : "yjzakAgYWejmJcuHz"
name : "Labbaik Waters"
peoples : ["yjzakAgYWejmJcuHz", "yjzakAgYWejmJcuHz"],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
here you can see peoples field contains the user ID's as Array, so How I publish this userId's as user Documents, as for example I tried the most popular meteor package named publishComposit, when I tried Loop in Children's find, I got undefined in children i.e below
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
find(company) {
let cursors = company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
and if I implement async loop in children's find I got error like below code
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
async find(company) {
let cursors = await company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
the error occured in above code is Exception in callback of async function: TypeError: this.cursor._getCollectionName is not a function
I don't know what I am exactly doing wrong here, or implementing package function not as intended any help will be greatly appropriated
EDIT: my desired result should be full user documents instead of ID no matter it mapped in same peoples array or as another fields I just want as below
{
_id: "dYo4tqpZms9j8aG4C",
owner: "yjzakAgYWejmJcuHz",
name: "Labbaik Waters",
peoples: [
{
profile: {firstName: "Abdul", lastName: "Hameed"},
_id: "yjzakAgYWejmJcuHz"
}
],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
I ran into a similar problem couple of days ago. There are two problems with the provided code. First, using async; it's not needed and rather complicates things. Second, publishComposite relies on receiving one cursor not multiple within its children to work properly.
Below is a snippet of the code used to solve the problem I had, hopefully you can replicate it.
Meteor.publishComposite("table.conversations", function(table, ids, fields) {
if (!this.userId) {
return this.ready();
}
check(table, String);
check(ids, Array);
check(fields, Match.Optional(Object));
return {
find() {
return Conversation.find(
{
_id: {
$in: ids
}
},
{ fields }
);
},
children: [
{
find(conversation) {
// constructing one big cursor that entails all of the documents in one single go
// as publish composite cannot work with multiple cursors at once
return User.find(
{ _id: { $in: conversation.participants } },
{ fields: { profile: 1, roles: 1, emails: 1 } }
);
}
}
]
};
});
With a structure of
/archive: {
$userId: {
$archiveKey: {
foo: 1
},
...
},
...
}
Where $userId references a user id and $archiveKey is dynamic, created by .push().
Is it possible to query the archive ref and get all archiveObjects where foo = 1 ? Or do I need to fetch down the whole table, manually dig into $userId and extract the archiveObjects I'm looking for?
Firebase queries can nowadays query nested paths, but the paths cannot be dynamic.
So if you know the uid, you can query that user's archives with:
ref.child(authData.uid).orderByChild('foo').equalTo(1).on(...
If you don't know the uid, then you'll have to create a data structure that allows you to do the lookup:
archive_category_to_uids: {
foo: {
1: {
uid1: true,
uid2: true
}
}
}
A more common way is to separate the archives into their own top-level list and have both users and categories refer to that:
users: {
userId1: {
archiveKey1: true,
...
},
...
},
archives: {
archiveKey1: {
foo: 1,
uid: uid1
},
...
},
archiveCategories: {
foo: {
1: {
archiveKey1: true,
archiveKey2: true
}
}
}
Now you can get find the archives with:
ref.child('archiveCategories/foo/1').once('value', function(keys) {
keys.forEach(function(key) {
ref.child('archives').child(key.key()).once('value', function(snapshot) {
console.log(snapshot.val());
});
};
});
This process is called denormalization and is quite common in NoSQL databases. You're modeling the data for how your application needs to consume it. For more on this and other common patterns, I recommend reading this article on NoSQL data modeling.
My Firebase app contains a many-to-many relationship between Groups and Users. I'd like to store information about when a user joined a group - how do I go about that without the concept of a join table?
I'm using this Firebase example as a reference:
https://examples-k9xbyc0bhfwtlkdgfhs.firebaseio-demo.com/
Do you recommend doing something like this?
app: {
groups: {
group1: {
members: {
user1: {
joinDate: '2016-01-22T02:43:27.817Z',
isCreator: true
},
user2: {
joinDate: '2016-01-23T02:43:27.817Z',
isCreator: false
}
}
}
},
users: {
user1: {
firstName: 'Adam',
lastName: 'Soffer',
groups: {
group1: {
joinDate: '2016-01-22T02:43:27.817Z',
isCreator: true
}
}
},
user2: {
firstName: 'Joe',
lastName: 'Shmoe',
groups: {
group1: {
joinDate: '2016-01-23T02:43:27.817Z',
isCreator: false
}
}
}
}
}
The official structuring data guide is a great resource for helping with conceptual design like this.
In general, it's best to use nested data sparingly and to flatten data where possible.
You can represent the same characteristics with flatter structures.
membership: {
user1: {
group1: { joined: '2016-01-23T02:43:27.817Z' },
group2: { joined: '2016-01-23T02:43:27.817Z' }
}
},
groups: {
group1: {
creator: user1
},
group2: {
creator: user1
}
},
users: {
user1: {
firstName: 'Adam',
lastName: 'Soffer'
}
}
We've used a membership object to manage the many-to-many relationship of users and groups, allowing us to remove a lot of the nesting in the groups and users fields.
Assuming each group only has one creator, it's also possible to move this into the group objects, giving us a guarantee that there can only be one creator and removing some denormalized data from our user objects.
I'm using Redux and Normalizr.
I have a state which looks like:
{
entities: {
users: {
1: {...},
2: {...}
}
},
data: {
users: [1,2]
}
}
One of my API endpoints returns an object that includes a single user and some other data:
data = {
user: {id: 3, ...}
otherstuff: {...}
}
const user = new Schema('users');
normalize(data, {user: user})
This returns
{
entities: {
users: {
3: {id: 3, ...}
}
},
result: {
user: 3
}
}
However my data reducer expects to merge in a users array (of ids):
function data(state = {}, action) {
if (action.response && action.response.result) {
return deepmerge(state, action.response.result)
}
return state
}
Ideally I'd address this issue in the normalisation process rather than changing the reducer. Is there an easy way to either get normalizr to parse {user: {id: 3}} into {result: {users: [3]}}? It already changes the entities key to users.
If not, is there a clean and generic reducer-level solution to having this problem across a variety of entity type names?