Internally, how does relay cache pagination for subqueries? - relayjs

Let's say I have a blog:
1 blog has many posts
1 post has many likers
1 post has many comments
1 comment has many likers
On the front page of my blog, I'd like to show the top 10 posts. For each post, I'd like to show the 10 most recent likers, the 5 most recent comments, and the 10 most recent people who liked each comment. (Numbers aren't important, I'm just setting up something similar to facebook).
So my query might look something like this:
query getPosts(
$postCount: Int,
$likersCount: Int,
$commentCount: Int,
$commentCursor: ID,
$commentLikersCount: Int) {
recentPosts(first: $postCount) {
id,
title,
body,
likers(first: $likersCount) {
id,
name
},
comments(first: $commentCount, after: $commentCursor) {
id,
title,
body,
likers(first: $commentLikersCount) {
id,
name
},
}
}
}
If I resubmit this query with a new $commentCursor to load more comments, how does relay cache the data so it knows to grab everything else locally? I get the basic graph architecture of the store, but for nested things like this I get confused in the debugger.

Normally you would extend the paginated range by changing the first: $commentCount argument, and you wouldn't use the after argument. Just say you change $commentCount from 5 to 10, Relay knows to get the the items at positions 6 through 10, as it already has the ones from 1 through 5. Specifically, you'll see it issue an first: 5, after: <cursor> query, where <cursor> is the cursor of the 5th comment (Relay will automatically get cursors for every edge in the connection for this purpose, even if you don't ask for them explicitly).

Related

How to structure data in Firestore using swift [duplicate]

The documentation does not have any examples on how to add a subcollection to a document. I know how to add document to a collection and how to add data to a document, but how do I add a collection (subcollection) to a document?
Shouldn't there be some method like this:
dbRef.document("example").addCollection("subCollection")
Edit 13 Jan 2021:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
Assuming we have a chat application that has a database structure that looks similar to this:
To write a subCollection in a document, please use the following code:
DocumentReference messageRef = db
.collection("rooms").document("roomA")
.collection("messages").document("message1");
Creating a messages collection and calling addDocument() 1000 times will be expensive for sure, but this is how Firestore works. You can switch to Firebase Realtime Database if you want where the number of writes doesn't matter. But regarding Supported Data Types in Firestore, in fact, you can use an array because it is supported. In Firebase Realtime database you could also use an array, but this is an anti-pattern. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
Cloud Firestore can store arrays, but it does not support querying array members or updating single array elements. However, you can still model this kind of data by leveraging the other capabilities of the Cloud Firestore. Here is the documentation where it is very well explained.
You also cannot create a subcollection with 1000 messages, add all of them to the database, and expect it to be considered a single record. It will be considered one write operation for every message, in total 1000 operations. The picture above does not show how to retrieve data, it shows a database structure in which you have something like this:
collection -> document -> subCollection -> document
Here's a variation where the subcollection is storing ID values at the collection level, rather than within a document where the subcollection is a field there with additional data.
This is useful for connecting a 1-to-Many ID mapping w/out having to drill through an additional document:
function fireAddStudentToClassroom(studentUserId, classroomId) {
var db = firebase.firestore();
var studentsClassroomRef =
db.collection('student_class').doc(classroomId)
.collection('students');
studentsClassroomRef
.doc(studentUserId)
.set({})
.then(function () {
console.log('Document Added ');
})
.catch(function (error) {
console.error('Error adding document: ', error);
});
}
Thanks to #Alex's answer
This answer a bit off from the original question here, where it explicitly asks for adding a collection to a document. However, after searching for a solution for this scenario and not finding any mention in docs or on SO, this post seems like a reasonable place to share the findings
Here's my code:
firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).set(wordData)
.then(function() {
console.log("Collection added to Firestore!");
var promises = [];
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('AudioSources').doc($scope.accentDialect).set(accentDialectObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('FunFacts').doc($scope.longLanguage).set(funFactObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('Translations').doc($scope.translationLongLanguage).set(translationObject));
Promise.all(promises).then(function() {
console.log("All subcollections were added!");
})
.catch(function(error){
console.log("Error adding subcollections to Firestore: " + error);
});
})
.catch(function(error){
console.log("Error adding document to Firestore: " + error);
});
This makes a collection EnglishWords, which has a document of. The document of has three subcollections: AudioSources (recordings of the word in American and British accents), FunFacts, and Translations. The subcollection Translations has one document: Spanish. The Spanish document has three key-value pairs, telling you that 'de' is the Spanish translation of 'of'.
The first line of the code creates the collection EnglishWords. We wait for the promise to resolve with .then, and then we create the three subcollections. Promise.all tells us when all three subcollections are set.
IMHO, I use arrays in Firestore when the entire array is uploaded and downloaded together, i.e., I don't need to access individual elements. For example, an array of the letters of the word 'of' would be ['o', 'f']. The user can ask, "How do I spell 'of'?" The user isn't going to ask, "What's the second letter in 'of'?"
I use collections when I need to access individual elements, a.k.a. documents. With the older Firebase Realtime Database, I had to download arrays and then iterate through the arrays with forEach to get the element I wanted. This was a lot of code, and with a deep data structure and/or large arrays I was downloading tons of data that I didn't need, and slowing my app running forEach loops on large arrays. Firestore puts the iterators in the database, on their end, so that I can request a single element and it sends me just that element, saving me bandwidth and making my app run faster. This might not matter for a web app, if your computer has a broadband connection, but for mobile apps with poor data connections and slow devices this is important.
Here are two pictures of my Firestore:
From the docs:
You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists.
Here i faced the same issue and solve with the answere of #Thomas David Kehoe
db.collection("First collection Name").doc("Id of the document").collection("Nested collection Name").add({
//your data
}).then((data) => {
console.log(data.id);
console.log("Document has added")
}).catch((err) => {
console.log(err)
})
too late for an answer but here is what worked for me,
mFirebaseDatabaseReference?.collection("conversations")?.add(Conversation("User1"))
?.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.id)
mFirebaseDatabaseReference?.collection("conversations")?.document(documentReference.id)?.collection("messages")?.add(Message(edtMessage?.text.toString()))
}?.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
add success listener for adding document and use firebase generated ID for a path.
Use this ID for the complete path for a new collection you want to add.
I.E. - dbReference.collection('yourCollectionName').document(firebaseGeneratedID).collection('yourCollectionName').add(yourDocumentPOJO/Object)
Okay so I recently faced a similar problem given the recent update in the firebase/firestore documentation.
And here is a solution that worked for me
const sendMessage = async () => {
await setDoc(doc(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME, nanoid()), {
text:'this is a sample text',
createdAt: serverTimestamp(),
name: currentUser?.firstName + ' ' + currentUser?.lastName,
photoUrl: currentUser?.photoUrl,
userId: currentUser?.id,
});
}
You can find a similar example in the docs
https://firebase.google.com/docs/firestore/data-model#web-version-9_3
chat room
If you wish to listen for live update you can use a similar method as follows
const messagesRef = collection(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME)
const liveUpdate = async () => {
const queryObj = query(messagesRef, orderBy("createdAt"), limit(25));
onSnapshot(queryObj, (querySnapshot) => {
const msgArr: any = [];
querySnapshot.forEach((doc) => {
msgArr.push({ id: doc.id, ...doc.data() })
});
console.log(msgArr);
});
}
There is no separate method to add sub-collection into the document.
You can just call the collection method itself.
If the collection exists it will reference that otherwise create a new one.
dbRef.document("example").collection("subCollection")

