【问题标题】:KeystoneJs user-defined order for RelationshipKeystoneJs 用户定义的关系顺序
【发布时间】:2021-05-08 02:29:35
【问题描述】:

我使用 KeystoneJS 和 PostgreSQL 作为我的后端,并在我的应用程序的前端使用 Apollo。 我有一个架构,它有一个链接到另一个列表的列表。 我希望能够允许用户更改第二个列表的顺序。

这是我的架构的简化版本

keystone.createList(
    'forms',
    {
      fields: {
        name: {
          type: Text,
          isRequired: true,
        },
        buttons: {
          type: Relationship,
          ref: 'buttons.attached_forms',
          many: true,
        },
      },
    }
);

keystone.createList(
    'buttons',
    {
      fields: {
        name: {
          type: Text,
          isRequired: true,
        },
        attached_forms: {
          type: Relationship,
          ref: 'forms.buttons',
          many: true,
        },
      },
    }
);

所以我想做的是允许用户更改按钮的顺序,以便将来从表单中获取它们时:

const QUERY = gql`
  query getForms($formId: ID!) {
    allforms(where: {
      id: $formId,
    }) {
      id
      name
      buttons {
        id
        name
      }
    }
  }
`;

按钮应该以预定义的顺序从后端返回。

{
    id: 1,
    name: 'Form 1',
    buttons: [
        {
            id: 1,
            name: 'Button 1',
        },
        {
            id: 3,
            name: 'Button 3',
        },
        {
            id: 2,
            name: 'Button 2',
        }
    ]
}

或者甚至只是在查询中返回一些数据,这些数据将允许根据前端用户定义的排序顺序进行排序。

关键是这种关系是多对多的。

因此,将列添加到按钮架构是不够的,因为排序需要特定于关系。换句话说,如果用户将特定按钮放在特定表单的最后,则不应更改该按钮在其他表单上的顺序。

在我自己创建的后端中,我会在连接表中添加一些内容,例如 sortOrder 字段或类似字段,然后更改这些值以更改顺序,甚至使用该信息在前端对它们进行排序。 像this answer here 这样的东西。 多对多连接表将包含 formId、buttonId、sortOrder 等列。

我一直在深入研究 KeystoneJS 的文档,但如果不涉及覆盖我们正在使用的 KnexAdapter 的杂草,我想不出一种方法来完成这项工作。

我正在使用:

{
    "@keystonejs/adapter-knex": "^11.0.7",
    "@keystonejs/app-admin-ui": "^7.3.11",
    "@keystonejs/app-graphql": "^6.2.1",
    "@keystonejs/fields": "^20.1.2",
    "@keystonejs/keystone": "^17.1.2",
    "@keystonejs/server-side-graphql-client": "^1.1.2",
}

对我如何实现这一点有什么想法吗?

