[英]Generic fluent query with protocol in Vapor 4
I have several models that have a date field with the same name like this:我有几个具有相同名称的日期字段的模型,如下所示:
final class ModelA: Model {
static let schema = "model_a"
@Timestamp(key: "modification_date", on: .update, format: .unix)
var modificationDate: Date?
}
final class ModelB: Model {
static let schema = "model_b"
@Timestamp(key: "modification_date", on: .update, format: .unix)
var modificationDate: Date?
}
final class ModelC: Model {
static let schema = "model_c"
@Timestamp(key: "modification_date", on: .update, format: .unix)
var modificationDate: Date?
}
At some point in my code, I would like to get recently changed models.在我的代码中的某个时刻,我想获得最近更改的模型。 So I could write something like this:
所以我可以写这样的东西:
func getRecentlyChangedModelAs(on db: Database) async throws -> [ModelA] {
return try await ModelA.query(on: db)
.group(.and) { group in
group.filter(\.$modificationDate != nil)
group.filter(\.$modificationDate > Date.now.addingTimeInterval(TimeInterval(-3600)))
}
.all()
}
func getRecentlyChangedModelBs(on db: Database) async throws -> [ModelB] {
return try await ModelB.query(on: db)
.group(.and) { group in
group.filter(\.$modificationDate != nil)
group.filter(\.$modificationDate > Date.now.addingTimeInterval(TimeInterval(-3600)))
}
.all()
}
func getRecentlyChangedModelCs(on db: Database) async throws -> [ModelC] {
return try await ModelC.query(on: db)
.group(.and) { group in
group.filter(\.$modificationDate != nil)
group.filter(\.$modificationDate > Date.now.addingTimeInterval(TimeInterval(-3600)))
}
.all()
}
As you can see, there is a lot of code duplication.如您所见,有很多代码重复。 In order to avoid this, I tried to use a protocol and a generic query instead:
为了避免这种情况,我尝试使用协议和通用查询:
protocol ModifiableModel: Model, Content {
var modificationDate: Date? { set get }
}
final class ModelA: ModifiableModel {
static let schema = "model_a"
@Timestamp(key: "modification_date", on: .update, format: .unix)
var modificationDate: Date?
}
etc...
func getRecentlyChangedModel<T: ModifiableModel>(on db: Database) async throws -> [T] {
return try await T.query(on: db)
.group(.and) { group in
group.filter(\.$modificationDate != nil) // Errors: 'nil' is not compatible with expected argument type 'KeyPath<Right, RightField>', Generic parameter 'RightField' could not be inferred
group.filter(\.$modificationDate > Date.now.addingTimeInterval(TimeInterval(-3600))) // Errors: Cannot convert value of type 'Date' to expected argument type 'KeyPath<Right, RightField>', No exact matches in call to instance method 'filter'
}
.all()
Unfortunately, this causes errors in the filter of the query.不幸的是,这会导致查询过滤器出错。 I do not quite understand these errors but I found out that without the filter, everything compiles just fine.
我不太明白这些错误,但我发现没有过滤器,一切都编译得很好。 My best guess would be that the errors have something to do with the key paths I try to use within the filter.
我最好的猜测是错误与我尝试在过滤器中使用的关键路径有关。
Does anyone know what the problem is or how to fix it?有谁知道问题是什么或如何解决? Or is there another approach that I can use to avoid this kind of code duplication?
还是有另一种方法可以用来避免这种代码重复? Any help would be much appreciated!
任何帮助将非常感激!
I don't think this approach can be made to work.我认为这种方法行不通。 The clue is in the error message you get by putting
T
in the filter as in:线索在您通过将
T
放入过滤器中得到的错误消息中,如下所示:
group.filter(\T.$modificationDate != nil)
Gives error:给出错误:
Generic parameter 'Field' could not be inferred and Value of type 'T' has no member '$modificationDate'
无法推断通用参数“字段”并且类型“T”的值没有成员“$modificationDate”
The problem is due to the fact that filter
is expecting a property defined with a field wrapper (in this case, @Timestamp
).问题是由于
filter
期望使用字段包装器定义的属性(在本例中为@Timestamp
)。 However, as you have probably discovered putting:但是,正如您可能已经发现的那样:
protocol ModifiableModel {
@Timestamp(key: "modification_date", on: .update, format: .unix)
var modificationDate: Date?
}
results in an error message that you cannot define variables with wrappers in protocols.导致您无法在协议中使用包装器定义变量的错误消息。 Since your approach depends on this being the case, I don't think it can be made to work.
由于您的方法取决于这种情况,因此我认为它无法正常工作。
EDIT:编辑:
I forgot to add that you should be able to simplify your code to just filtering on the value.我忘了补充一点,您应该能够将代码简化为仅对值进行过滤。 There shouldn't be any need to test for non-nil first.
不应该首先测试非零。 Try:
尝试:
func getRecentlyChangedModelAs(on db: Database) async throws -> [ModelA] {
return try await ModelA.query(on: db)
.filter(\.$modificationDate > Date.now.addingTimeInterval(TimeInterval(-3600)))
.all()
}
If you are getting the wrong result, then check your migration to make sure there isn't something wrong here.如果您得到错误的结果,请检查您的迁移以确保这里没有问题。
As it turns out, there is a solution, even two actually.事实证明,有一个解决方案,实际上甚至有两个。 They are not perfect in the sense that a small part of the code still needs to be duplicated, but it's still much better than any alternative I know.
它们并不完美,因为一小部分代码仍然需要复制,但它仍然比我知道的任何替代方案要好得多。
This is the first way:这是第一种方式:
protocol ModifyableModel: Model {
var modified: KeyPath<Self, TimestampProperty<Self, UnixTimestampFormat>> { get }
}
extension ModifyableModel {
func getRecentlyModifiedModels(on db: Database) async throws -> [Self] {
try await Self.query(on: db)
.filter(Self().modified > Date.now.addingTimeInterval(TimeInterval(-3600))
.all()
}
}
extension ModelA: ModifyableModel {
var modified: KeyPath<User, TimestampProperty<User, UnixTimestampFormat>> {
return \.$modificationDate
}
}
As you can see, the variable modified
needs to be implemented separately for every Model that conforms to ModifyableModel
.如您所见,需要为每个符合
ModifyableModel
的 Model 单独实现变量modified
。 Just calling it modificationDate
instead doesn't work.只是将其称为
modificationDate
是行不通的。 It's not perfect but it's a small price to pay.它并不完美,但付出的代价很小。 If anyone figures out a way to avoid this, please let me know.
如果有人想办法避免这种情况,请告诉我。
Anyway, here is the other way of doing it.无论如何,这是另一种方法。 Personally, I think it's even a little bit more elegant without the KeyPath:
就个人而言,我认为没有 KeyPath 会更加优雅:
protocol ModifyableModel: Model {
var modified: TimestampProperty<Self, UnixTimestampFormat> { get }
}
func getRecentlyModifiedModels<T: ModifyableModel>(on db: Database) async throws -> [T] {
return try await T.query(on: db)
.filter(\T.modified > Date.now.addingTimeInterval(TimeInterval(-3600))
.all()
}
extension ModelA: ModifyableModel {
var modified: TimestampProperty<User, UnixTimestampFormat> {
return self.$modificationDate
}
}
Thank you, Mahdi BM @ Discord, for your help!谢谢您,Mahdi BM @ Discord,您的帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.