How to examine and change actions on commercetools updateCart query?

My question is this:
Is there a feature or method within graphql that allows editing of incoming queries EASILY so that I can prevent an action from being performed? I would like to examine my query in a web proxy, edit the actions, and then send it on to its destination.
Context:
I have a mutation running in a proxy service for commercetools that looks like this:
mutation AddLineItem(
$id: String
$version: Long!
$sku: String
$quantity: Long
$currencyCode: Currency!
$centAmount: Long!
$custom: String!
) {
updateCart(
version: $version
id: $id
actions: [
{
addLineItem: {
sku: $sku
quantity: $quantity
externalPrice: { centPrecision: { currencyCode: $currencyCode, centAmount: $centAmount } }
custom: { typeKey: "custom-type", fields: [{ name: "field", value: $custom }] }
}
}
]
) {
id
version
lineItems {
...LineItemFieldsCart
}
}
}
According to the commercetools documentation, when the external price field is set, duplicate items will be added as separate line items to the cart instead of increasing the quantity, as is the default behavior. The thing is, I want the default behavior instead of the duplicate line items. The normal way to mitigate this is to add an update extension to delete the duplicates, but I am working on an already mature system that has multiple update extensions that all operate on the line items, making a delete apparatus a heavy lift in the update extension. I want to edit the query in the proxy so the duplicate item never happens in the first place. Is there an easy way to do this?
for the add line item update action the current behaviour is to create a separate line item when external prices are used and not add the additional quantity to the already existing line item as you described.
If you want to make sure that the quantity is added to the already existing line item when using external prices, you could do a check prior to adding a new line item and use the update action change line item quantity in case the product variants match with the line items that already exist in the cart. This will let you set the external price there and will not add any additional like items. https://docs.commercetools.com/api/projects/carts#change-lineitem-quantity

Sales order total different with actual total

