【问题标题】:Feathers.js / Sequelize -> Service with relations between two modelsFeathers.js / Sequelize -> 具有两个模型之间关系的服务
【发布时间】:2017-08-08 02:00:39
【问题描述】:

我已经通过 sequelize 让 feathers.js 与 mysql 一起运行。这是有效的,我可以从表中收集数据。下一步是在模型中定义“连接”。

我有一个包含“status_id”和“country_id”列的表。这些列引用元数据表中的 id。在 SQL 中我是对的:

SELECT status.description, country.description, detail 
FROM details 
INNER JOIN metadata status 
    ON (details.status_id = status.id AND status.type = 'status' 
INNER JOIN metadata country 
    ON (details.country_id =country.id AND country.type = 'country')

在这种情况下,此元数据表不会很大,因此采用了这种方法。它确实提供了我需要的灵活性。

在feathters.js 中我需要做什么?

【问题讨论】:

    标签: mysql node.js sequelize.js feathersjs feathers-sequelize


    【解决方案1】:

    在帮助很多人解决同样的问题后,我了解到解决方案分为两部分:

    #1 - 拥抱 ORM
    大多数人的问题来自于对sequelize缺乏了解。为了帮助您,您首先需要了解sequelize associations 的工作原理以及如何使用"include" option 执行查询(又名“急切加载”)。我建议将这些链接的所有内容阅读几遍,然后再阅读一遍。这是 sequelize 学习曲线中最陡峭的部分。如果您从未使用过 ORM,请让它为您完成很多繁重的工作!

    #2 - 从羽毛钩设置续集选项
    一旦您了解了“包含”选项如何与 sequelize 一起使用,您将希望从羽毛中的“之前”钩子中设置该选项。 Feathers will pass the value of hook.params.sequelize as the options parameter 用于所有 sequelize 方法调用。这就是你的钩子的样子:

    // GET /my-service?name=John&include=1
    function (hook) {
       if (hook.params.query.include) {
          const AssociatedModel = hook.app.services.fooservice.Model;
          hook.params.sequelize = {
             include: [{ model: AssociatedModel }]
          };
          // delete any special query params so they are not used
          // in the WHERE clause in the db query.
          delete hook.params.query.include;
       }
       return Promise.resolve(hook);
    }
    

    在引擎盖下,feathers 会调用你的模型 find 方法,如下所示:

    // YourModel is a sequelize model
    const options = Object.assign({ where: { name: 'John' }}, hook.params.sequelize);
    YourModel.findAndCount(options);
    

    值得注意:
    旧的 v1.x 羽毛生成器(2017 年 3 月之前)不会生成对 sequelize 友好的代码。这已在新的 v2.x 生成器中得到修复。如果您在 2017 年 3 月之前对您的项目有相当长的了解,那么不要使用新的生成器。请加入Slack Channel 并加入sequelize 房间寻求帮助。我密切关注那里的事情,可以帮助你。如果您刚刚开始您的项目并且还没有走多远,那么我强烈建议您从新的生成器开始。运行此命令(并关注these instructions):

    $ feathers --version              # see what version you are using
    $ npm install -g @feathersjs/cli    # install latest version of the CLI
    

    【讨论】:

    • 感谢调整代码部分,我忘记了。
    • 我正在研究这些技巧,现在在模型中有关联。我现在正在尝试运行最后一部分,因为我不再遇到编译错误。我对我的模型文件中的复数和非复数符号以及在哪里使用哪个符号感到有些困惑,但我想通了。解决后我会发布。
    【解决方案2】:

    从 Sequelize v4 开始,classMethods 属性已被删除 See this reference。这意味着@Edgar 的答案中的示例将不起作用。

    associate 方法必须直接添加到模型中,而不是包装在 classMethods 属性中。来自 Sequelize 文档:

    上一个:

    const Model = sequelize.define('Model', {
        ...
    }, {
        classMethods: {
            associate: function (model) {...}
        },
        instanceMethods: {
            someMethod: function () { ...}
        }
    });
    

    新:

    const Model = sequelize.define('Model', {
        ...
    });
    
    // Class Method
    Model.associate = function (models) {
        ...associate the models
    };
    
    // Instance Method
    Model.prototype.someMethod = function () {..}
    

    【讨论】:

      【解决方案3】:

      好的,我已经对代码进行了一些调整。为了让每个人都能阅读,我将转到实际的表格示例。我有一个表'sections'和一个表'categories'(这是一个更简单的例子)。该部分有一个带有类别的外键。所以这就是我到目前为止所做的:

      类别模型.js

      classMethods: {
          associate() {
              category.hasOne(sequelize.models.sections, {
                  as: 'category',
                  foreignKey: 'category_id'
              });
          },
      },
      

      section-model.js

      classMethods: {
          associate() {
              section.belongsTo(sequelize.models.categories, {
                  allowNull: true
              });
          },
      },
      

      服务\index.js

      ...
      app.set('models', sequelize.models);
      ...
      Object.keys(sequelize.models).forEach(function(modelName) {
          if ("associate" in sequelize.models[modelName]) {
              sequelize.models[modelName].associate();
          }
      });
      

      在这种情况下,我使用的是 Jetbrains webstorm。所以我做了一个 npm start,我的表现在有正确的外键,所以这部分工作。此外,数据的显示仍然正确。获取这些部分的查询仍在工作。如果我在 index.js 中有不同的编码,那么 npm start 并没有失败,但是 section 查询失败了。

      接下来是:钩子。这就是我现在感到困惑的地方。一些网站说它在“查找定义”中(我们暂时称它为,但这是在我的 vue 组件安装部分)。然后你解释了,很好地展示了,包含在那部分,但它什么也没做,代码仍然运行,但是当我通过邮递员获取这些部分时,我没有看到类别信息。然后我会有例如

      serviceSections.find({
          include: [{
              model: serviceCategories.Model
          }],
          query: {
              $sort: {
                  section_description_en: 1
              }
          }
      }).then(page => {
          page.data.reverse();
          this.listSections = page.data;
      })
      

      serviceCategories 被定义为“appFeathers.service('categories');”。如前所述,它什么也不做。所以回到我在这里得到的解释,它说'.. from a before hook ..'。我找到了用于类别和服务的 hooks\index.js 文件。但在这里我犯了错误。我首先在类别中进行了此调整,然后在第一节中进行了调整

      exports.before = {
          ...
          find: [{
              function (hook) {
                  if (hook.params.query.include) {
                      const AssociatedModel = hook.app.services.category.Model;
                      hook.params.sequelize = {
                          include: [{ model: AssociatedModel }]
                      };
                  }
                  return Promise.resolve(hook);
              }
          }],
          ...
      };
      

      这导致代码 500 fn.bind 错误。

      只是想发布进度,这并不意味着我停止寻找最后一步(或缺少的步骤)。

      我忘记了一件事。我检查是否完成是在 chrome 中打开 F12,转到 VUE 插件并展开“serviceSections.find”的结果“listSections”。我希望在其中看到类别列,但也许这是错误的期望。我在调试器的选择中也没有看到“加入”

      稍后再编辑:

      好吧,所以我搞砸了。最后我也看到了这个帖子on how to retrieve data from many to many relationships。读到这里,我认为调整“hooks\index.js”的意图是正确的,但这意味着我的代码不是。所以尝试了该帖子的不同组合,以及上面提供的提示,我现在有了这个

      section\hooks\index.js

      ...
      exports.before = {
          all: [],
          find: [
              getCategory()
          ],
          get: [
              getCategory()
          ],
          create: [],
          update: [],
          patch: [],
          remove: []
      };
      ...
      
      function getCategory() {
          return function (hook) {
              const category = hook.app.services.categories.Model;
              hook.params.sequelize = {
                  include: [{ model: category }]
              };
              return Promise.resolve(hook);
          };
      }
      

      这在邮递员中使用 GET,因为将它放在“之前”部分的“获取”部分,它在 VUE 中工作,因为我将函数放在“之前”的“查找”部分。

      多重连接

      好的,我需要多个连接到“部分”。我也有地位。这来自我的元数据表。所以这意味着对元数据模型中的部分进行相同的关联。我是这样做的:

      元数据模型.js

      classMethods: {
          associate() {
              metadata.hasOne(sequelize.models.sections, {
                  as: 'satus',
                  foreignKey: 'status_id',
                  targetKey: 'status_id'
              });
          }, },
      

      这里我开始总是放入 foreignKey 属性。 targetKey 是另一个表中列的名称。如果您需要更改它很方便。 'as' 属性是一个别名,我几乎总是喜欢使用它,至少在多次使用它时是这样。在部分模型中,我对其进行了更改。

      section-model.js

      classMethods: {
          associate() {
              section.belongsTo(sequelize.models.categories, {
                  allowNull: false,
                  as: 'category'
              });
              section.belongsTo(sequelize.models.metadata, {
                  allowNull: false,
                  as: 'status'
              });
          }, },
      

      为了最终确定这一点,我更改了 hooks 函数。我第一次尝试了 too 函数,但没有奏效,所以我将两者合并。

      section\hooks\index.js

      function getRelatedInfo() {
          return function (hook) {
              hook.params.sequelize = {
                  include: [
                      {
                          model: hook.app.services.categories.Model,
                          as: 'category'
                      },{
                          model: hook.app.services.metadata.Model,
                          as: 'status',
                          where: {
                              type: 'status'
                          }
                      }
                  ]
              };
              return Promise.resolve(hook);
          };
      }
      

      如您所见,我更改了函数名称。再次使用别名很重要,否则它不起作用。在元数据部分,我放置了一个过滤器。我不想在查找时加载整个表格。好的,我加入了一个 id,所以它确实是一对一的,但是如果由于某种原因元数据条目更改为不同的类型,我仍然可以选择“状态”,并且可以对不正确的行发出警告。

      如果您想要内部或左外部联接,最后一部分将是。我是通过 hooks 部分中的 'required: true' 属性触发的。

      VUE.js 部分

      最后一部分是将其推送到 vue 组件。我在已安装部分执行此操作。我有这个代码。

      const socket = io();
      const appFeathers = feathers()
          .configure(feathers.socketio(socket))
          .configure(feathers.hooks());
      const serviceSections = appFeathers.service('sections');
      const serviceArticles = appFeathers.service('articles');
      const serviceMetadata = appFeathers.service('metadata');
      ...
      mounted() {
          serviceArticles.find({
              include: [{
                  model: serviceMetadata.Model,
                  as: 'country',
                  query: {
                      $select: [
                          'country_icon'
                      ]
                  }
              }],
              query: {
                  $sort: {
                      article_description_en: 1
                  },
                  $select: [
                      'id',
                      ['article_description_en', 'article_description'],
                      'article_length',
                      'article_ascend',
                      'article_code'
                  ]
              }
          }).then(page => {
              this.listTrails = page.data;
          })
      }
      

      我在这里做的是从数组中过滤掉不需要的列。我也重命名了一些。 '*_en' 是多语言的,所以我需要为它使用一个变量。再次重复包含以从连接中获取相关列。

      【讨论】:

      • 这太棒了,因为您的示例还显示了它在客户端上的工作方式。在客户端中,您可以调整要从后端返回的信息类型。有点像 GraphQL 的简化版本...
      猜你喜欢
      • 2016-12-03
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 2017-06-26
      • 1970-01-01
      • 1970-01-01
      • 2019-11-13
      相关资源
      最近更新 更多