Skip to content

Instantly share code, notes, and snippets.

@maximkrouk
Last active May 30, 2020 14:25
Show Gist options
  • Save maximkrouk/10126931f43694cf0950025032362bdf to your computer and use it in GitHub Desktop.
Save maximkrouk/10126931f43694cf0950025032362bdf to your computer and use it in GitHub Desktop.
DEPRECATED
import Fluent
private struct _Migration<T>: Migration {
var preparation: (Database) -> EventLoopFuture<Void>
var revertion: (Database) -> EventLoopFuture<Void>
func prepare(on database: Database) -> EventLoopFuture<Void> { preparation(database) }
func revert(on database: Database) -> EventLoopFuture<Void> { revertion(database) }
}
public protocol MigrationProvider {
static func prepare(on database: Database) -> EventLoopFuture<Void>
static func revert(on database: Database) -> EventLoopFuture<Void>
}
extension MigrationProvider {
public static var migration: Migration {
_Migration<Self>(preparation: prepare, revertion: revert)
}
}
public protocol DatabaseModel: Model, MigrationProvider {
typealias SchemaBuilder = SchemaBuilderProxy<Self>
static func prepareSchema(using schemaBuilder: SchemaBuilder) -> EventLoopFuture<Void>
static func revertSchema(using schemaBuilder: SchemaBuilder) -> EventLoopFuture<Void>
}
extension DatabaseModel {
public static func prepare(on database: Database) -> EventLoopFuture<Void> {
prepareSchema(using: database.schema(for: Self.self))
}
public static func revert(on database: Database) -> EventLoopFuture<Void> {
revertSchema(using: database.schema(for: Self.self))
}
}
//
// SchemaBuilder+Extension.swift
// App
//
// Created by Maxim Krouk on 3/17/20.
//
import Fluent
// MARK: - Pass by reference
@propertyWrapper
private final class Ref<Object> {
public var wrappedValue: Object
public convenience init(_ object: Object) {
self.init(wrappedValue: object)
}
public init(wrappedValue: Object) {
self.wrappedValue = wrappedValue
}
}
@dynamicMemberLookup
public struct SchemaBuilderProxy<T: Fluent.Model> {
@Ref private var wrappedValue: SchemaBuilder
/// Empty model, used to access fields by keypath
private let model: T = .init()
// MARK: Initialization
internal init(wrappedValue: SchemaBuilder) {
self._wrappedValue = .init(wrappedValue)
}
// MARK: DynamicMember
public subscript<T>(dynamicMember keyPath: KeyPath<SchemaBuilder, T>) -> T {
get { wrappedValue[keyPath: keyPath] }
}
public subscript<T>(dynamicMember keyPath: WritableKeyPath<SchemaBuilder, T>) -> T {
get { wrappedValue[keyPath: keyPath] }
set { wrappedValue[keyPath: keyPath] = newValue }
}
// MARK: - FieldRepresentableSupport
public func field<V: FieldRepresentable>(
_ keyPath: KeyPath<T, V>,
_ dataType: DatabaseSchema.DataType,
_ constraints: DatabaseSchema.FieldConstraint...
) -> Self {
self.field(.definition(
name: .string(model[keyPath: keyPath].field.key),
dataType: dataType,
constraints: constraints
))
}
public func unique<V: FieldRepresentable>(on keyPaths: KeyPath<T, V>...) -> Self {
wrappedValue.schema.constraints.append(.unique(
fields: keyPaths.map { .string(model[keyPath: $0].field.key) }
))
return self
}
public func foreignKey<V: FieldRepresentable>(
_ keyPath: KeyPath<T, V>,
references foreignSchema: String,
_ foreignField: String,
onDelete: DatabaseSchema.Constraint.ForeignKeyAction = .noAction,
onUpdate: DatabaseSchema.Constraint.ForeignKeyAction = .noAction
) -> Self {
self.foreignKey(
model[keyPath: keyPath].field.key,
references: foreignSchema,
foreignField,
onDelete: onDelete,
onUpdate: onUpdate
)
}
public func deleteField<V: FieldRepresentable>(_ keyPath: KeyPath<T, V>) -> Self {
deleteField(model[keyPath: keyPath].field.key)
}
}
// MARK: - SchemaBuilderProxy.SchemeBuilderSupport
extension SchemaBuilderProxy {
public func field(
_ name: String,
_ dataType: DatabaseSchema.DataType,
_ constraints: DatabaseSchema.FieldConstraint...
) -> Self {
field(.definition(
name: .string(name),
dataType: dataType,
constraints: constraints
))
}
public func field(_ field: DatabaseSchema.FieldDefinition) -> Self {
wrappedValue.schema.createFields.append(field)
return self
}
public func unique(on fields: String...) -> Self {
wrappedValue.schema.constraints.append(.unique(
fields: fields.map { .string($0) }
))
return self
}
public func foreignKey(
_ field: String,
references foreignSchema: String,
_ foreignField: String,
onDelete: DatabaseSchema.Constraint.ForeignKeyAction = .noAction,
onUpdate: DatabaseSchema.Constraint.ForeignKeyAction = .noAction
) -> Self {
wrappedValue.schema.constraints.append(.foreignKey(
fields: [.string(field)],
foreignSchema: foreignSchema,
foreignFields: [.string(foreignField)],
onDelete: onDelete,
onUpdate: onUpdate
))
return self
}
public func deleteField(_ name: String) -> Self {
deleteField(.string(name))
}
public func deleteField(_ name: DatabaseSchema.FieldName) -> Self {
wrappedValue.schema.deleteFields.append(name)
return self
}
public func delete() -> EventLoopFuture<Void> {
wrappedValue.delete()
}
public func update() -> EventLoopFuture<Void> {
wrappedValue.update()
}
public func create() -> EventLoopFuture<Void> {
wrappedValue.create()
}
}
// MARK: - SchemaBuilder.Binding
extension SchemaBuilder {
public func bind<T: Model>(to type: T.Type) -> SchemaBuilderProxy<T> {
.init(wrappedValue: self)
}
}
// MARK: - Model.Schema
extension Model {
static var schema: String { String(describing: self) }
}
// MARK: - Database.Schema
extension Database {
public func schema<T: Model>(for type: T.Type) -> SchemaBuilderProxy<T> {
schema(T.schema).bind(to: type)
}
}
@maximkrouk
Copy link
Author

maximkrouk commented Mar 30, 2020

The issue with your implementation is the reason why models and migrations have been decoupled in the first place - as soon as you change a column name, or delete a column then it's going to blow up
© 0xTim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment