Feathers.js/Sequelize -> Сервис с отношениями между двумя моделями

У меня есть перья.js, работающие с mysql через сиквелиз. Это работает, я могу собирать данные из таблиц. Следующим шагом является определение «соединений» в моделях.

У меня есть таблица со столбцами «status_id» и «country_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')

В этом случае эта таблица метаданных не будет большой, поэтому такой подход. Это дает гибкость, в которой я нуждаюсь.

Что мне нужно сделать, чтобы сделать это в featters.js?


person Edgar Koster    schedule 16.03.2017    source источник


Ответы (3)


Помогая многим людям с той же проблемой, я узнал, что решение состоит из двух частей:

№1 – используйте ORM
Большинство проблем людей возникает из-за непонимания принципа продолжения. Чтобы помочь вам, вам сначала нужно понять, как последовательность ассоциаций и как выполнять запросы с помощью параметра "include" (иначе "нетерпеливая загрузка"). Я рекомендую прочитать все содержимое этих ссылок пару раз, а затем еще раз для верности; это самая крутая часть кривой обучения продолжению. Если вы никогда не использовали ORM, пусть он сделает за вас большую часть тяжелой работы!

#2 — установка параметров продолжения с помощью хука перьев
Как только вы поймете, как параметр "включить" работает с продолжением, вы захотите установить этот параметр с помощью хука "до" в перьях. Feathers будет передавать значение hook.params.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 г.) не генерируют код, удобный для продолжения. Это было исправлено в новых генераторах v2.x. Если вы довольно далеко продвинулись в своем проекте до марта 2017 года, не используйте новые генераторы. Присоединяйтесь к каналу Slack и присоединяйтесь к sequelize комнате для получения помощи. Я слежу за вещами там и могу помочь вам. Если вы только начали свой проект и не продвинулись очень далеко, то я настоятельно рекомендую начать с новых генераторов. Запустите эту команду (и следуйте этим инструкциям):

$ feathers --version              # see what version you are using
$ npm install -g @feathersjs/cli    # install latest version of the CLI
person Ryan Wheale    schedule 16.03.2017
comment
Спасибо за корректировку разделов кода, я их забыл. - person Edgar Koster; 17.03.2017
comment
Я работаю над этими советами и теперь есть ассоциации в моделях. Сейчас я пытаюсь запустить последнюю часть, так как больше не получаю ошибок компиляции. Я немного запутался с обозначениями множественного и не множественного числа в моем файле модели и с тем, что и где использовать, но понял это. Я опубликую, когда это будет решено. - person Edgar Koster; 17.03.2017


Хорошо, я сделал некоторые настройки кода. Чтобы все было понятно для всех, я перейду к реальным примерам таблиц. У меня есть таблица «разделы» и таблица «категории» (это более простой пример). Раздел имеет внешний ключ с категорией. Итак, вот что я сделал для этого до сих пор:

категория-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. Итак, я запустил npm, и теперь моя таблица имеет правильный внешний ключ, так что эта часть работает. Кроме того, отображение данных по-прежнему корректно. Запрос, который получает разделы, все еще работает. Если бы у меня была другая кодировка в index.js, то запуск npm не был бы неудачным, но не удалось выполнить запрос секций.

Далее: крючки. Вот тут у меня сейчас какая-то путаница. Некоторые сайты говорят, что это находится в «определении поиска» (давайте назовем его так, но это в моем разделе смонтированного компонента 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.

Просто решил опубликовать прогресс, это не значит, что я перестану искать последний шаг (или пропущенный шаг).

Я забыл одну вещь. Моя проверка, чтобы увидеть, завершена ли она, - это открыть F12 в chrome, перейти к плагину VUE и развернуть «listSections», который является результатом «serviceSections.find». Я ожидаю увидеть в нем столбец категорий, но, возможно, это неправильное ожидание. Я также не вижу «присоединиться» в выборе моего отладчика.

Изменить чуть позже:

Ладно, значит, я повозился. В конце концов я также наткнулся на этот пост о том, как получить данные от многих к много отношений. Прочитав это, я понял, что намерение настроить «hooks\index.js» было правильным, но это означало, что мой код не был таковым. Итак, пробуя различные комбинации этого поста и приведенных выше советов, теперь у меня есть это

раздел\хуки\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, потому что я поместил функцию в часть «найти» «до».

Несколько объединений

Хорошо, мне нужно было несколько присоединений к «разделам». У меня тоже есть статус. Это исходит из моей таблицы метаданных. Таким образом, это означало создание такой же ассоциации с разделами в модели метаданных. Я сделал это так:

метаданные-model.js

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

Здесь я начал всегда добавлять атрибут ForeignKey. targetKey — это имя столбца в другой таблице. Удобно, если вам нужно изменить его. Атрибут as — это псевдоним, мне нравится использовать его почти всегда, по крайней мере, когда я использую его несколько раз. В раздел-модель я внес изменения.

раздел-model.js

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

Чтобы завершить это, я изменил функцию хуков. Сначала я попробовал слишком много функций, но это не сработало, поэтому я объединил их.

раздел\хуки\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);
    };
}

Как видите, я изменил имя функции. Важно снова использовать псевдоним, иначе это не сработало. В части метаданных я поставил фильтр. Я не хочу загружать всю таблицу при поиске. Хорошо, я присоединяюсь к идентификатору, так что это действительно один на один, но таким образом, если по какой-то причине запись метаданных изменяется на другой тип, у меня все еще есть выбор «статуса» и я могу сделать предупреждение о неправильных строках.

Последняя часть будет заключаться в том, хотите ли вы внутреннее или левое внешнее соединение. Я запустил это с помощью атрибута «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' многоязычны, поэтому мне нужно использовать для них переменную. Включение повторяется снова, чтобы получить связанные столбцы из объединений.

person Edgar Koster    schedule 18.03.2017
comment
Это фантастика, потому что ваш пример также показывает, как это работает на клиенте. В клиенте вы можете настроить, какую информацию вы хотите получить от серверной части. Вроде как упрощенная версия GraphQL... - person leo; 07.01.2019