I am working on a application in which a ship can be configured using rudders and other stuff. The database structure is sort of nested, and so far I have been keeping my GraphQL queries in correspondence with the database.
That means: I could fetch a ship using some query ship(projectId, shipId), but instead I am using a nested query:
query {
project(id:1) {
id
title
ship(id:1) {
id
name
rudders {
id
position
}
}
}
}
Such a structure of course leads to a lot of nested arrays. For example, if I have just added a new rudder, I would have to retrieve using cache.readQuery, which gives me the project object rather than the rudder list. To add the rudder to the cache, I'd get a long line with nested, destructured objects, making the code hard to read.
So I thought of using GraphQL fragments. On the internet, I see them being used a lot to prevent having to re-type several fields on extensive objects (which I personally find very useful as well!). However, there are not so many examples where a fragment is used for an array.
Fragments for arrays could save all the object destructuring when appending some data to an array that is nested in some cached query. Using Apollo's readFragment and writeFragment, I managed to get something working.
The fragment:
export const FRAGMENT_RUDDER_ARRAY = gql`
fragment rudderArray on ShipObject {
rudders {
id
position
}
}
`
Used in the main ship query:
query {
project(id: ...) {
id
title
ship(id: ...) {
id
name
...rudderArray
}
}
}
${RUDDER_FRAGMENT_ARRAY}
Using this, I can write a much clearer update() function to update Apollo's cache after a mutation. See below:
const [ createRudder ] = useMutation(CREATE_RUDDER_MUTATION, {
onError: (error) => { console.log(JSON.stringify(error))},
update(cache, {data: {createRudder}}) {
const {rudders} = cache.readFragment({
id: `ShipObject:${shipId}`,
fragment: FRAGMENT_RUDDER_ARRAY,
fragmentName: 'rudderArray'
});
cache.writeFragment({
id: `ShipObject:${shipId}`,
fragment: FRAGMENT_RUDDER_ARRAY,
fragmentName: 'rudderArray',
data: {rudders: rudders.concat(createRudder.rudder)}
});
}
});
Now what is my question? Well, since I almost never see fragments being used for this end, I find this working well, but I am wondering if there's any drawbacks to this.
On the other hand, I also decided to share this because I could not find any examples. So if this is a good idea, feel free to use the pattern!
Related
I have a set of related items like so:
book {
id
...
related_entity {
id
...
}
}
which apollo caches as two separate cache objects, where the related_entity field on book is a ref to an EntityNode object. This is fine, the related entity data is also used elsewhere outside of the context of a book so having it separate works, and everything seems well and good and updates as expected...except in the case where the related entity does not exist on the initial fetch (and thus the ref on the book object is null) and I create one later on.
I've tried adding an update function to the useMutation hook that creates the aforementioned related_entity per their documentation: https://www.apollographql.com/docs/react/caching/cache-interaction/#example-adding-an-item-to-a-list like this:
const [mutateEntity, _i] = useMutation(CREATE_OR_UPDATE_ENTITY,{
update(cache, {data}) {
cache.modify({
id: `BookNode:${bookId}`,
fields: {
relatedEntity(_i) {
const newEntityRef = cache.writeFragment({
fragment: gql`
fragment NewEntity on EntityNode {
id
...someOtherAttr
}`,
data: data.entityData
});
return newEntityRef;
}
}
})
}
});
but no matter what I seem to try, newEntityRef is always undefined, even though the new EntityNode is definitely in the cache and can be read just fine using the exact same fragment. I could give up and just force a refetch of the Book object, but the data is already right there.
Am I doing something wrong/is there a better way?
Barring that is there another way to get a ref for a cached object given you have its identifier?
It looks like this is actually an issue with apollo-cache-persist - I removed it and the code above functions as expected per the docs. It also looks like I could instead update to the new version under a different package name apollo3-cache-persist, but I ended up not needing cache persistence anyway.
Let's say I have 2 models.
Post
Author
The Author looks like below:
{
authorId: 'ghr4334t',
fullName: 'This is a post!'
Nickname: 'Avola
}
The Post looks like below and will have a reference to the author like below:
{
postId: '12fdc24',
authorId: 'ghr4334t',
content: 'This is a post!'
}
Currently, when a user clicks on a post, in order to show all the relevant information, I load the data as follow:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
// update state so I have the post object and author object.
})
})
So the above, I load the post, then I load the author. Once I've loaded them both, I can finally construct a custom object:
const finalPost = {
author: { ...this.state.authorData },
post: { ...this.state.postData }
}
Naturally..If I have a couple more fields that reference other collections, there will be a nest of get and .then() calls like below:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
getSomethingElse(listing.uid).then((document) => {
getAnother(listing.uid).then((document) => {
// finally update state with everything.
})
})
})
})
Is there a more a better way to load related information together without having to stack .then() calls?
Unfortunately, there isn't a better way to achieve what you want, with queries directly. Queries in Firestore doesn't provide you with many options on how to query and return data, mainly, when you would need to do any kind of JOIN on them, to search via references, which makes the work not very easy. I believe the way you are doing is the best option you have for more now.
An alternative you can try is to have Subcollections, where you will have a subcollection of Author inside your collection Post. This way, you will only treat with the reference of the Post, since the Author will be within the document of each specific Post. This way, the queries would be more simple, looking like this below. Of course, this would require you to modify your database.
var messageRef = db.collection('Post').doc('Post1')
.collection('Author').doc('Author1');
In case you still think this is not enough, I would recommend you to raise a Feature Request at Google's System, where the Google Developers will be able to check if having a new way of getting data is possible to be implemented.
Let me know if the information clarified your doubts!
I'm trying to query my Firebase Realtime Database to find all games a user belongs to.
I have a list of games, each game has a property of players. Each child of players has a key of $uid, and within that {key: $uid, name: "joe"}
Is it possible to get all games this way? Or do I need to start keeping another index of players_games/$uid/$game?
I've tried firebase.database().ref('games').orderByChild('players').equalTo(token.uid), but this yields null
It looks like database.ref('games').orderByChild('players/${token.uid}') works, but then I'd need to give .read access to all of games, or do this server-side.
Your current data structure makes it easy to find all the users for a specific game. It does not however make it easy to find all the games for a specific user. To allow that, you'll want to add an addition data structure that inverts the information.
So that'd look something like this:
player_games: {
"XDYNyN8il6TDsM4LuttwDzNuytj1": {
"-M5vf...U5zK": true
},
"NxH14...mxY2": {
"-M5vf...U5zK": true
}
}
Also see:
Firebase query if child of child contains a value
Firebase Query Double Nested
I recommend you also study the Firebase documentation on structuring your database, specifically the section on avoiding nested data. By mixing entity types as you currently do, you'll likely run into problems with security, and scalability.
The most idiomatic way to model your many-to-many relationship in the Firebase database is with four top-level lists:
players: {
$playerId: { ... }
}
games: {
$gameId: { ... }
}
player_games: {
$playerId: {
$gameId: true
}
}
game_players: {
$gameId: {
$playerId: true
}
}
Also see:
Many to Many relationship in Firebase
I am using node.js with bookshelf as an ORM. I am a serious novice with this technology.
I have a situation where I have several columns in a database table. For the sake of this question, these columns shall be named 'sold_by_id', 'signed_off_by_id' and 'lead_developer_id', and are all columns that will reference a User table with an ID.
In other words, different User's in the system would at any point be associated with three different roles, not necessarily uniquely.
Going forward, I would need to be able to retrieve information in such ways as:
let soldByLastName = JobTicket.soldBy.get('last_name');
I've tried searching around and reading the documentation but I'm still very uncertain about how to achieve this. Obviously the below doesn't work and I'm aware that the second parameter is meant for the target table, but it illustrates the concept of what I'm trying to achieve.
// JobTicket.js
soldBy: function() {
return this.belongsTo(User, 'sold_by_id');
},
signedOffBy: function() {
return this.belongsTo(User, 'signed_off_by_id');
},
leadDeveloper: function() {
return this.belongsTo(User, 'lead_developer_id');
}
Obviously I would need a corresponding set of methods in User.js
I'm not sure where to start, can anyone point me in the right direction??
Or am I just a total idiot? ^_^
Your definitions look right. For using them it will be something like:
new JobTicket({ id: 33 })
.fetch({ withRelated: [ 'soldBy', 'signedOffBy' ] })
.then(jobTicket => {
console.log(jobTicket.related('soldBy').get('last_name');
});
Besides that I would recommend you to use the Registry plugin for referencing other models. That eases the pains of referencing models not yet loaded.
I wish to create a reporting employee structure, I am using internal keys generated by the push function as EID's and will be nesting reporting employees EID under the reported employees EID. So for Eg. if a manager has a EID: -KbAGV6Uhg0BKK31HduN and a reporting engineer has a EID: -Kb9ioY8dkBEOO46uX7t the Json structure would look like
{
"-KbAGV6Uhg0BKK31HduN " : {
"reportingID" : "-Kb9ioY8dkBEOO46uX7t"
}
}
but because I am using the push function it looks something like this:
{
"-KbAGV6Uhg0BKK31HduN" : {
"-KbAH1RiVsz5FpSFudD2" : {
"reportingID" : "-Kb9ioY8dkBEOO46uX7t"
}
}
}
I don't require this extra key(-KbAH1RiVsz5FpSFudD2)what can be done to avoid it or have something else like E1 or someother easy identifier in its place as the key is making the structure too complicated
What you have described is the Firebase way to handle things if you want to use push.
Instead of pushing, you could use the set method to do something like this:
ManagerID/directReports/EngineerID
where EngineerID could contain some additional info about the engineer or some key:value pair that would be useful to have in the app.
The JSON structure would look like this:
"ManagerGUID":{
"directReports": {
"EngineerGUID":{
"someinfokey":"somevalue"
}
}
}
That way, if you want a list or iterable, you can just download the ManagerID/directReports node and have individual objects using the original key for the engineers. It might make some extra working in transforming to an array, but it sounds like this is kind of what you want to do. Let me know if I'm off base and I can update my answer.
It is not a best practice but you can generate a global unique id for each entry instead of Firebase generated uid.
Have a look a one of id generator here: https://gist.github.com/mikelehen/3596a30bd69384624c11