juniper-eager-loading
juniper-eager-loading copied to clipboard
HasManyThrough with other fields
How can i join two tables and add other attributes from the join table ?
I have the following database schema
My graphql scheme look like this:
schema {
query: Query
mutation: Mutation
}
type Query {
cinemas(
criterias: CinemaSearchCriterias
pagination: Pagination = { perPage: 20 }
order: Order = "-id"
): [Cinema]!
movies(
criterias: MovieSearchCriterias
pagination: Pagination = { perPage: 20 }
order: Order = "-id"
): [Movie]!
}
type Mutation {
}
scalar Order
enum EExposedFormat {
FULLHD
4K
3D
4D
}
type Cinema {
id: ID!
name: String!
movies(
criterias: MovieSearchCriterias
pagination: Pagination = { perPage: 20 }
order: Order = "-id"
): [PlannedMovie]
}
type PlannedMovie {
id: ID!
exposedFormat: EExposedFormat
boundingBox: [Float]
movie: Movie!
}
type Movie {
id: ID!
name: String!
description: String
pixelsBox: [Float]
path: String
}
input Pagination {
page: Int
perPage: Int!
}
input CinemaSearchCriterias {
name: String
}
input MovieSearchCriterias {
name: String
path: String
}
So when i request the cinema i need to get the PlannedMovie type for each movie, how can i create that object using juniper eager loading (it doesn't exist in db so i don't know witch model i am supposed to put when implementing EagerLoading)
Also i think we don't have complete example of complex use case. I have the following database scheme https://github.com/Farkal/test-wundergraph/blob/master/test_data_model_v1.png (i tried to implement it using wundergraph but there is a rustc issue with repetitive trait bound that make long compilation time). I think it could be a great and complete example of complex use case for juniper-eager-loading and for graphql api using rust in general. If you could help me implementing some part that would be awesome ! (i plan to create the graphql scheme in few hours)
juniper-eager-loading doesn't require that your structs map directly to your database schema. So you're fully able to make structs that compose several database models. Doing that will require implementing EagerLoadChildrenOfType
manually though. You're free to decide if load_children
should use the LoadFrom
trait to make the queries or just make them directly in load_children
. When doing manual implementations that isn't important. The rest of the methods in EagerLoadChildrenOfType
should be fairly straight forward.
You can find an example of how to do that here and one here if your GraphQL fields need to take arguments that impact eager loading.
I have done something like in the past so it is totally possible but it does require wrapping your head around the concepts. Give it a shot and let me know if you run into any problems.
Waow that was quick ! Thank's for the information I will try to do something :+1:
Sorry but I don't find how to create custom type from HasManyThrough. I am able to link Cinema to its Movies through the MovieToCinema model, but impossible to find how return custom type that contains layer information.
Here is my current code:
#[derive(Clone, EagerLoading)]
#[eager_loading(context= Context, error = Error)]
pub struct Cinema {
cinema: models::Cinema,
#[has_many_through(join_model = models::MovieToCinema)]
movies: HasManyThrough<PlannedMovie>,
}
impl CinemaFields for Cinema {
fn field_id(&self, executor: &Executor<'_, Context>) -> FieldResult<ID> {
Ok(self.cinema.id.to_string().into())
}
fn field_name(&self, executor: &Executor<'_, Context>) -> FieldResult<&String> {
Ok(&self.cinema.name)
}
fn field_movies(
&self,
executor: &Executor<'_, Context>,
trail: &QueryTrail<'_, PlannedMovie, Walked>,
criterias: Option<MovieSearchCriterias>,
pagination: Option<Pagination>,
order: Option<Order>,
) -> FieldResult<&Option<Vec<PlannedMovie>>> {
self.movies.try_unwrap().map_err(From::from)
}
}
impl juniper_eager_loading::LoadFrom<models::Cinema> for models::MovieToCinema {
type Error = Error;
type Context = Context;
fn load(
cinemas: &[models::Cinema],
_field_args: &(),
ctx: &Self::Context,
) -> TRustResult<Vec<Self>> {
let cinema_ids = cinemas.iter().map(|e| e.id).collect::<Vec<_>>();
movie_to_cinema::get_by_cinemas_ids(&ctx.db, &cinema_ids)
}
}
impl juniper_eager_loading::LoadFrom<models::MovieToCinema> for Movie {
type Error = Error;
type Context = Context;
fn load(
movies_to_cinemas: &[models::MovieToCinema],
_field_args: &(),
ctx: &Self::Context,
) -> TRustResult<Vec<Self>> {
let movies_ids = movies_to_cinemas
.iter()
.map(|movie_to_cinema| movie_to_cinema.movie_id)
.collect::<Vec<_>>();
movie::get_by_ids(&ctx.db, &movies_ids)
}
}
struct EagerLoadingContextMovieToCinema;
impl<'a>
EagerLoadChildrenOfType<
'a,
PlannedMovie,
EagerLoadingContextMovieToCinema,
models::MovieToCinema,
> for Cinema
{
type FieldArguments = ();
fn load_children(
models: &[Self::Model],
field_args: &Self::FieldArguments,
ctx: &Self::Context,
) -> Result<LoadChildrenOutput<PlannedMovie, models::MovieToCinema>, Self::Error> {
let join_models: Vec<models::MovieToCinema> = LoadFrom::load(&models, field_args, ctx)?;
let child_models: Vec<models::Movie> = LoadFrom::load(&join_models, field_args, ctx)?;
let mut child_and_join_model_pairs = Vec::new();
// // WANT TO CONVERT MOVIE TO PLANNED MOVIE BY MERGING WITH MOVIETOCINEMA HERE BUT CAN'T BECAUSE RETURN TYPE NEED TO IMPL EAGERLOADING
Ok(LoadChildrenOutput::ChildAndJoinModels(
child_and_join_model_pairs,
))
}
fn is_child_of(
node: &Self,
child: &Movie,
join_model: &models::MovieToCinema,
_field_args: &Self::FieldArguments,
_ctx: &Self::Context,
) -> bool {
node.cinema.id == join_model.cinema_id && join_model.movie_id == child.movie.id
}
fn association(node: &mut Self) -> &mut dyn Association<Movie> {
&mut node.movies
}
}
My other solution is to don't use the HasManyThrough but create a HasMany from Cinema to PlannedMovie and a HasOne from PlannedMovie to Movie. It should work and maybe it's more simpler (i don't know about the perf)
Are you able to share all your code so I can poke around? A repo I could fork/clone would be ideal.
Yes ! Here -> https://github.com/Farkal/rust-graphql-complex-api-example
Could you also release the latest version of the lib supporting field_arguments ? (it seems it doesn't support Option<MyFieldArg> i will implement EagerLoadChildrenOfType and investigate on this)