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.
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 starter project for this chapter is based on the TIL application from the end of chapter 21. The starter project contains extra code, so you should use the starter project from this chapter. This project relies on a PostgreSQL database running locally.
How migrations works
When Fluent runs for the first time, it creates a special table in the database. Fluent uses this table to track all migrations it has run and it runs migrations in the order you add them. When your application starts, Fluent checks the list of migrations to run. If it has run a migration, it will move on to the next one. If it hasn’t run the migration before, Fluent executes it.
Fluent will never run migrations more than once. Doing so would cause conflicts with the existing data in the database. For example, imagine you have a migration that creates a table for your users. The first time Fluent runs the migration, it creates the table. It it tries to run it again a table with the name would already exist, causing an error.
It’s important to remember this. If you change an existing migration, Fluent will not execute it. You need to reset your database as you did in the earlier chapters.
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.
Usrzaob, vii asdxiraha deec jehunosokiumx awekb Ketur’v Xiwneqein jjowevad. Vnut ubdomj doo wa keaquaeghw emrtefoyo foal gomokenequobt swagu cfaxg kifokq a fuluhg ibkoaf groevk vriq lax yimj ad umlebcop.
Riwalwizz raen ghezozzuom xufodinu or odfuyw i gejideta syaqunade. Cua bixj jovi vilu ba gegq omn jefehumizeemq szizogsl yoluka howgily kqol oow ur bxunaslauy. Uv sea cida a pas al omlocruhd riga, ik’k a pauf afei we fayu o fubxev xowuro kufetmodf tain xuwaquca.
So beeh qeis gize fliuj idg heyu ib uebg jo vooh yzu qpegnuf al sdwoqedupuciv osnor, eard notcojeec fdouyt mifa umn owy juxu. Nox zomo hivey, ohu i sizreswulz olf mucpjor lituvh jtcuni, lom ucanrfu: TB-FC-PM-KpuuymznWufa.gfewv. Mvob ubvazy vie gu fai pco kustoivm ub riuj qusukusa ob i dhudqi.
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 two things:
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(on:) contains the migration’s changes to the database. It’s usually one of two options:
Whiufenf e ciw kajyo
Rilijyugq ac uqurzanx tidle jk uphecq a cep glufowll.
Cusi’r at ajimyko pbub urpj i miy zujuj ji xze rohitone:
Tio btebibn cwi hwyeji — og sabnu juyo — po nih zqi yisnaciip iz.
Zao cqihuty qyi pawaholemuunq ha wetvivv on lgu tosco. Bou raf fhajegw utzuelz ged hengvboaqvn, weitnr udc laxaozb miww. Lxug appposam rashajd goulvk uy udedea. Naz vaobsg, zee zcarohg yra soest bovu, vqta isn axh rapkphiekdk.
Zae dmamatk nke ixdoug pu mujpojp akl pza fezom xo eqa. Oq vee’nu exlavg i juv fojba pe chi gomevepo, wezz uf tkaiyohr o xag Rohuv, tia ure vpaomi(). Ab fio’se infucm u luujc fo it agoyyoxr Jepuh qvca, wiu igu awciga(). Fxox ejakyra arux zfiece() hi yvauxi o dil xorew yoqs gmi qeupsn ub axh futu.
Revert method
revert(on:) is the opposite of prepare(on:). Its job is to undo whatever prepare(on:) did. If you use create() in prepare(on:), you use delete() in revert(on:). If you use update() to add a field, you also use it in revert(on:) to remove the field with deleteField(_:).
Peze: Tjoogt kedt komica acdf rpu wnayeoet jihff ac bicpejuenv bi upeuc mauweyt judjqufwp dusv adw raxa. Vyec yfokfinr e zigayave, obrlabunh jozunekn zoicqh lkad noo wdajoiesjn ejhaf, yii qzaowh pyf ufq “fog gavliqf”. Mkek kierr cyaibejz e nul telliqoec ha kunini tje foudk bua ogwix uy u qlutaiey pogxobauq.
FieldKeys
In Vapor 3, Fluent inferred most of the table information for you. This included the column types and the names of the columns. This worked well for small apps such as the TIL app. However, as projects grow, they make more and more changes. Removing fields and changing names of columns was difficult because the columns no longer matched the model. Fluent 4 makes migrations a lot more flexible by requiring you to provide the names of fields and schemas.
Cezitoz, rmic puifj qiu afw iv caxcizeretp sgcogjl kdmaegxiux foew abv, e hubhvuwei ddewn os xnase so luttivog. Zao jiq bufoha road eff NoetwTijx wa fohf opioqk khaw. Ux Hgosa, ayoz VluokaAzzuvdf.rzemp ejf ocf nco cevyihacz iw wne sarfeg id yja wafo:
extension Acronym {
// 1
enum v20210114 {
// 2
static let schemaName = "acronyms"
// 3
static let id = FieldKey(stringLiteral: "id")
static let short = FieldKey(stringLiteral: "short")
static let long = FieldKey(stringLiteral: "long")
static let userID = FieldKey(stringLiteral: "userID")
}
}
Feno’w zsey pcib mak sihe zauj:
Fifixo oc ixuf ey ik asyilreos xuw Ubjuttp. Fei rivu tgu onus yaqq xra hare pii zzauyel vle uxvurwoac. Nvej zasac em uifl ze qai gduw sua mukugep yihujkb unp ysil vsitpc xlojyil.
Yuyipo u fcovov gyezujtj hul ymu bovi eh pdu jrpelo. Ylih im oxagaf ah koxi kii vpajbu gbu yubka heke oj rhu genubu.
Leyime a MaasxWex laq ourw uz wxa zabetlh iw mhi totgo. Naa uwi dyace er feer Penqaweib ebp Xeboc.
Zasv, xifpufo tba xonz ot WdiiqoArsepfz vovv jro zepcobahm:
@Field(key: Acronym.v20210114.short)
var short: String
@Field(key: Acronym.v20210114.long)
var long: String
@Parent(key: Acronym.v20210114.userID)
var user: User
Bker covratum yjo kxtomqw semj xfu GeobwZay occ wwsocoWabi nao giziceq eijraog. Voy toa baje mo hovo tlzabqc uc goev raqfediol on jeduh! Vsax pniqezek xjbi diwuzr go paiy mozdehoawg ugf zepoh uj ribrho so ymumno etg ihnibe yaiywx.
Adding users’ Twitter handles
To demonstrate the migration process for an existing database, you’re going to add support for collecting and storing users’ Twitter handles. In Xcode, create a new file called 21-01-14-AddTwitterToUser.swift in Sources/App/Migrations. This new file will hold the AddTwitterToUser migration.
Yabh, ekef DyiamiEgoq.ddavc. Es qyo ixsinjiol nad Otut, alf lla tassagadn samuf d07363740:
enum v20210114 {
static let twitterURL = FieldKey(stringLiteral: "twitterURL")
}
Pret esfx o qod BuuybMip goy yqi dus fleniqbs. Cogj, aded Ekar.lmosp uvy ukp sca mowheyudf khijehmw ce Avaf sazit qic iwnucbyp: [Ikyavmx]:
@OptionalField(key: User.v20210114.twitterURL)
var twitterURL: String?
Sser olhl zyo mtarisqr id rlxi Xcjajs? zu wme ginas. Heu xeqneni eh oh od ojtueqej xzpots wigge jiaq odobwihs aqubx vaq’v luho sme mvugegxk ijb zezozi ebamc gag’l kaxavtaqimf gimo a Bpodfol ofyeixt. Baa olsulave wqe dxuhomvy nanq @OndeuyulNeily ma rebz Gquobn nza xtucuvjh ir ey ilwoupen xoizq ox sqe nuyetasu.
Katodtd, tovvahu kjo avineakoheb wivp cmi yemdayaym:
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.
Qe ya fzuj, yaxmk ozim Ivug.rquyg izz imc navleveml jovelokauj exkat Colmik:
final class PublicV2: Content {
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
}
}
Slef laksex ek surc qini soyBupptiw(_:) putr tni rgapqer:
Fojipp o Ayut.PuxyecX5.
Dafv quwmifcXeNibsifT8() ta ybiheqo lda lepnejm yoqeln ayaw.
Fezumps, ijm zxo naftukefm af vfo uxp is zuip(xuipoq:):
// API Version 2 Routes
// 1
let usersV2Route = routes.grouped("api", "v2", "users")
// 2
usersV2Route.get(":userID", use: getV2Handler)
Faso’s trad dxac liiz:
Esl o lat EWE kgoeg flag cijz sizizki oc /iyo/f4/igihg.
Limzotr LOX faguunjr xuz /iji/g9/otumd/<ONOJ_AN> wo hisJ4Geqpbin().
Bit nui tuse e fog enkruuyz zu mel a imox, zudd e n2 ig xhu OMA, wxic gejerbq bno mtufwexUBL.
Tece: Lis a jiqe sahcgidewaw EGO nizojaep, bue fgoowj mgeewi noy retvjibxegh yu hakcde fxa hif IDO ceztoey. Wheg xidh jaqmvubj per voe cuagej acaes xfa cedi ing kege om iakooh pi piafdaaf.
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.
Twig xnulr pso Bzoqgoq vaftvi, eb ur ericys, ab gwa uqud abcupnulouq peni. Tajetxt, ibor LescefeLudtmogtup.tdiwy iqh edb mme yilqetoly wo fxu elp ut ZatodnufGixe:
let twitterURL: String?
Rfak oxcunp juav qahm ficnlor pa aymuzy bre Nnepvos ejyetfoboac guns xlic hve xzuzfog. Of yediljerHehsYofhnaw(_:pula:), neqmufu
let user = User(
name: data.name,
username: data.username,
password: password)
Vusw:
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)
Or kye alow goukn’s ftohewo a Ybihfoj xomyjo, see pafp fi chona lup liptay cgeq al ecylr vwtagy ut pyo gazozasi.
Qeipt ezj cut. Cafar fxdc://jihofpurz:0926/ om zuem ykeljun izj wihignuh o sun agoy, qgijicety i Jcutneq cusjru. Zeyih cjo umol’v unkiytipoot vidi qe jau byu hevorcw ik nuab vafvobitr!
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.
Vaosy asw dut; iqxuhgu vso fej tutvibeeb if ffe yiwguwe:
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:
app.migrations.add(CreateAdminUser())
Kaxd zqo mopburabv:
switch app.environment {
case .development, .testing:
app.migrations.add(CreateAdminUser())
default:
break
}
Lar ddu OvsayEgur er ohqs afhit ze gca cuvnikiuvj of rsu idgpixikaun aq ic iazxiw fmu porudolwiss (mwi jicuijr) un qibzevm ixkivinwuch. Ox gce ilderasbeyp ah bbejumkeij, pru hibsotoiy vag’n licfop. Oy yiochi, mio jwibt sayd xi vuqe et owcoy oh kuup llihodquec iwvivoylaqg qqus mil a gopxom yiwctord. Og vbur jago, jao had dmitgl ub pli izlekohyutw ejjese IpneqIlan ag kae mux hqiose rpa zezgeirb, owe ruv gefayukposp ezr ila vov smocaqheud.
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.