【问题讨论】:

    标签: postgresql graphql keystonejs


    【解决方案1】:

    我们想出的一个选项是将订单添加到表单表中。

    keystone.createList(
        'forms',
        {
          fields: {
            name: {
              type: Text,
              isRequired: true,
            },
            buttonOrder: {
              type: Text,
            },
            buttons: {
              type: Relationship,
              ref: 'buttons.attached_forms',
              many: true,
            },
          },
        }
    );
    

    这个新字段buttonOrder 可以包含按钮 ID 顺序的字符串表示形式,就像在 JSON 字符串化数组中一样。 这样做的主要问题是很难使该字段与实际链接的按钮保持同步。

    【讨论】:

      【解决方案2】:

      我曾经遇到过类似的挑战,所以经过一些研究并发现了这个answer,我使用 PostgreSQL TRIGGER 为一个项目实施了一个解决方案。

      因此,您可以在更新时添加触发器,它应该移动 buttonOrder

      这是我的 SQL,这是测试代码,我正则表达式替换了这些术语以适合您的问题 :)

      
          // Assign order
          await knex.raw(`
              do $$
              DECLARE form_id text;
              begin
                  CREATE SEQUENCE buttons_order_seq;
                  CREATE VIEW buttons_view AS SELECT * FROM "buttons" ORDER BY "createdAt" ASC, "formId";
                  CREATE RULE buttons_rule AS ON UPDATE TO buttons_view DO INSTEAD UPDATE buttons SET order = NEW.order WHERE id = NEW.id;
              
                  FOR form_id IN SELECT id FROM form LOOP
                      ALTER SEQUENCE buttons_order_seq RESTART;
              
                      UPDATE buttons_view SET order = nextval('buttons_order_seq') WHERE "formId" = form_id;
                  END LOOP;
              
                  DROP SEQUENCE buttons_order_seq;
                  DROP RULE buttons_rule ON buttons_view;
                  DROP VIEW buttons_view;
              END; $$`);
      
          // Create function that shifts orders
          await knex.raw(`
              CREATE FUNCTION shift_buttons_order()
                  RETURNS trigger AS
                  $$
                  BEGIN
                      IF NEW.order < OLD.order THEN
                          UPDATE buttons SET order = order + 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
                              WHERE order >= NEW.order AND order < OLD.order AND "formId" = OLD."formId";
                      ELSE 
                          UPDATE buttons SET order = order - 1, "shiftOrderFlag" = NOT "shiftOrderFlag"
                              WHERE order <= NEW.order AND order > OLD.order AND "formId" = OLD."formId";
                      END IF;
                  RETURN NEW;
                  END;
                  $$
                  LANGUAGE 'plpgsql'`);
      
          // Create trigger to shift orders on update
          await knex.raw(`
              CREATE TRIGGER shift_buttons_order BEFORE UPDATE OF order ON buttons FOR EACH ROW
                  WHEN (OLD."shiftOrderFlag" = NEW."shiftOrderFlag" AND OLD.order <> NEW.order)
                  EXECUTE PROCEDURE shift_buttons_order()`);
      

      【讨论】:

        【解决方案3】:

        一种方法是有两个“按钮”列表,一个带有按钮模板(下面的buttonTemplate),其中包含名称等常用数据,另一个(下面的button)引用一个buttonTemplate和一个form。这允许您为每个button 分配一个formIndex 属性,这决定了它在相应表单上的位置。

        (未经测试)示例代码:

        keystone.createList(
          'Form',
          {
            fields: {
              name: {
                type: Text,
                isRequired: true,
              },
              buttons: {
                type: Relationship,
                ref: 'Button.form',
                many: true,
              },
            },
          }
        );
        
        keystone.createList(
          'Button',
          {
            fields: {
              buttonTemplate: {
                type: Relationship,
                ref: 'ButtonTemplate.buttons',
                many: false,
              },
              form: {
                type: Relationship,
                ref: 'Form.buttons',
                many: false,
              },
              formIndex: {
                type: Integer,
                isRequired: true,
              },
            },
          }
        );
        
        keystone.createList(
          'ButtonTemplate',
          {
            fields: {
              name: {
                type: Text,
                isRequired: true,
              },
              buttons: {
                type: Relationship,
                ref: 'Button.buttonTemplate',
                many: true,
              },
            },
          }
        );
        

        我认为与 buttonOrder 解决方案相比,这不太可能导致您头痛(我相信您会看到)。用户删除此字段引用的按钮。

        如果您决定采用这种方法,您可以使用 Keystone 中的 hook functionality 来防范此类问题。例如。在buttondeleted 之前,检查所有表单并重写buttonOrder 字段,删除对已删除按钮的所有引用。

        【讨论】:

        • 不错。因此,您将缺少的中间表添加为一个全新的列表。我没有想到这一点。我想缺点是现在不是 3 个表(2 个表和一个连接),它现在有 5 个表(2 个表,连接表和它们之间的实际连接表),所以性能可能会成为一个问题,但你完全正确的。这似乎是解决限制的好方法。谢谢。
        • NP。您可以通过在创建按钮时将属性从按钮模板复制到每个按钮来避免性能下降,因此您只需为每个按钮引用一次模板 - 此处复制数据的示例keystonejs.com/keystonejs/server-side-graphql-client/#use-cases
        猜你喜欢
        • 2015-03-08
        • 1970-01-01
        • 2014-10-27
        • 1970-01-01
        • 2014-03-15
        • 2018-05-20
        • 2021-06-10
        • 2017-01-17
        • 2015-11-23
        相关资源
        最近更新 更多