Siblings relationship between same models in Vapor
The issue here is that in a same-Model
(User-User
) siblings relation, Fluent cannot infer which sibling you are referring to – the sides need to be specified.
extension User {
// friends of this user
var friends: Siblings<User, User, UserFriendsPivot> {
siblings(UserFriendsPivot.leftIDKey, UserFriendsPivot.rightIDKey)
}
// users who are friends with this user
var friendOf: Siblings<User, User, UserFriendsPivot> {
siblings(UserFriendsPivot.rightIDKey, UserFriendsPivot.leftIDKey)
}
}
The other same-Model
consequence is that you will not be able to use the attach convenience method to add to the pivot table, and need to manually create instead:
let pivot = try UserFriendsPivot(user, friend)
pivot.save(on: req)
(There are other approaches to work around this, I just find these straightforward ways above the easiest to use. Specifying the sides and reversing the key positions to obtain the inverse relation are the important concepts.)
as answered by grundoon
Vapor 4: how to include siblings including extra properties from the pivot table?
I encountered the same issue about accessing additional fields in the pivot table, but there is a fairly tidy way of accomplishing this. In addition to your siblings relationship, define a @Children
relation from Course
to Student
. Then, in your query, do a nested with
.
Put this in your Course model:
@Children(for:\.$course) var students: [Student]
Query:
let query = Course.query(on: req.db).with(\.$students){ $0.with(\.$user) }.all()
The first with
gets the additional fields of the pivot table and then the nested with
get the User
model.
How do I declare a Siblings extension on a Fluent model in Vapor 3 where the base and related types are the same?
After fiddling around with this for a while longer, I've figured out an answer.
The implementation of Fluent's siblings
convenience functions can be found in this highlighted segment on GitHub. I've copied it below for clarity of discussion:
extension Model {
...
/// Free implementation where pivot constraints are met.
/// See `Model.siblings(_:_:)`.
public func siblings<Related, Through>(
related: Related.Type = Related.self,
through: Through.Type = Through.self
) -> Siblings<Self, Related, Through>
where Through.Right == Self, Through.Left == Related
{
return siblings(Through.rightIDKey, Through.leftIDKey)
}
/// Free implementation where pivot constraints are met.
/// See `Model.siblings(_:_:)`.
public func siblings<Related, Through>(
related: Related.Type = Related.self,
through: Through.Type = Through.self
) -> Siblings<Self, Related, Through>
where Through.Left == Self, Through.Right == Related
{
return siblings(Through.leftIDKey, Through.rightIDKey)
}
}
The issue, I believe, is that my desired usage was ambiguous. The first function in the above snippet is used when Self
is the right-hand type of the pivot and Related
is the left-hand type. Similarly, the second function is used when the opposite is the case.
As I was using a type of Siblings<X, X, XPivot>
, Swift was unable to determine which function was better, since the conditions were satisfied for each.
To fix this, I implemented my own extension:
extension Model {
public func childrenSiblings<Through>(
through: Through.Type = Through.self
) -> Siblings<Self, Self, Through>
where Through.Left == Self, Through.Right == Self
{
return siblings(Through.leftIDKey, Through.rightIDKey)
}
public func parentSiblings<Through>(
through: Through.Type = Through.self
) -> Siblings<Self, Self, Through>
where Through.Left == Self, Through.Right == Self
{
return siblings(Through.rightIDKey, Through.leftIDKey)
}
}
I used childrenSiblings
to indicate when you're looking for children of the current type (which are also of the same type), and parentSiblings
for looking for parents of the current type (which are of the same type). The difference between these lies in the inner call to siblings
, where the Through.leftIDKey
and Through.rightIDKey
are switched in the second function. This is because of how I structured the pivot table (i.e., the left-hand column is parent_folder_id
and the right-hand column is child_folder_id
).
The usage of these functions is similar to that of the regular siblings
functions. In my case in the question, where I'm relating Folder
types to other Folder
s:
extension Folder {
var subFolders: Siblings<Folder, Folder, FoldersToSubfoldersPivot> {
return childrenSiblings()
}
var superFolders: Siblings<Folder, Folder, FoldersToSubfoldersPivot> {
return parentSiblings()
}
}
Vapor 4 parent child relationship
In this case ID
should be set as String
@ID(key: .id, generatedBy: .user) var id: String?
How to wrap relationship children into array in Vapor?
You can use a query to form the Future
array and then map
it. Assuming you are in some controller/route where event
contains the appropriate Event
and release
contains the appropriate Release
, try this:
{
release, event in
_ = release.testPrices.query(on:request).all().map { testP in
// testP is now [TestPrice]
event.testPrices = testP
}
}
Complex query using Fluent in Vapor 4
If I have understood your requirement correctly, you should be starting with the sibling relationship from the User
end, not Chat
. Your query then simplifies to:
func fetch(req: Request) throws -> EventLoopFuture<[Chat]> {
let user = try req.auth.require(User.self)
return User
.query(on: req.db)
.filter(\.$id == user.id!)
.with(\.$chats)
.first()
.unwrap(or:Abort(.internalServerError))
.map
{
chattyUser in
return chattyUser.chats
}
}
Return Children and Siblings values in response
You can map the array of results into the type you want. So
ClimbingGym
.query(on: req.db)
.with(\.$socialNetworks)
.with(\.$disciplines)
.all().flatMapThrowing { gyms in
try gyms.map { try ClimbingGymResponse(id: $0.requireID(), ...) }
}
Vapor 4: how to map an eagerly loaded parent relation into a different format?
You can do this by first encoding the original model and then decoding it into a structure with fewer fields. So, for an instance of Course
stored in course
to convert to PublicCourse
you would do:
struct PublicCourse: Decodable {
//...
let teacher: PublicUser
//...
}
let course:Course = // result of Course.query including `with(\.$teacher)`
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(course)
let publicCourse = try decoder.decode(PublicCourse.self, from: data)
Notice the PublicUser
field in the structure. If this is the cut-down version, you can generate your minimal JSON in one go.
Vapor 3, Fluent 3 and Many-to-Many relations not working as expected
So I believe you're expecting the relationship to be reflected in what is returned when you query for a Movie model. So for example you expect something like this to be returned for a Movie:
{
"id": 1,
"dateReleased": "2017-11-20T00:00:00Z",
"totalGrossed": 0,
"name": "Star Wars: The Last Jedi",
"synopsis": "Someone with a lightsaber kills another person with a lightsaber",
"actors": [
"id": 1,
"firstName": "Leonardo",
"lastName": "DiCaprio",
"dateOfBirth": "1974-11-11T00:00:00Z",
"story": "Couldn't get an Oscar until wrestling a bear for the big screen."
]
}
However, connecting the Movie and Actor models as siblings simply just gives you the convenience of being able to query the actors from a movie as if the actors were a property of the Movie model:
movie.actors.query(on: request).all()
That line above returns: Future<[Actor]>
This works vice versa for accessing the movies from an Actor object:
actor.movies.query(on: request).all()
That line above returns: Future<[Movie]>
If you wanted it to return both the movie and its actors in the same response like how I assumed you wanted it to work above, I believe the best way to do this would be creating a Content response struct like this:
struct MovieResponse: Content {
let movie: Movie
let actors: [Actor]
}
Your "all" function would now look like this:
func all(_ request: Request) throws -> Future<[MovieResponse]> {
return Movie.query(on: request).all().flatMap { movies in
let movieResponseFutures = try movies.map { movie in
try movie.actors.query(on: request).all().map { actors in
return MovieResponse(movie: movie, actors: actors)
}
}
return movieResponseFutures.flatten(on: request)
}
}
This function queries all of the movies and then iterates through each movie and then uses the "actors" sibling relation to query for that movie's actors. This actors query returns a Future<[Actor]> for each movie it queries the actors for. Map what is returned from the that relation so that you can access the actors as [Actor] instead of Future<[Actor]>, and then return that combined with the movie as a MovieResponse.
What this movieResponseFutures actually consists of is an array of MovieResponse futures:
[Future<[MovieResponse]>]
To turn that array of futures into a single future that consists of an array you use flatten(on:). This waits waits for each of those individual futures to finish and then returns them all as a single future.
If you really wanted the Actor's array inside of the Movie object json, then you could structure the MovieResponse struct like this:
struct MovieResponse: Content {
let id: Int?
let name: String
let synopsis: String
let dateReleased: Date
let totalGrossed: Float
let actors: [Actor]
init(movie: Movie, actors: [Actor]) {
self.id = movie.id
self.name = movie.name
self.synopsis = movie.synopsis
self.dateReleased = movie.dateReleased
self.totalGrossed = movie.totalGrossed
self.actors = actors
}
}
Related Topics
How to Create Generic Closures in Swift
Solve Equations of Type A*X = B Using Dgtsv_ or Sgtsv_
How to Draw a Hollow Circle with Two Different Colors
Using a Timer in The Background Thread to Update UI
User Already Authenticated in App and Firebase Realtime Database Returns Failed: Permission_Denied
Nsextensionpointidentifier Error Only on Real Device
Swift Applications Takes More Space on Disk
Swiftui Textfield Resets Value and Ignores Binding
[Bool]' to 'Nil' Always Returns True - Issue in Swiftui
Proper Way of Editing a Cocoapod Library
Tvos Button Inside Navigationlink Is Not Working
Parsing JSON from Url Ends Up with an Error - Swift 5
How to Synchronize Access to a Property That Has Didset
How to Pass Data from Using Post/Form Leaf Template
Applying Impulses in Spritekit
Continuous Rotation of Nsimageview (So It Appears to Be Animated)
How to Send Multiple Buttons in Button.Addtarget Action? Swift3