In Meteor Collections, use an array-field or another collection? - arrays

The question
In short, my question is: when an array in a document is changed, will the users receive the new array, or just the changes?
If that question is unclear, I've described my problem below.
The problem
I have a collection whose documents contain an array field two users will push values to. A document in this collection kind of looks like this:
var document = {
userId1: "...user id...", // The id of the first of the two users.
userId2: "...user id...", // The id of the second of the two users.
data: [] // The field the two users will push values to.
}
data will from the beginning be empty, and the users will then take turns pushing values to it.
When one of the user pushes some value to data, the server will send the changes to the second user. Will the second user receive the entire data-array, or just the changes (the pushed value)? I'm a little bit worried that the second user will receive the entire data-array, even though it's just a single value that's been pushed to it, and if data contains many values, I fear this will become a bottleneck.
Is this the case? If it is, using another collection for storing the values will solve it, right? Something like this:
var document = {
id: "...unique id...",
userId1: "...user id...", // The id of the first of the two users.
userId2: "...user id..." // The id of the second of the two users.
}
var documentData = {
idReference: "...the unique id in the document above...",
value: "...a value..."
}
Instead of pushing the values into an array in document, insert them into a collection containing documentData. This (I know) won't have the downside I fear the first solution has (but I rather use the first solution if it doesn't have the downside).

As per https://github.com/meteor/meteor/blob/master/packages/livedata/DDP.md
changed (server -> client):
collection: string (collection name)
id: string (document ID)
fields: optional object with EJSON values
cleared: optional array of strings (field names to delete)
Users will receive the new array. To only send "diffs," use a collection of {userId: userId, value: value} documents.

I inspected what was sent as commented by user728291, and it seems like the entire array-field is sent, and not just the pushed value. I don't know if this always is the case (I just tested with an array containing few and small values; if it contains many or big values Meteor maybe try to do some optimization I couldn't see in my tiny test), but I'll go with the solution using another collection instead of the array-field.

Related

Improvements to Recently Viewed Component in React

I have this small app I created using a REST Countries API.
https://rest-country-react.netlify.app/
If you click on one country card then it is displayed under the "Recently Viewed" header. So far it works fine, but I wanna tune it a little bit. What I thought I'd do:
#1 Add a limit of three recently viewed countries, so basically if the user clicks on 4,5,6 countries, only the three most recent clicked countries are displayed.
#2 Visited countries are currently sorted in an "oldest" to "newest" order. I wanna reverse that so the newest gets the first spot, then the second newest, then the third and so on.
I am stuck because I am not sure how to implement these tweaks. For the first one I thought I'd filter the state array before mapping it in the component, saying something like... if index > 2, filter it out the element.
But for the second, I haven't found a solution yet. Maybe instead of using concat() method, I should use unshift()? From what I read in the React documentation, it's not advised to directly edit the state or its array, so I don't know what to do.
onCountryClick(country) {
const uniqueRecent = [
...new Set(this.state.recentlyViewed.concat(country)),
];
this.setState({
// ... other state updates here
recentlyViewed: uniqueRecent
});
}
There are actually multiple solutions, but let's take the one which is very clean and understandable:
onCountryClick(country) {
const { recentlyViewed } = this.state;
const newState = {}; // add your other updates here
if (!recentlyViewed.includes(country)) {
// firstly take first two items as a new array
newState.recentlyViewed = recentlyViewed.slice(1);
// add country into beginning of new array
newState.recentlyViewed.unshift(country);
}
this.setState(newState);
}
Firstly we check if country already exists in the recentlyViewed array and if no - then continue.
We must use new array when updating the state, so simply calling unshift() method will not work for us as it modifies original array and doesn't return new one. Instead, we firstly call .slice() method which solves two main things for us: it takes a portion of original array (in our case items with index 0 and 1) and return new array with them. Great! Now we can easily use unshift to add country into beginning of new array. Then simply update the state.
With this solution you always get a new array with max of 3 countries, where the first item is the newest.

update data in Array of Structs with data in other Array of Struct

Let's say I have struct :
struct Planet {
var id : UUID
var name: String
...
}
I have an array of such structs which is constructed from data fetched from a database. I use this for a form in a browser where the user can:
edit the fields (eg change the name of Planet)
create one or more new Planets
at this time the user may not delete a Planet but it would be great if the solution would support that too
When the form is submitted I get an array with those structures (the order is also not the same as the original). What is the best/most efficient way to do update the data in the original array with the data from the second.
My current idea is:
map the original array to a dictionary with key= id, value= aPlanetStructure
loop over the second array (with the edited data) and if that 'key' can be retrieved in the dictionary (=data from first array)-> update the struct there, if not create an additional planet in the first array.
I'm not sure if this is a good approach, it seems like there could be a more efficient way (but I can't think of it). It would also not support deleting a Planet
In general, if you can separate out the elements of the array by action, you'll make your life easier.
For example:
var created= [Planet]()
var updated= [Planet]()
var deleted = [Planet]()
In your UI layer, when an edit is made, add the edited planet to the
updated array, when a planet is deleted, add it to the deleted array, etc.
Submit all 3 arrays with your form.
Loop over the results of each and pass too your create, update, and delete methods that access your database.
That will require restructuring your form code a bit, but... in general it's easier in your UI layer to tell whether someone is doing a create, an update, or a delete, than it is to mush them all together and try to figure it out after the fact by doing comparisons.

