Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested Routes #40

Merged
merged 9 commits into from
Feb 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This is a work in progress, so don't rely on this for anything important. And pu

## Wiki

Visit the [Vapor Wiki](https://github.com/tannernelson/vapor/wiki) for extensive documentation on using and contributing to Vapor.
Visit the [Vapor Wiki](https://github.com/tannernelson/vapor/wiki) for extensive documentation on using and contributing to Vapor.

## Server

Expand Down Expand Up @@ -247,11 +247,37 @@ Here the `HeartbeatControllers`'s index method will be called when `http://examp

Resource controllers take advantage of CRUD-like `index`, `show`, `store`, `update`, `destroy` methods to make setting up REST APIs easy.

####Single Resources####

```swift
Route.resource("user", controller: UserController())
```

This will create the appropriate `GET`, `POST`, `DELETE`, etc methods for individual and groups of users.
This will create the appropriate `GET`, `POST`, `DELETE`, etc methods for individual and groups of users:

- .Get /user - an index of users
- .Get /user/:id - a single user etc

####Nested Resources####

You can also create nested resources for one to many relationships. For example, a "company" can have multiple "users".
This can be achieved by using dot notation in the path, as follows:

```swift
Route.resource("company.user", controller: CompanyUserController())
```

This will create appropriate nested `GET`, `POST`, `DELETE`, etc methods, for example:

- .Get /company/:company_id/user - an index of users at a specific company
- .Get /company/:company_id/user/:id - a specific user at a specific company
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we show some example code of pulling these parameters from request.data


You can now access these parameters in a controller, as follows:

```swift
let companyId = request.parameters["company_id"]
let userId = request.parameters["id"] //Note: The final parameter is always `id`.
```

## Middleware

Expand Down
25 changes: 18 additions & 7 deletions Sources/Route.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,25 @@ public class Route {
self.delete(path, closure: closure)
}


public class func resource(path: String, controller: Controller) {
self.get(path, closure: controller.index)
self.post(path, closure: controller.store)

self.get("\(path)/:id", closure: controller.show)
self.put("\(path)/:id", closure: controller.update)
self.delete("\(path)/:id", closure: controller.destroy)

let last = "/:id"
let shortPath = path.componentsSeparatedByString(".")
.flatMap { component in
return [component, "/:\(component)_id/"]
}
.dropLast()
.joinWithSeparator("")

// ie: /users
self.get(shortPath, closure: controller.index)
self.post(shortPath, closure: controller.store)

// ie: /users/:id
let fullPath = shortPath + last
self.get(fullPath, closure: controller.show)
self.put(fullPath, closure: controller.update)
self.delete(fullPath, closure: controller.destroy)
}

public class func host(hostname: String, closure: () -> ()) {
Expand Down
53 changes: 53 additions & 0 deletions Tests/RouteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// RouteTests.swift
// Vapor
//
// Created by Matthew on 20/02/2016.
// Copyright © 2016 Tanner Nelson. All rights reserved.
//

import XCTest

class RouteTests: XCTestCase {

func testSimpleResource() {

Route.resource("foo", controller: Controller())

let expectedRoutes = [
"Get foo nil",
"Post foo nil",
"Get foo/:id nil",
"Put foo/:id nil",
"Delete foo/:id nil",
]

assertRoutesExist(expectedRoutes)

}

func testNestedResource() {

Route.resource("foo.bar", controller: Controller())

let expectedRoutes = [
"Get foo/:foo_id/bar nil",
"Post foo/:foo_id/bar nil",
"Get foo/:foo_id/bar/:id nil",
"Put foo/:foo_id/bar/:id nil",
"Delete foo/:foo_id/bar/:id nil",
]

assertRoutesExist(expectedRoutes)
}

private func assertRoutesExist(expected: [String]) {

expected.forEach { description in
let exists = Route.routes.filter { $0.description == description }.count == 1
XCTAssert(exists, "routes should contain \(description)")
}
}


}
4 changes: 4 additions & 0 deletions Vapor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
3492B2ED1C7819D800D8E588 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3492B2EC1C7819D800D8E588 /* main.swift */; };
3492B2EF1C787DD600D8E588 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3492B2EE1C787DD600D8E588 /* RouteTests.swift */; };
C304C8A71C62D65200058C92 /* ServerDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C304C8A61C62D65200058C92 /* ServerDriver.swift */; };
C304C8A81C62D65200058C92 /* ServerDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C304C8A61C62D65200058C92 /* ServerDriver.swift */; };
C304C8AA1C62FCA300058C92 /* RequestFormExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C304C8A91C62FCA300058C92 /* RequestFormExtension.swift */; };
Expand Down Expand Up @@ -76,6 +77,7 @@

/* Begin PBXFileReference section */
3492B2EC1C7819D800D8E588 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
3492B2EE1C787DD600D8E588 /* RouteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = "<group>"; };
C304C8A01C62C3F400058C92 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = "<group>"; };
C304C8A61C62D65200058C92 /* ServerDriver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDriver.swift; sourceTree = "<group>"; };
C304C8A91C62FCA300058C92 /* RequestFormExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestFormExtension.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -231,6 +233,7 @@
C352E6931C62BB1C00E26467 /* Info.plist */,
C38808631C62C0390067DADD /* ResponseTests.swift */,
C3943DC21C7663E80014F4EE /* RouterTests.swift */,
3492B2EE1C787DD600D8E588 /* RouteTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -443,6 +446,7 @@
C352E6F81C62BC6A00E26467 /* ResponseConvertible.swift in Sources */,
C352E6F01C62BC6A00E26467 /* MemorySessionDriver.swift in Sources */,
C352E7001C62BC6A00E26467 /* Session.swift in Sources */,
3492B2EF1C787DD600D8E588 /* RouteTests.swift in Sources */,
C3943DC41C7664D90014F4EE /* Middleware.swift in Sources */,
C352E6F61C62BC6A00E26467 /* Response.swift in Sources */,
C3943DC31C7663E80014F4EE /* RouterTests.swift in Sources */,
Expand Down