Just need to know any one of you experiencing this issue with sales order document in acumatica ERP 4.2,
The header level total is wrong when compared to the total of lines. Is there any way we can recalculate the totals in code as i couldn't find fix from acumatica yet?
If document is not yet closed, you can just modify qty or add/remove line.
If document is closed i do not see any possible ways except changing data in DB.
I am adding my recent experience to this topic in hopes it might help others.
Months ago, I wrote the code shown below anticipating its need when called by RESTful services. It was clearly not needed, and even worse, merely written and forgotten...
The code was from a SalesOrderEntryExt graph extension.
By removing the code block, the doubling of Order Total was resolved.
It's also an example of backing out custom code until finding the problem.
protected void _(Events.RowInserted<SOLine> e, PXRowInserted del)
{
// call the base BLC event handler...
del?.Invoke(e.Cache, e.Args);
SOLine row = e.Row;
if (!Base.IsExport) return;
if (row != null && row.OrderQty > 0m)
{
// via RESTful API, raise event
SOLine copy = Base.Transactions.Cache.CreateCopy(row) as SOLine;
copy.OrderQty = 0m;
Base.Transactions.Cache.RaiseRowUpdated(row, copy);
}
}

Post a proudct with MWS api and Ruby on Rails

I'm trying to upload a new product to mws with the mws api and mws gem
The product is added (like Fix failed listings, because, doen't have a quantity and price)
I'm trying with the next code:
mws = Mws.connect(
merchant: 'merchant',
access: 'access',
secret: 'secret'
)
Later:
product = Mws::Product('11333663') { upc '1234355462233' tax_code
'GEN_TAX_CODE' name 'Some Pduct 034' brand 'Some Bnd' msrp 18.9,
'USD' quantity 10 manufacturer 'Some Mufacturer' category :ce
details {
cable_or_adapter {
cable_length as_distance 5, :feet
} } }
later:
submission_id = mws.feeds.products.add(product)
The product is added, but when I excuted this line:
submission_id = mws.feeds.products.update(product)
The next message is displayed:
=> #<Mws::Apis::Feeds::SubmissionResult:0x9d9ae78 #transaction_id="12345678", #status=#<Mws::EnumEntry:0x9d96170
#sym=:complete, #val="Complete">, #messages_processed=0,
#counts={:success=>0, :error=>1, :warning=>0},
#responses={:"0"=>#, #code=90208, #description="Purge
and replace is not allowed for this feed type.">}>
2.0.0-p195 :050 > result = mws.feeds.get(submission_id.id)
=> #, #messages_processed=1,
#counts={:success=>0, :error=>1, :warning=>0},
#responses={:"0"=>#, #code=90000,
#description="http://sellercentral.amazon.com/myi/search/ErrorListingsSummary?batchId=7564766086">,
:"1"=>#, #code=99042, #description="A
value was not provided for \"item_type\". Please provide a value for
\"item_type\". Please use the Product Classifier or download the
category-specific Browse Tree Guide from Seller Help to see a list of
valid \"item_type\" values. This information tells Amazon where your
product should be classified and affects how easily customers can find
your product.", #additional_info={:sku=>"11333668"}>}>
But, when I tried update the inventory and the price, the follow error ocurred:
result = mws.feeds.get(price_submission_id.id) =>
#sym=:complete, #val="Complete">, #messages_processed=0,
#counts={:success=>0, :error=>1, :warning=>0},
#responses={:"0"=>#, #code=90208, #description="Purge
and replace is not allowed for this feed type.">}>
What can I do?
Without any indepth knowledge of that Ruby Gem (and of Ruby), I can probably still point you in the right direction:
In MWS, feeds automatically update information already in the Amazon database. The call to create a record is identical to a subsequent call to update it. That also means you don't have to keep track of which items were already added to Amazon in the past.
In terms of your Ruby library, you probably should call mws.feeds.products.add(product) for subsequent updates of that product record and not call mws.feeds.products.update(product) at all. The latter seems to create what's called PurgeAndReplace feeds in MWS which you should avoid like the plague.
All other errors you encountered seem to be related to the same root cause.

Find and modify, fetch the data, process it and save- Mongoid

I am using Mongoid in my Rails application and found i can use find_and_modify command to update a document as soon as find operation succeeds.
consider a collection User below document structure
name
points
so the documents are saved like
{ "_id" : "51a7420eb09de918204883c4", "name" : "balan", "points" : 1727 }
now how do i update the points count as soon as i fetch the record, is there any way to do like below
User.where(id: "51a7420eb09de918204883c4").find_and_modify( "$set" => { points: points + 1 } )
i.e., the system should fetch the stored points and increment it by 1 and save it back again.
Please suggest.
Thanks for the link James.
Got the solution
User.where(id: "51a7420eb09de918204883c4").find_and_modify({ "$inc" => { points: 1 } })
as per mongoid.org/en/mongoid/docs/querying.html – James Wahlin in comments.
As James said, increment is the way to go:
User.where(id: "51a7420eb09de918204883c4").inc(:points, 100)
This will find all records with the given ID (or whatever you query), increment the value of points by 100, and save it again. Pretty nifty. You can call it on any mongoid query or individual record too, so User.find("some-id").inc(:points, 100) and User.first.inc(:points, 100) will work as well.

Resources