Как правильно обновлять модели с помощью Bookshelf.js?

Я уверен, что у меня что-то не хватает, но я считаю, что API Bookshelf неумолимо запутывает меня. Вот что я пытаюсь сделать:

  • У меня есть модель под названием Radio с первичным ключом строки с именем приложения с именем serial и для этого примера два поля с именем example1 и example2. Я указал пользовательский идентификатор с idAttribute: 'serial' в определении модели.
  • Я пытаюсь выполнить upsert с Bookshelf (не с Knex напрямую, в моем фактическом приложении этот запрос становится довольно сложным).
  • У меня есть дело ввода вставки, но я не могу заставить дело обновления работать.
  • Для простоты я сейчас не забочусь о транзакциях или атомарности. Я доволен, чтобы получить простой select → вставить/обновить для работы.

И конкретно в этом примере:

  • Вставить набор вставки example1 и example2.
  • В наборе обновлений example1 и оставьте example2 неизменным.

Итак, у меня есть что-то подобное в модели Bookshelf, так как метод класса (то есть "статический" ) ( "info" имеет поля "serial", "example1" и "example2" ):

insertOrUpdate: function (info) {
    return new Radio({'serial':info.serial}).fetch().then(function (model) {
        if (model) {
            model.set('example1', info.example1);
            return model.save({}, {
                method: 'update',
                patch: true
            })
        } else {
            return new Radio({
                serial: info.serial,
                example1: info.example1,
                example2: info.example2
            }).save({}, {
                method: 'insert'
            })
        }
    }).then(function (model) {
        console.log("SUCCESS");
    }).catch(function (err) {
        console.log("ERROR", err);
    });
}

Пример вызова:

Radio.insertOrUpdate({
    serial: ...,
    example1: ...,
    example2: ...
})

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

ERROR { Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where `serial` = '123223'' at line 1

Что очевидно при включенной отладке Knex, где в сгенерированном запросе отсутствует предложение set:

update `radios` set  where `serial` = ?

Теперь я сосредоточен на документах Bookshelf для fetch и save, и мне интересно, пойду ли я в неправильном направлении.

Я знаю, что неправильно использую API, но не могу понять. Несколько странных вещей, которые я заметил/должен был сделать, чтобы получить его в полурабочее состояние:

  • Я не понимаю первый параметр save. Было бы разумно, если бы сохранить был статический метод Model, но это не так. Это метод экземпляра, и вы уже можете передавать атрибуты конструктору Model (например, что бы обозначить new X(a:1).save({a:2})...?), И вы уже можете установить атрибуты с set до сохранения. Поэтому я не могу понять это. Мне пришлось передать {} в качестве заполнителя, чтобы указать параметры.

  • Существует эта forge, но я не уверен, какова ее цель, поскольку вы уже можете передавать атрибуты конструктору Model если авторы не нашли преимущества для X.forge({a:1}) vs. new X({a:1})...?).

  • Я обнаружил, что мне нужно явно указать способ сохранения из-за кажущегося quirk Bookshelf: Bookshelf основывает свой выбор метода на isNew(), но isNew() всегда true, когда вы передаете идентификатор к конструктору модели, который вы должны выполнить в случае с идентификатором приложения. Поэтому для присвоенных приложениями идентификаторов Bookshelf всегда будет делать "вставку", так как всегда думает, что модель "новая". Поэтому вам нужно заставить этот метод "обновить"... это просто добавляет к путанице в Книжной полке.

В любом случае, как мне это сделать правильно? Как сделать эту работу вставки и обновления?

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

Ответ 1

Это было интересно, и мне потребовалось некоторое время, чтобы понять, что происходит.

Как вы, кажется, обнаружили, что save() метод документация относительно опции patch указывает, что он

Сохранять только атрибуты, предоставленные в аргументах для сохранения.

Итак, вам просто нужно изменить свой код на

if (model) {
    model.set('example1', info.example1);
    return model.save();
}

и атрибуты set будут сохранены.

НО НО НО НО

ВСЕ атрибуты попадут в оператор update, даже id!

Это обычное поведение для ORM, его обоснование заключается в том, что если мы получили данные из одной транзакции и сохраняем ее с другой (плохая, плохая практика!), данные могут быть изменены другим клиентом. Таким образом, сохранение только части атрибутов может привести к несогласованному состоянию.

Но само существование атрибута patch нарушает это понятие. Так Книжная полка может быть улучшена путем:

  • Просто игнорируйте параметр patch. (Я мог бы предпочесть это)
  • Поскольку модели Bookshelf отслеживают измененные атрибуты, я думаю, что это должно быть тривиально, чтобы сделать обновления более умными в этом отношении. Это изменение также может привести к устареванию опции patch.
  • Другой подход может заключаться в том, чтобы семантика patch относилась к измененным атрибутам вместо тех, что были поставлены на save(). Но такие изменения, к сожалению, могут нарушить некоторые варианты использования.
  • Или, наконец, введение опции new для работы со всеми измененными атрибутами. Но это кажется грязным.

Ответ 2

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

В основном я смог заставить его работать, изменив дело "обновление" на:

  • Передайте атрибуты save в качестве первого параметра, а не установите их с помощью set.

Следуя окончательному решению:

insertOrUpdate: function (info) {
    return new Radio({'serial':info.serial}).fetch().then(function (model) {
        if (model) {
            // pass params to save instead of set()
            var params = { 'example1' : info.example1 }
            return model.save(params, {
                method: 'update',
                patch: true
            })
        } else {
            return new Radio({
                serial: info.serial,
                example1: info.example1,
                example2: info.example2
            }).save({}, {
                method: 'insert'
            })
        }
    }).then(function (model) {
        console.log("SUCCESS");
    }).catch(function (err) {
        console.log("ERROR", err);
    });
}

Я все еще не уверен, как /if forge подходит здесь или что должна быть сделка с первым параметром save в случае "вставки".

Что еще более важно, я не совсем уверен, для чего теперь set. Предполагается, что одно из основных преимуществ структуры ORM заключается в том, чтобы сделать этот вид прозрачным (т.е. "Сохранить" работает правильно, позволяя вам использовать модель, не задумываясь об этом, и без необходимости знать о том, что изменилось на укажите, что вы "сохраняете" - я должен иметь неизвестный произвольный код set вещи раньше времени, а затем сохранить его, не зная, что изменилось, но похоже, что я не могу), поэтому я не уверен что я на самом деле получил от Книжной полки здесь. Должен быть лучший подход.