Populate two list with data

I currently have rest api call being done, with responseOne & responseTwo. I then add these response to a list casted to a wrapperclass. And this list is looped on to be displayed on vf page. Issue is that now I need 2 fields from the responseTwo to add these to my vf page. BOth response are linked with an ID with which data in the list can be linked. So from my responseTwo, i need number and Amount field to add this to my responseOne list to be displayed on vf page. I don't know how to achieve this based on adding the Amount to each specific data found in my responseOne.
So am trying to do looping in both datasets received.
for (Data.dataWrapper responseOne : listOne){
for(Data.dataWrapper responseTwo : listTwo){
if(responseOne.Id = responseTwo.id){
/* i need to add the amount field from responseTwo to this specific line of data at ID xxxx */
}
}
}
This is a bad pattern on performance grounds; it is of N*M complexity because you must iterate over every item in listTwo for every item in listOne. This will negatively impact your CPU limits and the responsivity of your interface.
Your wrapper class should typically include all of the data that is required to display a line item. It's not clear why these don't; you have not included any of that context in your question. Here, the most I can recommend is to digest these items into Map<Id, Data.dataWrapper> collections:
Map<Id, Data.dataWrapper> mapTwo = new Map<Id, Data.dataWrapper>();
for (Data.dataWrapper responseTwo : listTwo) {
mapOne.put(responseOne.Id, responseTwo);
}
Then, you can loop over listOne and access its corresponding listTwo entry directly, through the Map keyed on Id. This avoid the multiplicative complexity, because you end up iterating through each list exactly once, and can simply do:
mapTwo.get(responseOne.Id); // yields corresponding responseTwo entity.
You then have the data elements you need and can digest them to meet the specific needs of your Visualforce page.

How do I use the last value in an array as a path for .child() when retrieving a snapshot?

I'm new to Firebase and have a function that writes all of my event ID's to an array. I want to use the last value in that array (the last event ID) to lookup the children of that specific eventID.
I know how to get the last item in the array but how do I put that into my .child() path?
I tried the code below, but it doesn't seem to work. I'm guessing that because .child("(lastEvent)") isn't a valid path.
let lastEvent = eventIDArray.last
refHandle = ref.child("Bouts").child("\(lastEvent)")
How do I plug the lastEvent value in as my path? Or is that even possible? Again, total newbie- alternatives welcome.
Sorting and filtering data
you can use sorting and filtering function to get the item.
To get the last item you can write the query like this.**
let recentBoutsQuery = (ref?.child("Bouts").queryLimited(toLast: 1))!
This will return 1 entry from last of your database which the last entry.
You can learn more from the firebase documentation. Work with Lists of Data

Firebase Firestore: Append/Remove items from document array

I am trying to append/remove items from an array inside of a Firestore Document but every time the entire array is replaced instead of the new value being appended. I have tried both of the following:
batch.setData(["favorites": [user.uid]], forDocument: bookRef, options: SetOptions.merge())
batch.updateData(["favorites": [user.uid]], forDocument: bookRef)
I know that instead of an array I can use an object/dictionary but that would mean storing additional data that is irrelevant (such as the key), all I need is the ID's stored inside the array. Is this something that is currently possible in Firestore?
Update elements in an array
If your document contains an array field, you can use arrayUnion() and arrayRemove() to add and remove elements. arrayUnion() adds elements to an array but only elements not already present. arrayRemove() removes all instances of each given element.
let washingtonRef = db.collection("cities").document("DC")
// Atomically add a new region to the "regions" array field.
washingtonRef.updateData([
"regions": FieldValue.arrayUnion(["greater_virginia"])
])
// Atomically remove a region from the "regions" array field.
washingtonRef.updateData([
"regions": FieldValue.arrayRemove(["east_coast"])
])
See documentation here
Actually, nowadays it is possible. With latest updates db.collection.updateData
method actually appends new item to array instead of replacing it.
Example usage can be found in Firebase documentation.
If you need to do it manually, you can use
FieldValue.arrayUnion([user.uid])
Nope. This isn't possible.
Arrays tend to be problematic in an environment like Cloud Firestore where many clients could theoretically append or remove elements from an array at the same time -- if instructions arrive in a slightly different order, you could end up with out-of-bounds errors, corrupted data, or just a really bad time. So you either need to use a dictionary (where you can specify individual keys) or replace the entire array.

Resources