0x00 Chapter 10 Sibling Relationships
1.
Sibling relationships
describe a relationship that links
two models
to each other
They are also known as
many-to-many
relationships
Unlike
parent-child
relationships,
there are no
constraints
between models in a
sibling
relationship
For instance, if you model the relationship between
pets
and
toys
a pet can have
one or more
toys
and a toy can be used by
one or more
pets
In the TIL application, you’ll be able to categorize acronyms.
An acronym can be part of
one or more
categories
and a category can contain
one or more
acronyms
2.Category model
create a new file
Category.swift
in Sources/App/Models
import Vapor
import Fluent
final class Category: Model, Content {
static let schema = "categories"
@ID
var id: UUID?
@Field(key: "name")
var name: String
init() {}
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
3.Migration
create a new file
CreateCategory.swift
in Sources/App/Migrations
import Fluent
struct CreateCategory: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("categories")
.id()
.field("name", .string, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("categories").delete()
}
}
open
configure.swift
and add CreateCategory to the migration list, after
app.migrations.add(CreateAcronym())
app.migrations.add(CreateCategory())
4.Category controller
create a new file called
CategoriesController.swift
In Sources/App/Controllers
import Fluent
import Vapor
struct CategoriesController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let categoriesRoute = routes.grouped("api", "categories")
categoriesRoute.post(use: createHandler)
categoriesRoute.get(use: getAllHandler)
categoriesRoute.get(":categoryID", use: getHandler)
categoriesRoute.get(":categoryID", "acronyms", use: getAcronymsHandler)
}
func createHandler(_ req: Request) throws -> EventLoopFuture<Category> {
let category = try req.content.decode(Category.self)
return category.save(on: req.db).map { category }
}
func getAllHandler(_ req: Request) throws -> EventLoopFuture<[Category]> {
Category.query(on: req.db).all()
}
//
func getHandler(_ req: Request) throws -> EventLoopFuture<Category> {
Category.find(req.parameters.get("categoryID"), on: req.db)
.unwrap(or: Abort(.notFound))
}
// http://127.0.0.1:8080/api/categories/<categoryID>/acronyms
func getAcronymsHandler(_ req: Request) throws -> EventLoopFuture<[Acronym]> {
Category.find(req.parameters.get("categoryID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { category in
category.$acronyms.get(on: req.db)
}
}
}
open
routes.swift
and register the controller by adding the following to the
end of
routes(_:)
app.migrations.add(CreateCategory())
通過 Rested 送出 category 資料
url:
http://127.0.0.1:8080/api/categories
method:
POST
parameter:
{"name": "Teenager"}
5.Creating a pivot
In Chapter 9, “Parent Child Relationships”, you added a
reference
to the
user
in the
acronym
to create the
relationship
between an acronym and a user
However, you
can’t
model a sibling relationship like this as it would be too inefficient to query
You need a separate model to hold on to this relationship. In Fluent, this is a
pivot
create file called
AcronymCategoryPivot.swift
in Sources/App/Models
import Vapor
import Fluent
final class AcronymCategoryPivot: Model {
static let schema = "acronym-category-pivot"
@ID
var id: UUID?
@Parent(key: "acronymID")
var acronym: Acronym
@Parent(key: "categoryID")
var category: Category
init() {}
init(id: UUID? = nil, acronym: Acronym, category: Category) throws {
self.id = id
self.$acronym.id = try acronym.requireID()
self.$category.id = try category.requireID()
}
}
Create
CreateAcronymCategoryPivot.swift
in Sources/App/Migrations
import Fluent
struct CreateAcronymCategoryPivot: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("acronym-category-pivot")
.id()
.field("acronymID", .uuid, .required, .references("acronyms", "id", onDelete: .cascade))
.field("categoryID", .uuid, .required, .references("categories", "id", onDelete: .cascade))
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("acronym-category-pivot").delete()
}
}
As in Chapter 9, “Parent Child Relationships,” it’s good practice to use
foreign key
constraints with sibling relationships
The migration also sets a
cascade
schema reference action when you
delete
the model.
This causes the database to remove the relationship
automatically
instead of throwing an error
open
configure.swift
and add CreateAcronymCategoryPivot to the
migration list, after app.migrations.add(CreateCategory())
app.migrations.add(CreateAcronymCategoryPivot())