it-swarm.com.ru

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

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

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

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

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

7
Edgar Koster

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

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

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

serviceCategories определяется как "appFeathers.service ('category');". Как уже упоминалось, это ничего не делает. Так что, возвращаясь к объяснению, которое я получил здесь, он говорит: «от крючка до». Я нашел файл 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, потому что помещает его в раздел 'get' в части 'before', и работает в VUE, потому что я помещаю функцию в часть 'find' части 'before'.

Mutliple Joins

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

метаданные-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 part

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

2
Edgar Koster

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

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

# 2 - настройка параметров сиквела из перьевого крючка
Как только вы поймете, как опция «include» работает с sequelize, вы захотите установить эту опцию из «до» ловушки в перьях. Перья передадут значение hook.params.sequelize в качестве параметра options для всех вызовов метода 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 Channel и присоединяйтесь к комнате sequelize за помощью. Я слежу за вещами там и могу помочь вам. Если вы только начали свой проект и не продвинулись слишком далеко, я настоятельно рекомендую начать с новых генераторов. Запустите эту команду (и следуйте этим инструкциям ):

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

Начиная с v4 в Sequelize свойство 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 () {..}
4
Kryten