如何正确地使用Bookshelf.js更新模型?

时间:2022-06-08 07:24:19

I'm sure I am missing something but I find the Bookshelf API to be relentlessly confusing for me. Here's what I am trying to do:

我肯定我漏掉了一些东西,但我发现书架API让我困惑不已。以下是我想做的:

  • I have a model called Radio with an application-assigned string primary key named serial and, for the purpose of this example, two fields named example1 and example2. I have specified the custom ID with idAttribute: 'serial' in the model definition.
  • 我有一个名为Radio的模型,它有一个应用程序分配的字符串主键,名为serial,在本例中,有两个字段,命名为example1和example2。我在模型定义中使用idAttribute: 'serial'指定了自定义ID。
  • I'm trying to perform an upsert with Bookshelf (not with Knex directly, in my actual application that query becomes rather complex).
  • 我正在尝试对书架执行更新(不是直接对Knex执行,在我的实际应用程序中,查询变得相当复杂)。
  • I've got the insert case working but can't seem to get the update case working.
  • 我已经有了insert case working,但似乎无法让update case工作。
  • For simplicity I'm not caring about transactions or atomicity right now. I am satisfied to get a simple select → insert/update to work.
  • 为了简单起见,我现在不关心事务或原子性。我很满意让一个简单的选择→插入/更新工作。

And specifically in this example:

特别是在这个例子中

  • On insert set example1 and example2.
  • 在插入集example1和example2上。
  • On update set example1 and leave example2 unchanged.
  • 在更新时,设置example1并保持example2不变。

So I've got something like this in the Bookshelf model, as a class (i.e. "static") method ("info" has fields "serial", "example1", and "example2"):

我在书架模型中有这样的东西,作为一个类。“静态”)方法(“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);
    });
}

Example call:

示例调用:

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

The problem I'm running into here is that while the "insert" case works, the "update" case fails with:

我在这里遇到的问题是,当“插入”案例起作用时,“更新”案例以以下方式失败:

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

Which is obvious with Knex debugging turned on, where the generated query is missing the set clause:

这在Knex调试打开时很明显,生成的查询忽略了set子句:

update `radios` set  where `serial` = ?

Now, I'm focused on the Bookshelf docs for fetch and save, and I'm wondering if I'm headed in the wrong direction.

现在,我把注意力放在书架上的文档上,以获取和保存,我想知道我是不是走错了方向。

I know I'm using the API wrong but I can't figure it out. A couple of weird things I noticed / had to do just to get it into its semi-working state:

我知道我使用的API是错误的,但我不明白。为了让它进入半工作状态,我注意到/不得不做的一些奇怪的事情:

  • I don't understand the first parameter of save. It would make sense to me if save was a static method of Model, but it isn't. It's an instance method and you can already pass attributes to the Model constructor (e.g. what would new X(a:1).save({a:2}) mean...?), and you can already set attributes with set prior to the save. So I can't make sense of this. I had to pass {} as a placeholder to let me specify the options.

    我不理解save的第一个参数。如果save是模型的静态方法,对我来说是有意义的,但它不是。它是一个实例方法,您已经可以将属性传递给模型构造函数(例如:new X(a:1).save({a:2}),您可以在保存之前设置属性。我搞不懂这个。我必须通过{}作为占位符,让我指定选项。

  • There is this forge thing but I'm not sure what its purpose is since you can already pass attributes to the Model constructor (unless the authors found some benefit to X.forge({a:1}) vs. new X({a:1})...?).

    有一个forge的东西,但是我不确定它的目的是什么,因为您已经可以将属性传递给模型构造函数(除非作者发现X.forge({a:1})和new X({a:1})…?

  • I found that I had to explicitly specify the method to save because of an apparent Bookshelf quirk: Bookshelf bases its choice of method on isNew(), but isNew() is always true when you pass the id to the model constructor, which you have to do in the application-assigned ID case. Therefore for application-assigned IDs Bookshelf will always do an "insert" since it always thinks the model "is new". So you have to force the method to "update"... this just adds to my Bookshelf confusion.

    我发现我必须显式地指定要保存的方法,因为有一个明显的书架怪癖:书架根据isNew()来选择方法,但是isNew()在将id传递给模型构造函数时总是正确的,这在应用程序分配的id用例中是必须的。因此,对于应用程序分配的IDs,书架将始终执行“插入”操作,因为它总是认为模型“是新的”。所以你必须强迫方法“更新”…这只是增加了我书架上的混乱。

Anyways, how do I do this properly? How do I make this insert and update work?

不管怎么说,我怎么才能做到这一点?如何使插入和更新工作?

More importantly, where is this documented? In good faith I assume that it is documented clearly somewhere and I just can't see the forest for the trees right now, so I'd actually appreciate some direction to follow in the docs even more than a direct answer, because I do need to figure this out. I've been spending a lot of time on Bookshelf instead of actual development, so much that I almost wish I would have just stuck to direct SQL queries from the start.

更重要的是,这些文档在哪里?我敢肯定地认为,它在某些地方被清楚地记录下来了,而我现在只是看不到森林中的树木,所以我更希望在文档中遵循一些指导,而不是直接的答案,因为我确实需要弄清楚这一点。我花了很多时间在书架上而不是实际开发上,以至于我几乎希望从一开始就坚持直接使用SQL查询。

2 个解决方案

#1


3  

That was an interesting one and took me some time to understand what was going on.

这很有趣,我花了一些时间才明白是怎么回事。

As you seem to have found out, the save() method documentation regarding the patch option states that it

您似乎已经发现,关于补丁选项的save()方法文档说明了它

Only save attributes supplied in arguments to save.

只保存参数中提供的要保存的属性。

So you just need to change your code to

所以你只需要把你的代码改成

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

and the set attributes will be saved.

设置属性将被保存。

BUT BUT BUT BUT

但是但是但是

ALL attributes will get into the update statement, even the id!

所有属性都将进入update语句,甚至id!

This is a common behavior for ORMs, its rationale is that if we got the data from one transaction and are saving from another one (bad, bad practice!), the data may have been changed by some other client. So saving only part of the attributes may lead to inconsistent state.

这是ORMs的一种常见行为,其基本原理是,如果我们从一个事务中获取数据,并从另一个事务中保存数据(糟糕的、糟糕的实践!)因此,只保存部分属性可能导致不一致的状态。

But the very existence of the patch attribute violates this concept. So Bookshelf could be improved by:

但是patch属性的存在就违背了这个概念。因此,可以通过:

  • Simply deprecating the patch option. (I could prefer that)
  • 简单地弃用补丁选项。(我希望)
  • Since Bookshelf models keep track of changed attributes I think it should be trivial to make the updates smarter on this regard. This change could lead too to the deprecation of the patch option.
  • 由于书架模型跟踪已更改的属性,因此我认为在这方面使更新更智能应该很简单。这种改变也可能导致补丁选项的废弃。
  • Another approach could be making the patch semantics relate to the changed attributes instead to just the ones supplied on the save(). But such change could unfortunately break some use cases.
  • 另一种方法是使补丁语义与已更改的属性相关,而不是只与save()上提供的属性相关。但是这样的改变很不幸地会破坏一些用例。
  • Or finally introducing a new option to act upon all changed attributes. But that feels messy.
  • 或者最终引入一个新的选项来处理所有已更改的属性。但这感觉混乱。

#2


1  

After a few iterations of guessing I seem to have gotten it working, but I have no idea if this is right or how I could have determined this without guessing, and I definitely do not stand by its correctness.

经过几次反复的猜测,我似乎已经成功了,但我不知道这是对的还是我本可以不经猜测就确定的,而且我绝对不支持它的正确性。

Basically I was able to get it to work by modifying the "update" case to:

基本上,我可以通过修改“更新”案例让它工作:

  • Pass the attributes to save as its first parameter, rather than setting them with set.
  • 将属性作为第一个参数保存,而不是设置为set。

Leading to a final solution of:

最终解决:

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);
    });
}

I'm still not sure how/if forge fits in here or what the deal should be with the first parameter to save in the "insert" case.

我仍然不确定forge如何/如果适合这里,或者在“插入”情况下第一个要保存的参数应该如何处理。

More importantly, I'm not entirely sure what set is for, now. One of the main benefits of an ORM framework is supposed to be making that kind of stuff transparent (i.e. "save" works correctly while letting you use the model without thinking about it, and without you having to have knowledge of what has changed at the point you "save" -- I should be able to have unknown arbitrary code set things ahead of time then be able to save it without knowing what changed, but it looks like I can't), so I'm not sure what I've actually gained from Bookshelf here. There must be a better approach.

