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

Edg*_*ter 7 mysql node.js sequelize.js feathersjs feathers-sequelize

我有通过sequelize使用mysql运行的feathers.js.这是有效的,我可以从表中收集数据.下一步是在模型中定义"连接".

我有一个表'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')
Run Code Online (Sandbox Code Playgroud)

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

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

Rya*_*ale 13

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

#1 - 拥抱ORM
大多数人的问题来自于对续集的缺乏理解.为了帮助您,您首先需要了解sequelize关联如何工作以及如何使用"include"选项(又名"eager loading")执行查询.我建议几次阅读这些链接的所有内容,然后再多一次以获得良好的衡量标准; 这是续集学习曲线中最陡峭的部分.如果你从未使用过ORM,那就让它为你做很多繁重的工作吧!

#2 - 从羽毛钩设置续集选项
一旦你理解了"包含"选项如何与续集一起使用,你就会想要从羽毛中的"之前"勾选中设置该选项.Feathers将传递hook.params.sequelize作为所有sequelize方法调用的options参数的值.这是你的钩子可能是这样的:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

在引擎盖下,羽毛会将你的模型find方法称为:

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

值得注意的是:
旧的v1.x羽毛生成器(2017年3月之前)不会生成对sequelize友好的代码.这已在新的v2.x生成器中修复.如果您距离2017年3月之前的项目很远,那么请不要使用新的生成器.请加入Slack Channel并加入sequelize房间寻求帮助.我一直关注那里的事情,可以帮助你.如果你刚刚开始你的项目并且还没有走得太远,那么我强烈建议从新的发电机开始.运行此命令(并按照这些说明):

$ feathers --version              # see what version you are using
$ npm install -g @feathersjs/cli    # install latest version of the CLI
Run Code Online (Sandbox Code Playgroud)


Edg*_*ter 2

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

类别模型.js

classMethods: {
    associate() {
        category.hasOne(sequelize.models.sections, {
            as: 'category',
            foreignKey: 'category_id'
        });
    },
},
Run Code Online (Sandbox Code Playgroud)

部分模型.js

classMethods: {
    associate() {
        section.belongsTo(sequelize.models.categories, {
            allowNull: true
        });
    },
},
Run Code Online (Sandbox Code Playgroud)

服务\index.js

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

在本例中我使用的是 Jetbrains webstorm。所以我做了一个 npm start,我的表现在有了正确的外键,所以这部分可以工作。而且,数据的显示仍然是正确的。获取这些部分的查询仍然有效。如果我在 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;
})
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }],
    ...
};
Run Code Online (Sandbox Code Playgroud)

这给出了代码 500 fn.bind 错误。

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

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

稍后编辑:

好吧,那我就乱来了。最终我还发现了这篇关于如何从多对多关系中检索数据的文章。读到这里,我认为调整“hooks\index.js”的意图是正确的,但这意味着我的代码不是。因此,尝试该帖子的不同组合以及上面提供的提示,我现在有了这个

节\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);
    };
}
Run Code Online (Sandbox Code Playgroud)

这在带有 GET 的邮递员中工作,因为将其放在“之前”部分的“获取”部分中,并且它在 VUE 中工作,因为我将该函数放在“之前”的“查找”部分中。

多重连接

好的,我需要多次加入“部分”。我也有身份了 这是来自我的元数据表。因此,这意味着对元数据模型中的部分进行相同的关联。我是这样做的:

元数据模型.js

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

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

部分模型.js

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

为了完成这一点,我更改了 hooks 函数。我首先尝试了太的功能,但这不起作用,所以我将两者合并。

节\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);
    };
}
Run Code Online (Sandbox Code Playgroud)

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

最后一部分是如果您想要内部连接还是左外部连接。我通过钩子部分中的“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;
    })
}
Run Code Online (Sandbox Code Playgroud)

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