I'm building my first web app with React, Redux and Firebase, it's about registering the assistance of students (alumnos) to lessons (clases). I've the following database structure (the same in Redux state, this.props).
"clases" : {
"-LpQkLyGXEd-Up8hExTx" : {
"alumnos" : [ "-LpKkSh0E5jiuM0JCCpS", "-LpQi33M-0OSS4Jvup8k" ],
"fechaClase" : "20-09-2019",
"profesor" : "Nacho",
"tema" : "Misión"
},
"-LpQmExVsWtW1uPHLK52" : {
"alumnos" : [ "-LpJvbXb2FjgZvvBv3ei", "-LpKkSh0E5jiuM0JCCpS", "-LpQi33M-0OSS4Jvup8k", "-LpQiDGlRWITax2t6U2A" ],
"fechaClase" : "22-09-2019",
"profesor" : "Nacho",
"tema" : "Bautismo"
},
"-LpQqZ_uWu8HxROagVjN" : {
"alumnos" : [ "-LpKkSh0E5jiuM0JCCpS", "-LpQi33M-0OSS4Jvup8k", "-LpQiPCS2cIK7opMNqyH" ],
"fechaClase" : "21-09-2019",
"profesor" : "Manzo, Ignacio",
"tema" : "Bautismo"
}
I want to select a student (alumno), and know which lessons have made. Do I have to make a double map to the object? Can you give some help?
This is the deployed app https://metanoia-ic.herokuapp.com/
This is my github repository: https://github.com/tonicanada/metanoia
Thanks!
I would use Object.keys to be able to use filter and include Array.prototype methods. Asuming that clases is the object containing all the state, I would do something like this:
const studentId = this.props.studentId; // or any other value
const clasesPerStudent = Object.keys(clases).filter(clase =>
clases[clase].alumnos.includes(studentId)
);
Related
I have this database:
{
"interpreters" : [ null, {
"InterpreterID" : "1",
"Name" : "Pedro",
"languages" : [ "portuguese", "russian", "english", "german" ],
"latitude" : 37.633,
"latitudeDelta" : 0.0143,
"longitude" : -122.345,
"longitudeDelta" : 0.0134
}, {
"InterpreterID" : "2",
"Name" : "Paulo",
"languages" : [ "english", "portuguese" ],
"latitude" : 37.554,
"latitudeDelta" : 0.0143,
"longitude" : -122.245,
"longitudeDelta" : 0.0134
} ]
}
And I'm trying to get only interpreters that match with the language I want. For example, get only interpreters
that speak russian.
I'm doing like this:
database()
.ref()
.child('interpreters')
.orderByChild('languages')
.equalTo('russian')
.on('value', snapshot => {
setInterpreters(snapshot.val())
console.log(snapshot.val())
});
But it returns 'null'.
If I look for orderByChild('Name').equalTo('Pedro') then return Pedros data.
Maybe it's because languages is an array but I'm not sure how to get data from it and couldn't find how to do it.
The forEach method simple didn’t work without any error. The code ran fine but I couldn’t display anything.
I'm following firebase documentation and seems that there is nothing different.
What should I do ?
Thank you in advance.
Firebase doesn't natively support arrays, well it does in form of objects. An array that you send as
["Portuguese", "Russian"]
is converted to
{0: "Portuguese", 1: "Russian"}
Hence your queries don't work as expected. Firebase doesn't support querying inner values of arrays
The workaround
Firebase recommends changing your data to:
{
"portugese": true,
"russian": true
}
and then query as
.orderByChild('languages/portuguese')
.equalTo(true)
Here is my structure of realtime database in firebase
{
"student1" : {
"name" : "somename",
"skillset" : [
"cpp",
"c",
"java"
],
other properties
},
"student2" : {
"name" : "somename",
"skillset" : [
"javascript",
"c",
"python"
],
other properties
},
"student3" : {
"name" : "somename",
"skillset" : [
"cpp",
"java"
],
other properties
},
"student4" : {
"name" : "somename",
"skillset" : [
"java",
"kotlin"
],
other properties
} }
I want to retrieve all the students having some specific set of all skills
e.g. skills = ["cpp","java"]
then answer should be ["student1","student3"]
Your current structure allows you to easily determine the skills for a user. It does however not make it easy to determine the users for a skill. To allow that, you'll need to add a reverse index, looking something like:
skills: {
java: {
student1: true,
student3: true,
student4: true
},
kotlin: {
student4: true
}
...
}
With the above you can look up the user IDs for a skill, and from there look up each user. For more on this, see my answer here: Firebase query if child of child contains a value
But this still won't allow you to query for users by multiple skills. To allow that, you'll have to add skill combinations to the new data structure. For example with the above skills, there is one user who knows both kotlin and Java:
skills: {
java: {
student1: true,
student3: true,
student4: true
},
java_kotlin: {
student4: true
}
kotlin: {
student4: true
}
...
}
While this leads to extra data, it performs quite well in practice, since you can always directly access the data that you need (so there's no real database query needed).
That is not possible under this structure, as firebase only support filtering by one child.
In your case, you would need to get all the data and filter by code
I'm working on a React Native application with AppSync, and following is my schema to the problem:
type JoineeDeletedConnection {
items: [Joinee]
nextToken: String
}
type Mutation {
deleteJoinee(ids: [ID!]): [Joinee]
}
In 'request mapping template' to resolver to deleteJoinee, I have following (following the tutorial from https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-batch.html):
#set($ids = [])
#foreach($id in ${ctx.args.ids})
#set($map = {})
$util.qr($map.put("id", $util.dynamodb.toString($id)))
$util.qr($ids.add($map))
#end
{
"version" : "2018-05-29",
"operation" : "BatchDeleteItem",
"tables" : {
"JoineesTable": $util.toJson($ids)
}
}
..and in 'response mapping template' to the resolver,
$util.toJson($ctx.result.data.JoineesTable)
The problem is, when I ran the query, I got empty result and nothing deleted to database as well:
// calling the query
mutation DeleteJoinee {
deleteJoinee(ids: ["xxxx", "xxxx"])
{
id
}
}
// returns
{
"data": {
"deleteJoinee": [
null
]
}
}
I finally able to solve this puzzle, thanks to the answer mentioned here to point me to some direction.
Although, I noticed that JoineesTable does have trusted entity/role to the IAM 'Roles' section, yet it wasn't working for some reason. Looking into this more, I noticed that the existing policy had following actions as default:
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem"
]
Once I added following two more actions to the list, things have started working:
"dynamodb:BatchWriteItem",
"dynamodb:BatchGetItem"
Thanks to #Vasileios Lekakis and #Ionut Trestian on this appSync quest )
Implementing an Android+Web(Angular)+Firebase app, which has a many-to-many relationship: User <-> Widget (Widgets can be shared to multiple users).
Considerations:
List all the Widgets that a User has.
A User can only see the Widgets which are shared to him/her.
Be able to see all Users to whom a given Widget is shared.
A single Widget can be owned/administered by multiple Users with equal rights (modify Widget and change to whom it is shared). Similar to how Google Drive does sharing to specific users.
One of the approaches to implement fetching (join-style), would be to go with this advice: https://www.firebase.com/docs/android/guide/structuring-data.html ("Joining Flattened Data") via multiple listeners.
However I have doubts about this approach, because I have discovered that data loading would be worryingly slow (at least on Android) - I asked about it in another question - Firebase Android: slow "join" using many listeners, seems to contradict documentation .
So, this question is about another approach: per-user copies of all Widgets that a user has. As used in the Firebase+Udacity tutorial "ShoppingList++" ( https://www.firebase.com/blog/2015-12-07-udacity-course-firebase-essentials.html ).
Their structure looks like this:
In particular this part - userLists:
"userLists" : {
"abc#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
}
},
"xyz#gmail,com" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"listName" : "Test List 1 Rename 2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1456950573084
},
"timestampLastChanged" : {
"timestamp" : 1457044229747
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044229747
}
},
"-KByb0imU7hFzWTK4eoM" : {
"listName" : "List2",
"owner" : "xyz#gmail,com",
"timestampCreated" : {
"timestamp" : 1457044332539
},
"timestampLastChanged" : {
"timestamp" : 1457044332539
},
"timestampLastChangedReverse" : {
"timestamp" : -1457044332539
}
}
}
},
As you can see, the copies of shopping list "Test List 1 Rename 2" info appears in two places (for 2 users).
And here is the rest for completeness:
{
"ownerMappings" : {
"-KBt0MDWbvXFwNvZJXTj" : "xyz#gmail,com",
"-KByb0imU7hFzWTK4eoM" : "xyz#gmail,com"
},
"sharedWith" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"shoppingListItems" : {
"-KBt0MDWbvXFwNvZJXTj" : {
"-KBt0heZh-YDWIZNV7xs" : {
"bought" : false,
"itemName" : "item",
"owner" : "xyz#gmail,com"
}
}
},
"uidMappings" : {
"google:112894577549422030859" : "abc#gmail,com",
"google:117151367009479509658" : "xyz#gmail,com"
},
"userFriends" : {
"xyz#gmail,com" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
}
}
},
"users" : {
"abc#gmail,com" : {
"email" : "abc#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Agenda TEST",
"timestampJoined" : {
"timestamp" : 1456950523145
}
},
"xyz#gmail,com" : {
"email" : "xyz#gmail,com",
"hasLoggedInWithPassword" : false,
"name" : "Karol Depka",
"timestampJoined" : {
"timestamp" : 1456952940258
}
}
}
}
However, before I jump into implementing a similar structure in my app, I would like to clarify a few doubts.
Here are my interrelated questions:
In their ShoppingList++ app, they only permit a single "owner" - assigned in the ownerMappings node. Thus no-one else can rename the shopping list. I would like to have multiple "owners"/admins, with equal rights. Would such a keep-copies-per-user structure still work for multiple owner/admin users, without risking data corruption/"desynchronization" or "pranks"?
Could data corruption arise in scenarios like this: User1 goes offline, renames Widget1 to Widget1Prim. While User1 is offline, User2 shares Widget1 to User3 (User3's copy would not yet be aware of the rename). User1 goes online and sends the info about the rename of Widget1 (only to his own and User2's copies, of which the client code was aware at the time of the rename - not updating User3's copy). Now, in a naive implementation, User3 would have the old name, while the others would have the new name. This would probably be rare, but still worrying a bit.
Could/should the data corruption scenario in point "2." be resolved via having some process (e.g. on AppEngine) listening to changes and ensuring proper propagation to all user copies?
And/or could/should the data corruption scenario in point "2." be resolved via implementing a redundant listening to both changes of sharing and renaming, and propagating the changes to per-user copies, to handle the special case? Most of the time this would not be necessary, so it could result in performance/bandwidth penalty and complicated code. Is it worth it?
Going forward, once we have multiple versions deployed "in the wild", wouldn't it become unwieldy to evolve the schema, given how much of the data-handling responsibility lies with the code in the clients? For example if we add a new relationship, that the older client versions don't yet know about, doesn't it seem fragile? Then, back to the server-side syncer-ensurerer process on e.g. AppEngine (described in question "3.") ?
Would it seem like a good idea, to also have a "master reference copy" of every Widget / shopping-list, so as to give good "source of truth" for any syncer-ensurerer type of operations that would update per-user copies?
Any special considerations/traps/blockers regarding rules.json / rules.bolt permissions for data structured in such a (redundant) way ?
PS: I know about atomic multi-path updates via updateChildren() - would definitely use them.
Any other hints/observations welcome. TIA.
I suggest having only one copy of a widget for the entire system. It would have an origin user ID, and a set of users that have access to it. The widget tree can hold user permissions and change history. Any time a change is made, a branch is added to the tree. Branches can then be "promoted" to the "master" kind of like GIT. This would guarantee data integrity because past versions are never changed or deleted. It would also simplify your fetches... I think :)
{
users:[
bob:{
widgets:[
xxx:{
widgetKey: xyz,
permissions: *,
lastEdit...
}
]
}
...
]
widgets:[
xyz:{
masterKey:abc,
data: {...},
owner: bob,
},
...
]
widgetHistory:[
xyz:[
v1:{
data:{...},
},
v2,
v3
]
123:[
...
],
...
]
}
I'm trying to bind a Commerce product type to my own custom type node (serving as a display node). The goal is to enter new data in as few places as possible. I'm therefore exploring a rule-based creation of one type upon creation of the other. Seems like both directions are working. Of the two though, I prefer automatic creation of a Commerce Product upon user creation of Custom Type node, which will then serve as a product display.
I was wondering if anyone has been through this choice and could recommend this. Also, is the commerce_product_display_manager module necessary?
Commerce Product Display Manager is not necessary, I've gotten this to work and I've never used that module.
I went for the route of automatically creating a Node after saving the Product.
Below is my Rules export for this:
{ "rules_create_product_display" : {
"LABEL" : "Create Product Display",
"PLUGIN" : "reaction rule",
"REQUIRES" : [ "rules", "entity" ],
"ON" : [ "commerce_product_insert" ],
"IF" : [
{ "data_is" : { "data" : [ "commerce-product:type" ], "value" : "**PRODUCT_TYPE**" } }
],
"DO" : [
{ "entity_create" : {
"USING" : {
"type" : "node",
"param_type" : "**NODE_TYPE**",
"param_title" : "[commerce-product:title]",
"param_author" : [ "commerce-product:creator" ]
},
"PROVIDE" : { "entity_created" : { "entity_created" : "Created entity" } }
}
},
{ "data_set" : {
"data" : [ "entity-created:**PRODUCT_REFERENCE**" ],
"value" : [ "commerce-product" ]
}
}
]
}
}
You'll need to substitute your own values for:
PRODUCT_TYPE (product type that has been created)
NODE_TYPE (node type being created)
PRODUCT_REFERENCE (field that will reference the created product)
Sorry I can't dedicate more time to a better answer now, let me know if you'd like me to elaborate on the process of creating the above using the GUI
The above example was useful but here is a more specific one:
{ "rules_create_product_display_on_product_creation" : {
"LABEL" : "Create Product Display on Product creation",
"PLUGIN" : "reaction rule",
"REQUIRES" : [ "rules", "entity" ],
"ON" : [ "commerce_product_insert" ],
"IF" : [
{ "entity_is_of_type" : { "entity" : [ "commerce-product" ], "type" : "commerce_product" } }
],
"DO" : [
{ "entity_create" : {
"USING" : {
"type" : "node",
"param_type" : "product_display",
"param_title" : "[commerce-product:title]",
"param_author" : [ "commerce-product:creator" ]
},
"PROVIDE" : { "entity_created" : { "entity_created" : "Created entity" } }
}
},
{ "data_set" : {
"data" : [ "entity-created:field-product:0" ],
"value" : [ "commerce-product" ]
}
}
]
}
}
The only problem I had was with the second action ("data_set")- it was important to select "entity-created:field-product:0", not the "entity-created:field-product" to make it work because we want to assign specific product and not a list of products.
This example is using the standard product display node type (product_display) but you can change it with the one you are using. Also have in mind that this is working only for one product type - for every product type a separated rule should be created. You may create also a rule for deleting the product display node when deleting the product.
This rule is useful only when you have connection one product-one product display. If you need to add more products per product display (colors, images with different prices) then you have to use Commerce Bulk Product Creation module.