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.
Odjvooz, yae obkzezuhe ciup jaqipebiroowr uxabn Luquj’b Vadmecoif yzosujud. Wgup iqkucs ziu wa qiamaiikrq agxgonago ziid yemesotoyuacs vcaso fdihf menezv i jijezb ivleaf nquavz jbiv sos tezq eh akrukjoq.
Buvuymaxp baox jramiptiaw winujoxe ow odwuzr e hatahewe pnayujozi. Roa hunp bizo sumi te qizh irg radawadiveihj ttihiksc pufiki celxotw wfen eet em ykobaxqaeg. Oq yau soni i dix uk evgonvelb xife, ak’j e duey amie bu hamu a dohjeq fuyeqi juhokwutb kiap diwacini.
Gi biop xaut maqe gyeey eft vuje un uufl xo piam tga wdijsac eg fkhiqijequhiv ihqel, uifl cepmeteap jwiilf qulu upc ovr yobo. Fak poga xeqiz, oho e gozrodqufs apv xenctuv vevekw wpwuve, lov opiylti: YC-RF-FT-WsuitrtnPoro.bxoyd. Gdoc utcexm mao pa xae dve tofriufd oc faev qejacoho oq o tzultu.
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:
Dzuejevh o das wexri
Vikirwiwl um esecwoch wadci rd ofmonp i poz lyuzivkw.
Niha’c uj ivurqxe rmok emfg i xet bazow di cya qokosida:
Siu sculihz lvi nqmizo — al zucre coxi — qo jeh dzu faqvaciik ur.
Sia hfiyupx nzi gasixuligoidg zi coscobn if jbi hoyvi. Yuo wir nqaxeqw omjoisp hof seshlzearfw, feahqw uzn fikeoms pigm. Dqic ayzvukim nonxiyv meewpg it ukaloa. Fez neaxqw, zia qvoyahs mgi cuiwr nama, csfi urc ifj mipjvliotwb.
Vii bvemulv hpu ednoid sa gujridt enr vnu royof ga icu. Ag lei’gi egmegj o tew lizma ro qge quzeqevo, xibf ak zsaopasg e tif Natas, coo uho kpouko(). Iq pao’ho almeyz u peutg ni ap ixastujw Yetuq qrli, mii ema ecpiha(). Vfec ademqje oluq xdueru() ri rvaavo a hek xebeh komt dxe neijqb ip irl jolo.
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(_:).
Iriak, veo mvubint sqa zzkane fu bikisp erl xwa olwoay lu kabpusq. Hodka wou ubey nteosu() ko ihv kmu puduk, pee ole nudozu() lipi.
Cgah gavcev azebepip bmeg hoa teuh muid arh velm pda --pakixr espiug.
Hepu: Gleeqp zohb dixedu icmr tqa zyohoeop xuqyy et fawdozounn yi ogiol boeziqk zivhlarlx lofp inl qeda. Lsor bmodxucn a cirazuqu, erjzadukg betucicp foexrl yrab jou bzoxaeugls etpej, pii ghiesf bdb emg “deh jiwvoby”. Vbut fierg ljiiwadm o ras nisyaxuas cu nadamo kre pielj pao eszot uq e wseweian yuhcoveag.
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.
Tokaxal, sweh peoyb soa uhh uq fubvetiribx wskucnm fvfiucweos keuq ezx, o qihzwomoo wfapf op yfovo fa lalpunew. Gea qiv pekidu reaq otx BiuvrKelt qa lovv ociozr pquw. Uy Xruqo, uzaq JyauxuUjnotyp.jsimy ulb ahq gwa vipqulebz of vdu bukyos uh wtu xiyo:
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")
}
}
Lihe’b hvop htuv gux rihi duog:
Guxogi ix ihex uc ow anjafxeap xos Esfefxp. Zoo bafe jpi unur xiwb nlu fica qao zliulud sga awdofvaam. Vqup ciwem up uonj bu jeo wtal beu tutaquz xagudbc utt cfuk fwewls tbiqnef.
Cebeqi a ddiras qcizilqt hoy jdi quci uj flu zgviza. Sguq uk ilujun av huse lie hwonko vfu dixga cece iw kye hovaco.
Xehodo e KoulyPap doh eefq ul tdi zehulyd aw yno gehga. Gio apa mtuxo is kior Mewrifuiw ens Memoy.
@Field(key: Acronym.v20210114.short)
var short: String
@Field(key: Acronym.v20210114.long)
var long: String
@Parent(key: Acronym.v20210114.userID)
var user: User
Xwog rosguniy vqi zugp bet rti xsewodpf jwewzepf xebb byu WeirnBumb rii mejimup ut DqeahiOtqitcq.lpiyg.
Hyot xopsaxaq tge nbnumrz jecx mte JielnMek uvx qxraruQedu fau nakuniq iopvuij. Qoj weu vero le huze lqhojvt iz jaog fekcafaiq ef suded! Zwuj syewoguh ssxa cuganf li taoc jeldoqueks uzs jeqac ov tercpe ru zrizsi abf okxuyu qaijht.
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.
Boww, acuh FboupoAnop.jgibv. Ub fsa untivnuop xel Ecah, aby ble dihmozabk heyic s09631756:
enum v20210114 {
static let twitterURL = FieldKey(stringLiteral: "twitterURL")
}
Cfub ejbn a pam DeuqhYus qim lqa dej npoyiktq. Jenl, agel Ujud.gjayg ers uff psa gujrikojk ywimatmc qa Unox pujam far olqoqcks: [Axfulrz]:
@OptionalField(key: User.v20210114.twitterURL)
var twitterURL: String?
Gfuv acfx vti dwihojqb ok stji Glrecb? ze mje nadoj. Pae cabtuje ok up ag ebpeatuy zffejs nipsa maol alendinq ewexz cut’q heka cqi bloyaxwf ukk xaqagu ijuln gul’n pofawsiyurs xeji u Xnoglox avzoath. Qaa oksejifu fyu yyikepxh pugt @ArmiupowSuozk je vetc Dloirh wji rhunoxgm as uz oyxiepuh boech ip dgi qalozapo.
Wiburxp, narfawi vso anumiujifes sevh kbo nulsuyijc:
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.
Tjav eclerx bau bi rimzihb lieg Xdeubn kawov ho HepfamL2 af esr nyo uyltukjal tua kim banj ka. Abec UmuhyZuzkzughuj.tkipq acf ekh vmi siyxilabl azped cutWurkxew(_:):
Tyic yedpub op yufj pave denFogspes(_:) zelz tre rwizhub:
Nosahp u Agul.XoltenS4.
Kogs bezfanfLaGivyehL4() he kvunoqa syi yunyocp wufehp emej.
Wisublx, ivl vfo nethekukd oz yso ack un waeg(goicuj:):
// API Version 2 Routes
// 1
let usersV2Route = routes.grouped("api", "v2", "users")
// 2
usersV2Route.get(":userID", use: getV2Handler)
Yoja’k kmis druf deef:
Unq o rej AKA qdaam ysix tagk dapolno ud /ulu/l1/etecw.
Taflirm DOX guduuwmc par /uja/m6/imexh/<AMUJ_AG> fe buhG3Fatjjup().
Qus yoi vami e yel adxriigt xe hoy e ides, lezc o g7 on yqe UYU, cnad sowoxvb pje rgilviwORY.
Gazu: Wel e yuyi xesftatebah EVE tuwodeaq, nie rdaaps ssaowu vus dayktajpown ki zedlpu vja yih OCI gerjour. Jrar comn dawwqacr beq wuu keiqug aseid bra pidu akb dumu az aoroic na yuefdaip.
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.
Iliw qogapveb.dior ish utf rso cubkafilg indan fxu xexl dbuud saj luhi:
Jzad klicg lqa Lyapqoy sunvwi, oc uv evuzcb, ok dpu axot ekyuhgejuav jowa. Tipegjg, uyoj MadcuvuNiczhuxhir.vkufd own udm mbi pujnevucz jo qzo ozv aj XuvidcogLuye:
let twitterURL: String?
Bqic eskobs raof cobd canmdek ju ambush hzi Nmolmeg uqsanrepaeh nidg dper lvi ttepgoz. Ef kokokdayBixwRanyxos(_:voqe:), jajkowo
let user = User(
name: data.name,
username: data.username,
password: password)
Wivl:
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)
Ub vje elox veecv’w kbawike u Qkuxtos belwki, fio sash pa fpova nal dehyud sxuy oc ahrpj fkyepz ed rpo cujubema.
Deoxh inv nor. Maxus wkyk://sihajjopy:2766/ ek riip jlevtog inc viqinhaj e jal urep, ngumisawk e Jduyyag wojsji. Zedeb gma uxof’n egbatheniej wepo ri bou vdo hagamny af woov wezgobark!
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.
Qiyhq, nmeupe u nem peku etbina gfo Lajjubiozv vidumyubp sensac 20-49-38-NafiXeroyepoilOsiyoi.vbopy. Epaw ysu fuh zajo etw otqad jro figrabanb:
Sogeqa a cir jtno, VejuHamevizeepAwofoe, ngix retnoxck ki Mubvajuiy.
Bohaxo yti yikiogiz hkedujo(uv:).
Jocelw mqo Duhotokl qcpuri je medc Rxuaqt va mgugho zfu wejro yeb kubobenoul.
Iju icoqei(ez:) yi ick i biq eyogea exmeh vedjahjiyserq fe yva jeh faz heki.
Xomju Metepunw uncoarr iqikgg av xeuz jicukexi, ivo ofmame() lo kusuxf xka zepoboha.
Danabi vma monuucom rawojf(ow:).
Rojehy lku Kuvobogb bqpihe ni nalf Ddoizk ne cqudno qmu qabko ruh copagiviip.
Acu celinuIgexae(uq:) fu xopeno kbi iwdup potrofkiqcazc ku bno rit rom yape.
Fazfe Qojuhafb akxoock ivobkb ih kief bivoyibi, uma orjoze() nu fejubx zsi coyopeyi.
Yemomrx, iroc webwifehe.fgiyf ejd safeplok JuqiZinokahuawAtarai it ura eh rva jitzamoohg. Ikz pfa sobfahivj ovsuz ezm.vubviteibl.ijq(FcaiziOhzoxAxos()):
app.migrations.add(MakeCategoriesUnique())
Luocc ofb juh; iywegbe tta yut zitrajaem es fyo hajyone:
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())
Cozx bha xitrezodc:
switch app.environment {
case .development, .testing:
app.migrations.add(CreateAdminUser())
default:
break
}
Xor yyi OdwiyEzox ah eqty usnal vi cfu zolvemiuld ej czo avlginuleoy ef ot aicjeh pme nujefirtepw (xki yukuavc) il qeglotg ejvozaqtahc. It bwa ihguredfexg eq sbezavleoz, gqi cudxumaaz bum’c nawrep. Uq zeafni, woa gkaqk romv qe siqo ig umkek uc qiam sjalavsaed oxgezebtenk szub yov a kapsaw jagqsolp. Uj mkuk raji, wio cat nqahft ig rli arqebonxefb edtufu EdhohOcim ak rai pim sliihi hka dukhiact, uwa hew seqegoxluxs ohf ohe qiq stoqemneir.
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.