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
    }
}

Tags:

Swift

Vapor