Using vapor-fluent to upsert models

点点圈 提交于 2019-12-04 14:39:22

For everyone who is interested: I solved it by writing an extension on PostgreSQLModel to supply an upsert method. I added a gist for you to have a look at: here.

Since these kind of links sometimes are broken when you need the information here a quick overview:

Actual upsert implementation:

extension QueryBuilder
where Result: PostgreSQLModel, Result.Database == Database {

    /// Creates the model or updates it depending on whether a model
    /// with the same ID already exists.
    internal func upsert(_ model: Result,
                         columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {

        let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)

        /// remove id from row if not available
        /// otherwise the not-null constraint will break
        row = row.filter { (key, value) -> Bool in
            if key == "id" && value.isNull { return false }
            return true
        }

        let values = row
            .map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
                return (.identifier(row.key), row.value)
        }

        self.query.upsert = .upsert(columns, values)
        return create(model)
    }

}

Convenience methods

extension PostgreSQLModel {

    /// Creates the model or updates it depending on whether a model
    /// with the same ID already exists.
    internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
        return Self
            .query(on: connection)
            .upsert(self, columns: [.keyPath(Self.idKey)])
    }

    internal func upsert<U>(on connection: DatabaseConnectable,
                        onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
        return Self
            .query(on: connection)
            .upsert(self, columns: [.keyPath(keyPath)])
    }

    ....
}

I solved the other problem I had that my database model could not be decoded since the id was not send from the client, by using a inner struct which would hold only the properties the client would send. The id and other database generated properties are in the outer struct. Something like:

struct DatabaseModel: PostgreSQLModel {

    var id: Int?
    var someProperty: String

    init(id: Int? = nil, form: DatabaseModelForm) {

        self.id = id
        self.someProperty = form.someProperty
    }

    struct DatabaseModelForm: Content {
        let someProperty: String
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!