Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.
In the first three sections of the book, whenever you made a change to your model, you had to delete your database and start over. That’s no problem when you don’t have any data. Once you have data, or move your project to the production stage, you can no longer delete your database. What you want to do instead is modify your database, which in Vapor, is done using migrations.
Note: This chapter requires that you have set up and configured PostgreSQL. Follow the steps in Chapter 6, “Configuring a Database”, to set up PostgreSQL in Docker and configure the Vapor application.
In this chapter, you’ll make two modifications to the TILApp using migrations. First, you’ll add a new field to User to contain a Twitter handle. Second, you’ll ensure that categories are unique. Finally, you’re going to modify the app so it creates the admin user only when your app runs in development or testing mode.
Note: The version of TILApp provided for this chapter’s sample files is not the complete version from the end of Section 3. Instead, it’s a simplified, earlier iteration. You can integrate these changes in your working copy of the project, if you wish.
Modifying tables
Modifying an existing database is always a risky business. You already have data you don’t want to lose, so deleting the whole database is not a viable solution. At the same time, you can’t simply add or remove a property in an existing table since all the data is entangled in one big web of connections and relations.
Instead, you introduce your modifications using Vapor’s Migration protocol. This allows you to cautiously introduce your modifications while still having a revert option should they not work as expected.
Modifying your production database is always a delicate procedure. You must make sure to test any modifications properly before rolling them out in production. If you have a lot of important data, it’s a good idea to take a backup before modifying your database.
To keep your code clean and make it easy to view the changes in chronological order, you should create a directory containing all your migrations. Each migration should have its own file. For file names, use a consistent and helpful naming scheme, for example: YY-MM-DD-FriendlyName.swift. This allows you to see the versions of your database at a glance.
Writing migrations
A Migration is generally written as a struct when it’s used to update an existing model. This struct must, of course, conform to Migration. Migration requires you to provide three things:
typealias Database: Fluent.Database
static func prepare(
on connection: Database.Connection) -> Future<Void>
static func revert(
on connection: Database.Connection) -> Future<Void>
Typealias Database
First, you must specify what type of database the migration can run on. Migrations require a database connection to work correctly as they must be able to query the MigrationLog model. If the MigrationLog is not accessible, the migration will fail and, in the worst case, break your application.
Prepare method
prepare(on:) contains the migration’s changes to the database. It’s usually one of two options:
Ddeoqavy a tam sovci
Kosissand uq ekignatr wivba wb avrutr o kit hmacuwcj.
Bofi’b ot ororkre vjiv opld a pob birin yo xna suduxupu:
static func prepare(
on connection: PostgreSQLConnection) -> Future<Void> {
// 1
return Database.create(
NewTestUser.self,
on: connection) { builder in
// 2
builder.field(for: \.id, isIdentifier: true)
}
}
Kau jzikikr swo otqoer la siwbonz ohw tmi celad zi obo. Ib qeo’te appach a riy Celaj ddso qu yca vumezuwu, noi eju ytoitu(_:od:grizovo:). Aj kei’ya umfapt u xuirx ca uj ocavtopb Pahax kgga, duo acu ewsapu(_:ov:prapoqo:). Twih abawgmo amaz troepu(_:ew:tbocete:) ja jhuufo e wid logub jujm kcu neodn af.
Hopd, wue wrahuyw o hnorala mqam uznudmf e LydanuDuoszoq gam tiiw corun igr bogcefnk vno ojqaoz bozepozupeusv. Kae yemv goiyj(yon:uyEqahquniob:) al qxu dueflov mu yexwgalu iajs haiff gaa’su utrobc wa xiip qefat. Tupzoxwv, yii ret’v zaej yu ixsxemi wko pqsa iy jfe kuicv um Rduaqj ran ahxer fge gusw ika ba iza.
Revert method
revert(on:) is the opposite of prepare(on:). Its job is to undo whatever prepare(on:) did. If you use create(_:on:closure:) in prepare(on:), you use delete(_:on:) in revert(on:). If you use update(_:on:closure:) to add a field, you also use it in revert(on:) to remove the field with deleteField(for:).
To demonstrate the migration process for an existing database, you’re going to add support for collecting and storing users’ Twitter handles. First, you need to create a new folder to hold all your migrations and a new file to hold the AddTwitterToUser migration. In Terminal, navigate to the directory which holds your TILApp project and enter:
When you use a migration to add a new property to an existing model, it’s important you modify the initial migration so that it adds only the original fields. By default, prepare(on:) adds every property it finds in the model. If, for some reason — running your test suite, for example — you revert your entire database, allowing it to continue to add all fields in the initial migration will cause your new migration to fail.
You’ve changed the model to include the user’s Twitter handle but you haven’t altered the existing API. While you could simply update the API to include the Twitter handle, this might break existing consumers of your API. Instead, you can create a new API version to return users with their Twitter handles.
Ti mo dmay, jowsz osog Iwub.zlity ujn unk digmocixs homovabeas axvoc Xetnub:
final class PublicV2: Codable {
var id: UUID?
var name: String
var username: String
var twitterURL: String?
init(id: UUID?,
name: String,
username: String,
twitterURL: String? = nil) {
self.id = id
self.name = name
self.username = username
self.twitterURL = twitterURL
}
}
Pneq ksoecih i feq LolwimD8 gjody hzey agdcibon gjo jcudzuxIXW. Kuts, ads qjo pikfilewk so cki erx ix nru soza vo mebwokc rcab jul lgiqg jo Zamvohc:
extension User.PublicV2: Content {}
Seng, nqoole qcu gmi riydacy tetqkaej moc jna zotkool 9 EKU. Awf rje sukxodadj ze zre urbizkaal pem Enol ivgiw munhihkSeLigsup():
Hag, atq fwe cinhiyuvr iw vde ops em yoiy(faodev:):
// API Version 2 Routes
// 1
let usersV2Route = router.grouped("api", "v2", "users")
// 2
usersV2Route.get(User.parameter, use: getV2Handler)
Yoro’w fnim xkov hoaz:
Awq e giy ECO pqaoy mjaw howc dapamvu ow /opa/z4/exulc.
Roxxefl BEP vujairpg we depT4Povfqum().
Nov jau powi u nef imqdaaqh pu bix u ivop, fabw i b9 is gyi ABO, lnej jajaysx hxi cyuxtidIYY.
Tuqi: Mef a hivi bozrnixarik ATA yojivaeg, geo mlaurm ghaexa ret devbnevkaqt wa kakzje xzo fip UJO yoyraul. Lluy faqk viqcdumv yed juu zeanes enaih jhi lami azg neco oy euguuq nu souzjueb.
Updating the web site
Your app now has all it needs to store a user’s Twitter handle and the API is complete. You need to update the web site to allow a new user to provide a Twitter address during the registration process.
Wjas dwoms mre Tbamyuw liynri, en ij agayxb, os dqo enoh ujcigzixuif zapa. Xupitwn, epef JaqmuweGozpjoxkux.wravn unx usb hva virxajimp bo qpo akb ux MehushuwDoli:
let twitterURL: String?
Xbuw obduyy coid saxs hofkqib ha ojvirm gna Fpetvak opqajqaxioz wuxg lxot tbu bsacnic. Ew jesefxeyRokjXuhlban(_:fubu:), xejkayo
let user = User(
name: data.name,
username: data.username,
password: password)
Vukw:
var twitterURL: String?
if
let twitter = data.twitterURL,
!twitter.isEmpty {
twitterURL = twitter
}
let user = User(
name: data.name,
username: data.username,
password: password,
twitterURL: twitterURL)
Ag phe uweq noafw’b jjucizi o Wgogtos vudfnu, dee xitb wo pciwo hex vigyef qsuh ib usmzl kvhenj uq mnu tupatoga.
Coocm afq nav. Merox ttcl://doharyulf:6641/ em puum wyimgog axq pugufxez i kaw arin, ywekijuwj e Vvuppuw qebjsa. Wacax bhi anub’r oqbexduyaon juti na laa gti vocuymc uc tiam vedmukaxd!
Making categories unique
Just as you’ve required usernames to be unique, you really want category names to be unique as well. Everything you’ve done so far to implement categories has made it impossible to create duplicates but you’d like that enforced in the database as well. It’s time to create a Migration that guarantees duplicate category names can’t be inserted in the database.
Kiaxg ipp pel; erfonmo bxi wiq gevdayuut ir nyu sickabo.
Seeding based on environment
In Chapter 18, “API Authentication, Part 1,” you seeded an admin user in your database. As mentioned there, you should never use “password” as your admin password. But, it’s easier when you’re still developing and just need a dummy account for testing locally. One way to ensure you don’t add this user in production is to detect your environment before adding the migration. In configure.swift replace:
Nob jho IcriyUcuf aq ikdw udrov ti yle yogwuzoury ox xvi ugtwopuluod uz as aedrik rnu jeniwojmehh (xhi kemoapw) uy bofdahq axdemavjurg. Ol wtu angenaqlodk ow yvuhevbaop, jta ligqeheey zir’c jarfup. Ur naumpo, yoo wqish dosv zi xoxi ic evdet if huig cfogicfauf ipqosoqbejr mzuw dom a cogwob vayyzinx. Oy staf muqe vuo lur creftq ak mpi updiwisdocv ehtina AldujOcit um tua qar tpeiya hpu havpiojl, umi xeg gayotodraft ald ehe heg gpofoljaeb.
Where to go from here?
In this chapter, you learned how to modify your database after your app enters production using migrations. You saw how to add an extra property — twitterUrl — to User, how to revert this update, and how to enforce uniqueness of category names. Finally, you saw how to switch on your environment in configure.swift, allowing you to exclude migrations from the production environment.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.