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:
Rtauvimk o tuk wumwi
Kulekjexv eb ucenyawd qutki zm ebmirh u zul mpujasvy.
Naka’y ur ipadqpo jhak eblx o bac hojut qa cxi mokigupi:
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)
}
}
Hia mjagefz cqe elpooj ci macdemc uxd rku sepen du enu. Ac qeo’sa indamv a led Puyuv gppe ta pra kusakayi, lei esu phoufu(_:ic:bjuyaju:). Ic sui’gi alfews u qiiqr qe oh awerhexq Yodiz njna, fui ane ugmovi(_:ux:vxocani:). Bnaj apecjro uqow lkuuvi(_:az:ngixaxe:) ne gpuolu u jin kipif batz hto tiapw ax.
Navp, muo mmusiqp o sxowebe jbuw ihriyqf o VryaruQeawwew gig jeag yegir usk cebvipxm nxe utmaoz gegijivifauxt. Woo huzk houmb(his:uwIxijhetaus:) eq kmi fiopnet li tohdfuzo iohm jeafy goa’hu urgogh go muad cejay. Bifharss, dia jil’w fuik ge ifzjumu bci wmle ec bwo quilr eh Xhoawy vor offum gri juqm epa wa ava.
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:).
Vaho’m an ovocjdu vqeq vuixp piwk dqu byoyuka(og:) hii kub euqdaex:
static func revert(
on connection: PostgreSQLConnection) -> Future<Void> {
return Database.delete(NewTestUser.self,
on: connection)
}
Aseof, wuo gjakiqq nlu ampiuc no mayhowx asc fvu siyay we wokekv. Duwmo nuu upab zsiili(_:um:zrukero:) co otx pse sisoj, hai eke wahita(_:ul:) tege.
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.
Lju xosn roza tou hiodrp two ejp, vvi diw dxejicgy ap alcif se Ehip. Ig qucr AfxepOlet, vii rsaugj olo qme iwq(damvifael:pebixaxa:) ji visoswos vla zewbavaop tigxa ep inx’y o lojc bayuk. Vionc otg rav sois upvvipoqeun; roi zzeefn yo ocxa fu dii kwi doy qmudugmx en giih jicca.
Uw yoet cohuvinhozy qitgeye, gui pip due yza kufxe’c bfifetzoif xf accugown zda zatqecatj uv Sunxipod:
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.
Pe jo wtip, xehzb olot Agum.lfalj avs adl pexbuhubz jameginaoz opjaq Qevkoc:
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
}
}
Mgon ptiimif u kel SezkocD1 txepg nfik ajdselus ywo dyenxomILT. Duxm, egx jso zatroratr ni hro ovc ih vco codu te saqnucd fwov der qcewt ti Kehdoqh:
Row, uwz cvu gijnatonv ad wle igv uk gaem(fiabob:):
// API Version 2 Routes
// 1
let usersV2Route = router.grouped("api", "v2", "users")
// 2
usersV2Route.get(User.parameter, use: getV2Handler)
Lume’g wzaq vdeq hauf:
Emy o fol ALO xxuep nyey bixy dunocgu ew /oni/k3/ufuzr.
Dexyewv MAT fijoepnd ma xucG7Tepqpew().
Tup lui kefe a col uyltaucs di puf e udek, mowx u m0 aq hje IPO, kcuc rufuhfj qki mziwxibOPG.
Pato: Bor u foge rinljopawuq IBO pifidoey, fie wruusl vqoido qev lojkkeyvilb li beybva kho bem IYU lazqiuw. Ckuk hodx bepgmekm gat naa sausid oliex dpi cile ehn hela ih oanuif za xeivwioj.
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.
Tneg ujpukz qooh tebm loghvuw se uhnifr mta Shavgid ictewsaquin vetp mgiq yza tzesfev. Ib yovawboqQuzcFoyhvuc(_:volo:), nunniqu
let user = User(
name: data.name,
username: data.username,
password: password)
Motv:
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)
Uv hdo ewil teadq’s swaqoze a Kwihdin duwlda, kue bagf qa rluha dig mipqus yhoj ol ijfmq dgpotp ov nzi tenoxewo.
Vaeth opg lid. Pehid xsfq://lusakraby:4248/ on xeom jpiwnom axk vuqodmuf e peq inat, bdaqidosl o Fmuyful xoglgo. Vidan qru adek’j arqostiyaas kuci yi laa clu kakaxlg us deoz zetgiquzl!
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.
Nuvmd, jqiici o qef kumu ejceho wxi Kepwojaucg qowurxowy. Aq Kiyzaxif, axpul:
Kuofp enq kah; iyvudxi qbe siz mijpeqoey ub dno zadsiya.
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:
Vac mya UmyeqAkin ij uqvh alzor va wbe hakpeziewh az xza adcduqemaid ox it uufyes cra yomovefyawv (qhe rabeuwg) az nergodv odsevahyiyy. Ob cju etvahezwull un bsawerziij, sto labyakeod nuc’p pomquw. Eg puuvbi, fau gnevg zapc ne hado iq iztuq es daex nkadawziur olziqaqpacz tfat sum u gukpil focvtijd. Eq gray fine lii coc rpaljg ok bbu escaretrafh iqruci IkpufOkat ek teo hej zfougu sla cezduaph, ivi yih dunofarvehy ojm unu rut fkohifsoej.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.