In the last 2 episodes we added some behavior to add automatically managed timestamp fields and some fairly complex logic to set up UUID primary keys the way we want. Now if we want to share those, or make them the default for our models, we currently have to copy & paste. In this episode we will refactor this logic into reusable protocols so that our work can be applied on any model we wish easily.
Creating an Issue Model Let's create a new model for storing Issues for our project: final class Issue : Model { // A bunch of boilerplate copied from Project static var name: String = "issues" enum Status : String, PostgreSQLEnum { case open case closed case wontFix } var id: UUID? var createdAt: Date? var updatedAt: Date? var subject: String var body: String var status: Status = .open } Having to copy & paste a bunch of code to make our models consistent isn't ideal. Let's extract some of this logic so we can reuse it. Extracting a Protocol For UUID Primary Keys protocol UUIDModel : Model, PostgreSQLTable where Self.ID == UUID, Self.Database == PostgreSQLDatabase { var id: UUID? { get set } } extension UUIDModel { static var idKey: IDKey { return \.id } } Here you can see we're defining our primary key property and also declaring that this uses a Postgres database. Extracting the UUID migration logic into a helper Most of the customized logic is actually when we run our migrations. If we extract this into a protocol on any type that implements UUIDModel, we can easily reuse this in other models. extension SchemaCreator where Model : UUIDModel { func uuidPrimaryKey() { let pk = PostgreSQLColumnConstraint.primaryKey(default: nil, identifier: nil) let defaultUUID = PostgreSQLColumnConstraint.default(.function("uuid_generate_v4"), identifier: nil) field(for: \.id, type: .uuid, pk, defaultUUID) } } Extracting a Protocol For Timestamps Next let's look at extracting the timestamp logic. protocol TimestampModel : Model { var createdAt: Date? { get set } var updatedAt: Date? { get set } } extension TimestampModel { static var createdAtKey: TimestampKey? { return \.createdAt } static var updatedAtKey: TimestampKey? { return \.updatedAt } } And again, we can do the same trick above to make the migrations easier: extension SchemaCreator where Model : TimestampModel { func timestampFields() { field(for: \.createdAt) field(for: \.updatedAt) } } Cleaning up our models If we look at Project.swift now, it looks a lot cleaner: final class Project : UUIDModel, TimestampModel { static var name: String = "projects" var id: UUID? var title: String var description: String var createdAt: Date? var updatedAt: Date? init(title: String, description: String) { self.title = title self.description = description } } extension Project : Migration { static func prepare(on conn: PostgreSQLConnection) -> Future<Void> { return PostgreSQLDatabase.create(self, on: conn) { builder in builder.uuidPrimaryKey() builder.field(for: \.title, type: .varchar(500)) builder.field(for: \.description) builder.timestampFields() } } } We are also set up to use this on any of our models going forward, keeping things consistent.