Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination of relations? #56

Open
docelic opened this issue Jul 27, 2022 · 7 comments
Open

Pagination of relations? #56

docelic opened this issue Jul 27, 2022 · 7 comments

Comments

@docelic
Copy link

docelic commented Jul 27, 2022

Hello,

The docs and tests show pagination on a single Sequelize model.
But is it possible to paginate on model associations like author.getBooks()?

(In node's GraphQL implementation of connections it is transparently (for the user, at least) possible to paginate models and associations.)

@docelic
Copy link
Author

docelic commented Aug 3, 2022

Hello,
Any comments/hints on this question?

@Kaltsoon
Copy link
Owner

Kaltsoon commented Aug 4, 2022

There's no direct support, but quite simple workaround would be to use the Book model with appropriate query:

Book.paginate({
  limit: 20,
  where: {
    authorId: author.id // or whatever the foreign key is
  },
});

And this could be implemented as a method such as getPaginatedBooks() without too much hassle:

class Author {
  // ...
 getPaginatedBooks(options) {
   return Book.paginate({
      ...options,
      where: {
        ...options?.where,
        authorId: this.id,
      },
    });
 }
}

@docelic
Copy link
Author

docelic commented Aug 4, 2022

Yes, certainly, although I wanted to avoid that since it means duplicating the relation's query/conditions.

@docelic
Copy link
Author

docelic commented Aug 4, 2022

GraphQL's connection fields can be transparently created on either model or relations. Time-permitting I'll see if it is easily identifiable from the code how they did it.

@Kaltsoon
Copy link
Owner

Kaltsoon commented Aug 4, 2022

Yes, certainly, although I wanted to avoid that since it means duplicating the relation's query/conditions.

I don't see this as a major issue. Relation conditions change very rarely and only one method needs to be changed if that happens

@docelic
Copy link
Author

docelic commented Aug 4, 2022

A patch as simple as this makes it possible to paginate associations.

diff -ru sequelize-cursor-pagination/makePaginate.js sequelize-cursor-pagination/makePaginate.js
--- sequelize-cursor-pagination/makePaginate.js    2022-08-04 22:50:45.774000000 +0200
+++ sequelize-cursor-pagination/makePaginate.js 2022-08-04 22:45:08.181000000 +0200
@@ -2,10 +2,21 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 const sequelize_1 = require("sequelize");
 const utils_1 = require("./utils");
-const makePaginate = (model, options) => {
+
+function checkIsAssociation(target) {
+  return !!target.associationType;
+}
+
+const makePaginate = (target, options) => {
+    const isAssociation = checkIsAssociation(target)
+    const findFunction = isAssociation ? target.accessors.get : 'findAll'
+    const countFunction = isAssociation ? target.accessors.count : 'count'
+    const model = isAssociation ? target.target : target
+
     const primaryKeyField = options?.primaryKeyField ?? (0, utils_1.getPrimaryKeyFields)(model);
     const omitPrimaryKeyFromOrder = options?.omitPrimaryKeyFromOrder ?? false;
-    const paginate = async (queryOptions) => {
+    const paginate = async (queryOptions, source) => {
+        if (!isAssociation) source = model;
         const { order: orderOption, where, after, before, limit, ...restQueryOptions } = queryOptions;
         const normalizedOrder = (0, utils_1.normalizeOrder)(orderOption, primaryKeyField, omitPrimaryKeyFromOrder);
         const order = before ? (0, utils_1.reverseOrder)(normalizedOrder) : normalizedOrder;
@@ -33,9 +44,9 @@
             ...restQueryOptions,
         };
         const [instances, totalCount, cursorCount] = await Promise.all([
-            model.findAll(paginationQueryOptions),
-            model.count(totalCountQueryOptions),
-            model.count(cursorCountQueryOptions),
+            source[findFunction](paginationQueryOptions),
+            source[countFunction](totalCountQueryOptions),
+            source[countFunction](cursorCountQueryOptions),
         ]);
         if (before) {
             instances.reverse();

Associations require an instance to start querying from, rather than just a model. Sequelize keeps track of model's associations in Model.associations hash, along with all necessary data. Knowing that, and assuming we have Author -> Books relation defined in Sequelize, the usage would be:

paginatedBooksFromAuthor = await makePaginate(Author.associations.books)
author = await Author.findOne()
await paginatedBooksFromAuthor( {order: 'id'}, author )

This is just an in-place patch for .js as a proof of concept showing that it works. Let me know if you'd be willing to merge if a proper PR was made.

@Kaltsoon
Copy link
Owner

Kaltsoon commented Aug 5, 2022

Seems like a quite clean solution. If you can turn this into a PR it would be highly appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants