Feathers.js / Sequelize – >服务与两个模型之间的关系

我有通过sequelize与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中做到这一点?

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

#1 – 拥抱ORM
大部分人的问题来自对续集的理解不足。 为了帮助您,您首先需要了解续集关联如何工作以及如何使用“include”选项执行查询。 我build议阅读该链接的所有内容。 多读几遍,然后再多做一次,因为这是续集学习曲线中最陡峭的部分。 如果你从来没有使用ORM,让它为你做很多繁重的工作!

#2 – 从羽毛钩中设置续集选项
一旦你了解了“include”选项如何与sequ​​elize一起工作,你将需要在羽毛的“before”钩子中设置该选项。 羽毛将传递hook.params.sequelize的值作为所有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); } 

在引擎盖下面,羽毛会调用你的模型find方法:

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

值得注意的是:
旧的v1.x羽毛生成器(2017年3月之前)不生成对后续处理友好的代码。 这已在新的v2.x生成器中得到修复。 如果您在2017年3月以前离您的项目很远,那么请勿使用新的发电机。 请joinSlack Channel并joinsequelize room寻求帮助。 我会密切关注那里的事情,可以帮助你。 如果你刚刚开始你的项目,并没有很远,那么我强烈build议从新的发电机开始。 运行此命令(并按照这些说明 ):

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

从Sequelize的v4开始, classMethods属性已被删除请参阅此参考 。 这意味着@埃德加答案中的示例将不起作用。

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 () {..} 

好吧,我已经做了一些调整的代码。 为了保持每个人都可读,我将逐步转到实际的表格示例。 我有一个表'部分'和一个表'类别'(这是一个更简单的例子)。 该部分有一个与该类别的外键。 所以这就是我迄今为止所做的:

品类model.js

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

科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开始,现在我的表具有正确的外键,所以这部分工作。 此外,数据的显示仍然是正确的。 获取部分的查询仍在工作。 如果我在index.js中有不同的编码,那么npm start没有失败,但部分查询失败。

接下来是钩子。 这是我现在正在混淆的地方。 有些网站说这是在“查找定义”(现在让我们来说,但这是在我的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');”。 如前所述,它什么都不做。 所以回到我在这里得到的解释,它说'从一个前钩'.. 我发现了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错误。

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

我忘了一件事 我检查一下是否完整,是在chrome中打开F12,进入VUE插件并展开“serviceSections.find”的结果“listSections”。 我期望看到类别列,但也许这是错误的期望。 我也没有看到我的debugging器select“join”

稍后再编辑一下:

好吧,我搞砸了。 最后,我也遇到了这个post, 关于如何从多对多的关系中检索数据 。 读这个,我想调整“hooks \ index.js”的意图是正确的,但这意味着我的代码不是。 所以尝试不同的post组合,以上提供的提示,我现在有这个

部\钩\ 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的邮递员工作,因为把它放在'之前'部分的'get'部分,而且它在VUE中工作,因为我把函数放在'before'的'find'部分。

Mutliplejoin

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

元数据model.js

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

在这里,我开始总是把外键属性。 targetKey是另一个表格中列的名称。 方便,如果你需要改变它。 'as'属性是一个别名,我喜欢在多次使用时至less使用它。 在部分模型中,我对其进行了更改。

科model.js

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

为了完成这个,我改变了钩子函数。 我第一次尝试了太多的function,但那不起作用,所以我合并了这两个。

部\钩\ 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); }; } 

正如你看到我改变了function名称。 再次使用这个别名是很重要的,它并不是其他的方法。 在元数据部分,我放了一个filter。 查找时,我不想加载整个表格。 好吧,我join了一个ID,所以它真的是一对一的,但这样,如果由于某种原因,元数据条目改变为不同的types,我仍然有'状态'的select,并可以警告不正确的行。

最后的部分将是如果你想要内部或左外连接。 我通过钩子部分中的'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'是多语言的,所以我需要为它使用一个variables。 包括再次重复以获取来自联接的相关列。