Siblings Relationship Between Same Models in Vapor

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 Folders:

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



Leave a reply



Submit