不再积极维护 (见 #9)
我们一直在编写 CRUD (创建-读取-更新-删除) 路由。这个包的目的是减少重复的代码,并为 API 提供一个快速的开始。
这个项目是开放贡献的。欢迎克隆、Fork 或提交 PR。非常欢迎您的帮助!
将此包添加到您的 Package.swift
作为依赖项,并添加到您的 target 中。
dependencies: [
.package(url: "https://github.com/simonedelmann/crud-kit.git", from: "1.1.0")
],
targets: [
.target(name: "App", dependencies: [
.product(name: "CRUDKit", package: "crud-kit")
])
]
import CRUDKit
final class Todo: Model, Content {
@ID()
var id: UUID?
@Field(key: "title")
var title: String
@Field(key: "done")
var done: Bool
// ...
}
extension Todo: CRUDModel { }
app.crud("todos", model: Todo.self)
这将注册基本的 CRUD 路由
POST /todos # create todo
GET /todos # get all todos
GET /todos/:todos # get todo
PUT /todos/:todos # replace todo
DELETE /todos/:todos # delete todo
请注意! 端点名称 (例如 "todos") 也将被用作命名 ID 参数的名称。这是为了避免在有多个参数时出现重复。
您可以返回一个自定义结构作为公共实例,该实例将从所有 CRUD 路由中返回。
extension Todo: CRUDModel {
struct Public: Content {
var title: String
var done: Bool
}
var `public`: Public {
Public.init(title: title, done: done)
}
}
该计算属性稍后将被转换为 EventLoopFuture<Public>
。如果需要运行异步代码来创建公共实例 (例如加载关系),可以自定义该转换。虽然您可以在那里访问数据库,但不应该使用它来执行任何业务逻辑。
extension Todo: CRUDModel {
// ...
// This is the default implementation
func `public`(eventLoop: EventLoop, db: Database) -> EventLoopFuture<Public> {
eventLoop.makeSucceededFuture(self.public)
}
// You can find an example for loading relationship in /Tests/CRUDKitTests/Models/Todo.swift
}
您可以在创建/替换时添加特定的逻辑。如果您的创建/替换请求应该采用模型属性的子集,或者如果您需要在创建/替换时执行特殊操作,这将特别有用。
extension Todo: CRUDModel {
struct Create: Content {
var title: String
}
convenience init(from data: Create) {
// Call model initializer with default value for done
Todo.init(title: data.title, done: false)
// Do custom stuff (e.g. hashing passwords)
}
struct Replace: Content {
var title: String
}
func replace(with data: Replace) -> Self {
// Replace all properties manually
self.title = data.title
// Again you can add custom stuff here
// Return self
return self
// You can also return a new instance of your model, the id will be preserved.
}
}
您可以通过遵循 Patchable
协议,为您的模型添加 patch 支持。
PATCH /todos/:todos # patch todo
extension Todo: Patchable {
struct Patch: Content {
var title: String?
var done: Bool?
}
func patch(with data: Patch) {
if let title = data.title {
self.title = title
}
// Shorter syntax
self.done = data.done ?? self.done
}
}
要添加自动验证,您只需要让您的模型(或您的自定义结构)遵循 Validatable
协议即可。
extension Todo: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("title", as: String.self, is: .count(3...))
}
}
// Using custom structs
extension Todo.Create: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("title", as: String.self, is: .count(3...))
}
}
extension Todo.Replace: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("title", as: String.self, is: .count(3...))
}
}
extension Todo.Patch: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("title", as: String.self, is: .count(3...))
}
}
实验性 您可以通过闭包将您自己的子路由添加到 .crud()
中。
// routes.swift
app.crud("todos", model: Todo.self) { routes, _ in
// GET /todos/:todos/hello
routes.get("hello") { _ in "Hello World" }
}
实验性 目前仅支持 Children 关系。 请参见下面的示例...
// Todo -> Tag
final class Todo: Model, Content {
@Children(for: \.todo)
var tags: [Tag]
// ...
}
final class Tag: Model, Content {
@Parent(key: "todo_id")
var todo: Todo
// ...
}
extension Todo: CRUDModel { }
extension Tag: CRUDModel { }
// routes.swift
app.crud("todos", model: Todo.self) { routes, parentController in
routes.crud("tags", children: Tag.self, on: parentController, via: \.$tags)
}
这将为标签注册 CRUD 路由
POST /todos/:todos/tags # create tag
GET /todos/:todos/tags # get all tags
GET /todos/:todos/tags/:tags # get tag
PUT /todos/:todos/tags/:tags # replace tag
PATCH /todos/:todos/tags/:tags # patch tag (if Tag conforms to Patchable)
DELETE /todos/:todos/tags/:tags # delete tag
Children 关系支持所有功能 (公共实例、自定义创建/替换、patch 支持、验证)。
目前 Vapor 要求将父 ID 添加到创建/替换请求中。
final class Tag: Model, Content {
// ...
@Parent(key: "todo_id")
var todo: Todo
init(id: Tag.IDValue? = nil, title: String, todo_id: Todo.IDValue) {
// ...
self.$todo.id = todo_id
}
}
extension Tag: CRUDModel { }
这需要像这样的创建 payload
{
title: "Foo",
todo {
id: 1
}
}
您可以使用自定义的创建/替换结构来避免这种情况。此包将负责并为您填写正确的 ID。
final class Tag: Model, Content {
// ...
@Parent(key: "todo_id")
var todo: Todo
// Make todo_id parameter optional
init(id: Tag.IDValue? = nil, title: String, todo_id: Todo.IDValue?) {
// ...
// Use if let for unwrapping the optional
if let todo = todo_id {
self.$todo.id = todo
}
}
}
extension Tag: CRUDModel {
struct Create: Content {
var title: String
var todo_id: Todo.IDValue?
}
convenience init(from data: Create) throws {
self.init(title: data.title, todo_id: data.todo_id)
}
struct Replace: Content {
var title: String
var todo_id: Todo.IDValue?
}
func replace(with data: Replace) throws -> Self {
Self.init(title: data.title, todo_id: data.todo_id)
}
}
然后,您可以创建一个没有父 ID 在 payload 中的子项。
{
title: "Foo"
}