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