Skip to content

Commit 936a1bc

Browse files
committed
Add support for many-to-many relationships (@Siblings) in Fluent
1 parent 7989356 commit 936a1bc

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,44 @@ enum TodoState: String, CaseIterable {
6363
Enum(TodoState.self),
6464
```
6565

66-
#### `Parent` and `Children`
67-
Vapor has the functionality to fetch an objects parent and children automatically with `@Parent` and @`Children` types. To integrate this into GraphQL, GraphQLKit provides extensions to the `Field` type that lets you use the parent or children property as a keypath. The fetching of those related objects is then done automatically.
66+
#### `Parent`, `Children` and `Siblings`
67+
Vapor has the functionality to fetch an objects parent, children or siblings automatically with `@Parent`, `@Children` and `@Siblings` types. To integrate this into GraphQL, GraphQLKit provides extensions to the `Field` type that lets you use the parent, children or siblings property as a keypath. Fetching of those related objects is then done automatically.
6868

6969
> :warning: Loading related objects in GraphQL has the [**N+1** problem](https://itnext.io/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a). A solution would be to build a DataLoader package for Swift. But this hasn't been done yet.
7070
7171
```swift
72-
final class User {
72+
final class User: Model {
7373
...
74-
var userId: UUID
75-
@Parent(key: "userId") var user: User
74+
75+
@Children(for: \.$user)
76+
var todos: [Todo]
77+
78+
...
79+
}
80+
81+
final class Todo: Model {
82+
...
83+
84+
@Parent(key: "user_id")
85+
var user: User
86+
87+
@Siblings(through: TodoTag.self, from: \.$todo, to: \.$tag)
88+
public var tags: [Tag]
89+
7690
...
7791
}
7892
```
7993

8094
```swift
81-
// Schema type:
82-
Field(.user, with: \.user),
95+
// Schema types
96+
Type(User.self) {
97+
Field("todos", with: \.$todos)
98+
}
99+
100+
Type(Todo.self) {
101+
Field("user", with: \.$user)
102+
Field("tags", with: \.$tags)
103+
}
83104
```
84105

85106
### Register the schema on the application

Sources/GraphQLKit/Graphiti+Fluent.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,59 @@ import Graphiti
22
import Vapor
33
import Fluent
44

5+
// Child Relationship
56
extension Graphiti.Field where Arguments == NoArguments, Context == Request, ObjectType: Model {
7+
8+
/// Creates a GraphQL field for a one-to-many relationship for Fluent
9+
/// - Parameters:
10+
/// - name: Filed name
11+
/// - keyPath: KeyPath to the @Children property
612
public convenience init<ChildType: Model>(
713
_ name: FieldKey,
814
with keyPath: KeyPath<ObjectType, ChildrenProperty<ObjectType, ChildType>>
915
) where FieldType == [TypeReference<ChildType>] {
1016
self.init(name.description, at: { (type) -> (Request, NoArguments, EventLoopGroup) throws -> EventLoopFuture<[ChildType]> in
1117
return { (context: Request, arguments: NoArguments, eventLoop: EventLoopGroup) in
12-
return type[keyPath: keyPath].query(on: context.db).all()
18+
return type[keyPath: keyPath].query(on: context.db).all() // Get the desired property and make the Fluent database query on it.
1319
}
1420
}, as: [TypeReference<ChildType>].self)
1521
}
1622
}
1723

24+
// Parent Relationship
1825
extension Graphiti.Field where Arguments == NoArguments, Context == Request, ObjectType: Model {
26+
27+
/// Creates a GraphQL field for a one-to-many/one-to-one relationship for Fluent
28+
/// - Parameters:
29+
/// - name: Field name
30+
/// - keyPath: KeyPath to the @Parent property
1931
public convenience init<ParentType: Model>(
2032
_ name: FieldKey,
2133
with keyPath: KeyPath<ObjectType, ParentProperty<ObjectType, ParentType>>
22-
) where FieldType == TypeReference<ParentType>{
34+
) where FieldType == TypeReference<ParentType> {
2335
self.init(name.description, at: { (type) -> (Request, NoArguments, EventLoopGroup) throws -> EventLoopFuture<ParentType> in
2436
return { (context: Request, arguments: NoArguments, eventLoop: EventLoopGroup) in
25-
return type[keyPath: keyPath].get(on: context.db)
37+
return type[keyPath: keyPath].get(on: context.db) // Get the desired property and make the Fluent database query on it.
2638
}
2739
}, as: TypeReference<ParentType>.self)
2840
}
2941
}
42+
43+
// Siblings Relationship
44+
extension Graphiti.Field where Arguments == NoArguments, Context == Request, ObjectType: Model {
45+
46+
/// Creates a GraphQL field for a many-to-many relationship for Fluent
47+
/// - Parameters:
48+
/// - name: Field name
49+
/// - keyPath: KeyPath to the @Siblings property
50+
public convenience init<ToType: Model, ThroughType: Model>(
51+
_ name: FieldKey,
52+
with keyPath: KeyPath<ObjectType, SiblingsProperty<ObjectType, ToType, ThroughType>>
53+
) where FieldType == [TypeReference<ToType>] {
54+
self.init(name.description, at: { (type) -> (Request, NoArguments, EventLoopGroup) throws -> EventLoopFuture<[ToType]> in
55+
return { (context: Request, arguments: NoArguments, eventLoop: EventLoopGroup) in
56+
return type[keyPath: keyPath].query(on: context.db).all() // Get the desired property and make the Fluent database query on it.
57+
}
58+
}, as: [TypeReference<ToType>].self)
59+
}
60+
}

0 commit comments

Comments
 (0)