This workshop will be retired on May 31, 2020.
Creating Models9:29 with Pasan Premaratne
Using our API we're going to create, read, update and delete courses and reviews for each course so we need two models - a course and a review
rm -rf Sources/App/Models/* rm -rf Sources/App/Controllers/*
touch Sources/App/Models/Course.swift touch Sources/App/Models/Review.swift
Using our API we're going to create read, update and delete courses and 0:00 reviews for each course. 0:04 So we need two models, a course and a review. 0:06 Now, fire up your terminal and navigate back to that project directory. 0:09 In here we're going to first delete everything that's in the models and 0:12 controller's directory. 0:17 We'll use the rm command, which is short for remove, along with the r and 0:19 f flags, which are short for recursive and forced. 0:23 So, the r flag is going to remove all directories at the path recursively, 0:27 while the f flag indicates that we don't need to be prompted about this. 0:31 So, we're going to say Sources/App/Models/* 0:34 to say just delete everything. 0:39 Okay, and we're also going to do the same with Sources/App/Controllers/*. 0:42 Okay, next, inside the models directory, we're going to create two files, 0:49 Course.swift and Review.swift. 0:53 Here we'll use the touch command. 0:55 Touch is usually used to modify file permissions, but 0:58 the nice thing is if the file listed doesn't exist, it creates one for us. 1:01 So it's an easy way to create a file. 1:05 So we'll do this at Sources/App/Models, and I'm just tabbing here to autocomplete. 1:07 And then Course.swift. 1:13 And we'll do the same for Sources/App/Models/Review.swift. 1:15 At this point, if you're working with Xcode, we need to regenerate 1:20 the project file so that the files are added to the appropriate targets. 1:24 And the ones we deleted are removed from the project navigator. 1:29 So first, I'm going to go back to Xcode, close this out, and then in here, 1:32 I'll write vapor xcode -y. 1:37 You don not need to do this if you're just using a text editor. 1:39 Once it's backed up in Xcode or in you editor, navigate to Course.swift, which is 1:43 now in the Models directory, and here we're going to create our first model. 1:48 Course is going to be a class with two simple properties. 1:53 So, class Course. 1:56 And a course is going to have a name of type String, and 1:59 a url of type URL to point to the course in the Treehouse library. 2:04 Let's also define a simple initializer that accepts a name as a String and 2:09 a url as a URL, which we'll assign to the properties. 2:16 We get an error on url, and 2:21 that's because we haven't imported the foundation framework. 2:23 But instead of doing that, we're going to import Vapor. 2:26 Now, we need the Vapor framework to add a bunch of functionality in there, 2:28 and Vapor imports the foundation framework itself. 2:31 So by doing that, we should be good to go. 2:33 When we make post requests to create an instance of a course, 2:36 we need to save it to a database. 2:40 As I mentioned, we're going to use the default SQL-like configuration that 2:42 the template comes loaded with. 2:45 Vapor makes it quite easy to persist models to the database using its object 2:48 relational mapper, Fluent. 2:52 Using Fluent means we don't need to interact with the database directly and 2:54 can just write Swift code. 2:57 In fact very little Swift code at that. 2:59 Right now Vapor does not know that our course type is a model because it does not 3:02 conform to Fluent's model type. 3:06 Model is a protocol and we can add conformance in an extension. 3:07 But first we need to import Fluent. 3:11 Fluent is the high level framework, but since we are working with the SQLite 3:16 framework specifically or the SQLite database, we can use some help with 3:19 protocols provided by Fluent for different database providers. 3:23 So, what we're going to import is Fluent SQLite. 3:26 And you can see that there's stuff for other databases. 3:29 Before we can conform to model, we need to do three things. 3:32 All Fluent models need to conform to the codable protocol so 3:35 that they can be serialized and deserialized. 3:38 And then second, the class needs to be final in that it cannot be subclassed. 3:42 So let's say final class course and it conforms to codable. 3:46 Last, this class needs to model an identifier property for 3:51 the data base to set when the model is saved. 3:54 There are different kinds of IDs we can create in Vapor, but 3:56 we are going to keep it simple and assign an integer value. 4:00 This needs to be an optional value, because we're not going to assign an int 4:04 on initialization, but let Fluent handle that on saving to the database. 4:08 And we need to call this id, it's going to be of type Int, and 4:12 we'll set this to optional. 4:15 Now at the bottom, in an extension of course, 4:16 we can add conforments to model. 4:21 To conform to model, we need to provide three pieces of information. 4:24 First, we need to indicate what type of database we're working with. 4:28 This is defined as an associated type requirement in the protocol, so 4:32 we specified in the extension as a type alias. 4:36 So we'll say, type alias, Database, and this is SQLiteDatabase. 4:39 As you can see Xcode is having issues keeping up with all of these 4:46 dependencies and associate type. 4:50 But it'll catch up, no big deal. 4:53 We also need to indicate what kind of type will act as an ID. 4:55 Again this is an associated type requirement that we specify as 4:59 a type alias. 5:02 So I type alias, ID is of type Int. 5:03 Finally we need to indicate what property on our custom type, 5:08 on our model, serves as the stored, as the stored property for the ID. 5:12 And we do that by providing a key path to the stored property. 5:17 So we'll say static var, idKey and this is of type IDKey, 5:20 which is a built in type and vapor. 5:25 And we're going to assign the key path \Course.id for that. 5:27 Now Fluent knows enough about our type to persist to the database. 5:31 The cool thing though, is that we don't even need to do this to work. 5:35 Now that you know what conformance requires, 5:40 we can actually let Fluent do all this work for us by conforming not to model. 5:43 But a helper protocol named SQLiteModel which synthesizes all these 5:48 requirements for us automatically. 5:52 So we'll say SQLiteModel. 5:54 Before we can persist any instances though, 5:56 we need to create a table in the database. 5:59 Vapor, or Fluent rather, does this using a migration. 6:02 So going to navigate to configure.swift. 6:05 And at the bottom you should see some code, prop this up. 6:08 You should see some code included in the template that configures migrations from 6:15 models. 6:20 Since the template included a to-do model, there should be a line, line 30 for me, 6:20 that adds that model which no longer exists to the migration configuration, 6:25 along with a value specifying what kind of database is being used. 6:29 All we're going to do here is modify this to say Course.self. 6:33 This doesn't actually resolve the error, and that's because I forgot one thing. 6:37 To add a model to the MigrationConfig, the model needs to conform 6:41 to the migration protocol so that Fluent knows what to do. 6:45 So we'll go can to course.swift, and 6:48 right below we'll say extension Course, and we'll conform it to Migration. 6:51 With that, Fluent should be able to create the course table and 6:57 persist instances to the database. 7:00 Let's do the same thing for the review model. 7:03 So we'll navigate to review.swift, we'll start by importing Vapor and 7:05 all the other necessary frameworks, so here that's FluentSQLite. 7:10 To model a review, we need a rating and a comment. 7:14 Like we did with course, 7:17 review is going to be a final class that conforms to the Codable protocol. 7:19 We'll need an id property of type Int. 7:24 Again, it's an optional type. 7:26 We'll need a rating of type Double, and we'll need a comment of type String. 7:28 We also need an initializer, so we'll init with a rating and a comment. 7:35 And you'll notice that we're excluding the id from the initializer. 7:42 And that's because we don't set that, Fluent does. 7:46 So here we'll just say self.comment = comment. 7:50 A review can only be created in association with a course. 7:53 So let's add another property here named courseID to model this. 7:56 The type for this property will be Course.ID. 8:02 Now ID here is an associated type that resolves to 8:06 whatever ID is provided by the course model. 8:10 Now you remember a second ago when we added a configuration, or 8:14 rather added an implementation for the model protocol? 8:18 We provided as a type alias, what type would act as an ID. 8:22 So this is what that type alias is used for. 8:27 Here we don't care what that ID is, but we're saying whatever it is, 8:29 we're going to store that in here, it resolves that runtime. 8:32 Now we'll also need to modify our init method to account for this property. 8:35 And we'll put that right at the top, so courseID of type Course.ID. 8:38 And then we'll self.courseID = courseID. 8:44 Okay, to wrap up, let's add conformance to 8:50 extension Review to the SQLiteModel protocol and 8:55 the Migration protocol. 9:00 And then finally we'll navigate back to configure.swift, 9:03 and down here we'll add review to the migration configuration. 9:08 So we'll say migrations.add Review.self and the database type is sqlite. 9:13 Now that we have our models, the next thing we need to do is define routes. 9:20 But, before that, let's take a quick detour to talk about Vapor's async module. 9:23
You need to sign up for Treehouse in order to download course files.Sign up