更重要的是,我现在还不能完全确定是什么。ORM框架的主要好处之一应该是使这类内容变得透明(例如。“拯救”正常工作而让你使用该模型没有考虑它,并且你不必知道你改变的“拯救”——我应该能够提前有未知的任意代码组的事情能够保存它不知道改变了,但它看起来像我不能),所以我不确定我已经获得的书架。必须有更好的办法。

#1


3  

That was an interesting one and took me some time to understand what was going on.

这很有趣,我花了一些时间才明白是怎么回事。

As you seem to have found out, the save() method documentation regarding the patch option states that it

您似乎已经发现,关于补丁选项的save()方法文档说明了它

Only save attributes supplied in arguments to save.

只保存参数中提供的要保存的属性。

So you just need to change your code to

所以你只需要把你的代码改成

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

and the set attributes will be saved.

设置属性将被保存。

BUT BUT BUT BUT

但是但是但是

ALL attributes will get into the update statement, even the id!

所有属性都将进入update语句,甚至id!

This is a common behavior for ORMs, its rationale is that if we got the data from one transaction and are saving from another one (bad, bad practice!), the data may have been changed by some other client. So saving only part of the attributes may lead to inconsistent state.

这是ORMs的一种常见行为,其基本原理是,如果我们从一个事务中获取数据,并从另一个事务中保存数据(糟糕的、糟糕的实践!)因此,只保存部分属性可能导致不一致的状态。

But the very existence of the patch attribute violates this concept. So Bookshelf could be improved by:

但是patch属性的存在就违背了这个概念。因此,可以通过:

  • Simply deprecating the patch option. (I could prefer that)
  • 简单地弃用补丁选项。(我希望)
  • Since Bookshelf models keep track of changed attributes I think it should be trivial to make the updates smarter on this regard. This change could lead too to the deprecation of the patch option.
  • 由于书架模型跟踪已更改的属性,因此我认为在这方面使更新更智能应该很简单。这种改变也可能导致补丁选项的废弃。
  • Another approach could be making the patch semantics relate to the changed attributes instead to just the ones supplied on the save(). But such change could unfortunately break some use cases.
  • 另一种方法是使补丁语义与已更改的属性相关,而不是只与save()上提供的属性相关。但是这样的改变很不幸地会破坏一些用例。
  • Or finally introducing a new option to act upon all changed attributes. But that feels messy.
  • 或者最终引入一个新的选项来处理所有已更改的属性。但这感觉混乱。

#2


1  

After a few iterations of guessing I seem to have gotten it working, but I have no idea if this is right or how I could have determined this without guessing, and I definitely do not stand by its correctness.

经过几次反复的猜测,我似乎已经成功了,但我不知道这是对的还是我本可以不经猜测就确定的,而且我绝对不支持它的正确性。

Basically I was able to get it to work by modifying the "update" case to:

基本上,我可以通过修改“更新”案例让它工作:

  • Pass the attributes to save as its first parameter, rather than setting them with set.
  • 将属性作为第一个参数保存,而不是设置为set。

Leading to a final solution of:

最终解决:

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);
    });
}

I'm still not sure how/if forge fits in here or what the deal should be with the first parameter to save in the "insert" case.

我仍然不确定forge如何/如果适合这里,或者在“插入”情况下第一个要保存的参数应该如何处理。

More importantly, I'm not entirely sure what set is for, now. One of the main benefits of an ORM framework is supposed to be making that kind of stuff transparent (i.e. "save" works correctly while letting you use the model without thinking about it, and without you having to have knowledge of what has changed at the point you "save" -- I should be able to have unknown arbitrary code set things ahead of time then be able to save it without knowing what changed, but it looks like I can't), so I'm not sure what I've actually gained from Bookshelf here. There must be a better approach.

更重要的是,我现在还不能完全确定是什么。ORM框架的主要好处之一应该是使这类内容变得透明(例如。“拯救”正常工作而让你使用该模型没有考虑它,并且你不必知道你改变的“拯救”——我应该能够提前有未知的任意代码组的事情能够保存它不知道改变了,但它看起来像我不能),所以我不确定我已经获得的书架。必须有更好的办法。