Upgrading to Version 2

Here are the recommended steps for upgrading your app to Tailor 2.

Making it Build

  1. Install the TailorMysql framework, and import it in any files that reference MysqlConnection
  2. Update your code to use the Swift 2 syntax
  3. Have all of your models conform to the PersistableWithOldInitializer protocol. This provides an implementation of the new init(databaseRow: DatabaseRow) throws initializer that will call the older init?(databaseRow: [String: DatabaseValue]) initializer. This cannot propogate the error handling behavior, and will cause a fatal error if your failable initializer returns nil. It should still allow your app to build.
  4. Have all of your models conform to the Equatable protocol.

This should allow you to build your app and run your unit tests. Once the unit tests are all passing, you can start fixing the deprecation warnings.

Updating your Models

As we discussed above, the initializers in the Persistable protocol now take in a DatabaseRow rather than a hash, and throw an exception on failure rather than returning nil. You should go through you models one by one and update them to use the new syntax. Once you remove the PersistableWithOldInitializer protocol from you model, you'll be prompted to add the new initializer. Rather than add a new initializer, it'll be easier to replace the init?(databaseRow: [String:DatabaseValue]) initializer with init(databaseRow: DatabaseRow) throws. You can fetch the values from the database row by calling the read method with the name of the column. For instance, firstName = try databaseRow.read("first_name"). It will infer the desired type from the field that you're storing the result in, and will throw an exception if the value in the database is not of a compatible type. It will also infer whether the value is allowed to be null, and will throw an exception if the value is missing and is supposed to be present. If you have unit tests on the initializer, you can just put try? in front of the initializer to convert an error into a nil value and check it in your tests as before. You'll also need to wrap the hash of values from your old test in a DatabaseRow.

There are also several global functions that operated on Persistable records that are now provided in a protocol extension. toManyRecords(record) is now record.toMany(). saveRecord(record) is now record.save().

If you have a custom user model, you will need to make it implement the UserType protocol. This protocol requires an emailAddress and encryptedPassword field on your model. If you do not have a custom user model, you will need to make one, and have it conform to the protocol.

Updating Your Views

Next you should update your views to the new syntax. First, update your views to inherit from TemplateType rather than Template. There are limitations right now in Swift on calling mutating protocol methods from methods in a class, so you should make your views structs instead of classes. The body method will need to be mutating, and you'll need to add a variable called state with the type TemplateState. Inside your initializer, where you used to call a superclass initializer, should create this state based on the controller: self.state = TemplateState(controller).

The form building has changes as well. There's now a single method for creating a form builder and starting the block for its contents. The method is form on TemplateType. Where you used to call:

let form = FormBuilder(template: self, name: "post")
form.form(path) {

You would instead call:

form(path, name: "post") { form in

The form that is yielded to the block is a TemplateForm, but it provides a similar interface to FormBuilder. In order to accomodate having your templates be value types, the rendering order has changed. When you add inputs to the form, they don't get added directly to your template; instead they get added to a template within the TemplateForm itself. After your block is finished, everything will get added to the main template. If you want to add custom content to the form, you should add it to form.template.

It's also recommended that you use model types rather than model names when creating your forms. For instance, form(path, type: Post.self) rather than for(path: name: "post"). This gives the form access to more rich localizations.

There's a test helper that checks if a controller rendered a template, and takes a Template, not a TemplateType. Once your templates are converted to TemplateType, you'll have to change that to look for the template on the response rather than the controller. This will often come up as an error on callAction, because it can't infer the type of the closure and the compiler gets confused about where the real problem is.

If you have a custom layout, you should update it to conform to LayoutType rather than subclassing Layout.

If you have any links that specify a controller name, you should update them to specify a controller type instead.

Updating Your Controllers

Next let's update your controllers to use the new ControllerType protocol. This is a lot like the TemplateType protocol: have your controllers implement from the ControllerType protocol and add a field called state with the type ControllerState. You shouldn't have to define any initializers; they'll all be synthesized. You'll also need to add a static method called defineRoutes, which is responsible for specifying the routes on your controller. Rather than having this logic be split between the controller and the route set, it's all in the controller now. For instance, if you had a PostsController whose actions looked like this:

override class var actions: [Action] { return [
    Action(name: "index", body: wrap(indexAction)),
    Action(name: "new", body: wrap(newAction)),
    Action(name: "create", body: wrap(createAction))
  ] }

and a section in your route set that looked like this:

routes.withPrefix("posts", controller: PostsController.self) {
    routes.addRoute("", method: "GET", actionName: "index")
    routes.addRoute("", method: "POST", actionName: "create")
    routes.addRoute("new", method: "GET", actionName: "new")
}

You would now want to define a defineRoutes method in your controller like this:

static func defineRoutes(routes: RouteSet) {
    routes.withScope(path: "posts") {
          routes.route(.Get(""), to: indexAction, name: "index")
          routes.route(.Post(""), to: createAction, name: "create")
          routes.route(.Get("new"), to: newAction, name: "new")
    }
  }

And in your route set, all you need is:

routes.addControllerRoutes(PostsController.self)

If you have any non-controller routes, you may also need to update them to support new syntax. For instance, rather than having the HTTP method and the path be specified in separate parameters, they're now specified in a RoutePath enum that describes both the method and the path. You can see examples of this in defineRoutes above.

The system for adding filters to controller actions has changed as well. Instead of the filters being methods on controllers, they're now specified in instances that conform to the RequestFilterType protocol. You should check out the documentation on that if you want to implement custom filters. These filters are more powerful than the old system; they can run both preprocessing before the controller action is called and postprocessing after the controller action is called. You specify what kind of filters you want to apply to an action by calling withScope and giving the filters in either the filter or filters parameters.

There are a couple of filters provided by default. The CsrfFilter checks that any POST requests contain a CSRF token parameter in both the request data and the session, and that the two match. This parameter is automatically added to any forms you add through the default form builders. The CsrfFilter is added to all POST requests, but if you want to skip it you can call routes.withoutFilter(CsrfFilter()) {} and add the affected routes in that block.

There is also an AuthenticationFilter which requires that a user be signed in. For instance, if you wanted to require that users be logged in before making posts, you could specify that in your defineRoutes method:

static func defineRoutes(routes: RouteSet) {
    routes.withScope(path: "posts") {
          let loginPath = routes.pathFor(SessionsController.self, actionName: "new") ?? "/"
          routes.route(.Get(""), to: indexAction, name: "index")
          routes.withScope(filter: AuthenticationFilter(loginPath)) {
            routes.route(.Post(""), to: createAction, name: "create")
            routes.route(.Get("new"), to: newAction, name: "new")
          }
    }
  }

This assumes that you have a SessionsController that provides your login page.

The helper methods for generating responses have been left mostly as they were before. One big exception is the way sessions have handled. Controller actions can no longer modify state on the controller, so there is no longer a session field that can be modified in the course of the action. Instead, you should get the session from the request's session field, store it in a local variable, and modify it in your action. You can then pass it to the redirectTo and respondWith methods to include that session information in the response. The signIn methods have been redesigned to accomodate this: they now return a new session on success and throw an exception on failure.

There is also no longer a generateResponse method. Instead, you can just create a local response variable, modify it in your action, and pass it to the respondWith method. Rather than creating an empty response yourself, you can get the baseline response from your controller's state. This will allow filters to add initial information to the response before the controller action starts.

Another change to note is that the response codes are now represented by a ResponseCode enum rather than an integer. This allows you to specify the response code by its human-readable name. For instance, rather than setting response.code = 404, you would set response.responseCode = .NotFound.

The tests for controllers have changed as well. The callAction helper method in ControllerTestCase no longer passes a controller to the callback. Everything you need to check should be accessible on the response itself. You may get a complaint from the compiler that the type of the response parameter in the closure is ambiguous; this is because it doesn't know whether you expect it to be a single field containing a Response or a tuple containing a Response or a Controller. This will be fixed when the deprecated version is removed, but in the meantime you can just specify an explicit type for the response parameter.

Updating Your Configuration

The configuration system for Tailor has been reworked in version 2. Rather than specifying configuration in files, it's now largely specified in code. This allows it to be statically defined, type safe, and better documented. The recommended way of setting your configuration is through an extension to the Application.Configuration type. This type has a singleton instance, available at Application.configuration, which is loaded once at boot time. It also provides a dynamic configure method, which you can provide yourself in an extension to do your custom configuration.

The way we specify routes has changed as well. The routes are stored in a global fields that you can access through RouteSet.shared. You can set the routes through RouteSet.load, and it's generally best to do this at the same time as you load the rest of the configuration.

Here's what the configuration might look like under the new system:

extension Application.Configuration {
      public dynamic func configure() {
        userType = User.self
        databaseDriver = { return MysqlConnection(config:       Application.Configuration.configurationFromFile("database")) }
        staticContent = Application.Configuration.configurationFromFile("localization")
        RouteSet.load(loadRoutes)
      }

      func loadRoutes(inout routes: RouteSet) {
        /* Code to Load Routes. */

    }
  }

Alterations and Tasks have also been replaced by protocols: AlterationScript and TaskType. If you have anything subclassing the old classes, you should update them to use the new protocols. For the AlterationScript, the static method id has been replaced by a static field called identifier. The instance method alter has been replaced by a static method called run. For the TaskType, the instance method run has been replaced by a static method called runTask. The static method command has been replaced by a static method called commandName.