Skip to content

Instantly share code, notes, and snippets.

@nickhudkins
Created December 18, 2020 14:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickhudkins/8532c936f273d1b1353dacaeed7c7890 to your computer and use it in GitHub Desktop.
Save nickhudkins/8532c936f273d1b1353dacaeed7c7890 to your computer and use it in GitHub Desktop.
Relationship Fetchers
import sangria.execution.deferred.{Fetcher, HasId, Relation, RelationIds, SimpleRelation}
object Fetchers {
/*
The following three methods are intended to be representative
of whatever your DAO may provide.
*/
def recipesForUsers(ids: Seq[Id[User]]): Future[Seq[Recipe]] = Future {
Recipe(id = 3, foodId = 2, userId = 3) :: Nil
}
def recipesForFoods(ids: Seq[Id[Food]]): Future[Seq[Recipe]] = Future {
Recipe(id = 2, foodId = 5, userId = 6) :: Nil
}
def recipesById(ids: Seq[Id[Recipe]]): Future[Seq[Recipe]] = Future {
Recipe(id = 1, foodId = 7, userId = 8) :: Nil
}
/*
Here's where the fun begins! Skip reading this for now
*/
// TODO: Can we do better? Can we NOT end up with type erasure? Maybe!
def recipesByRelation(ctx: MFPContext, value: RelationIds[Recipe]): Future[Seq[Recipe]] = {
val allRelations = value.rawIds.collect({
case (Recipe.UserRel, ids: Seq[Id[User]] @unchecked) => recipesForUsers(ids)
case (Recipe.FoodRel, ids: Seq[Id[Food]] @unchecked) => recipesForFoods(ids)
})
// It is possible that within a single fetch, we look things up by many different
// relations. Here we smush them all back together as though we fetched them as one.
Future.sequence(allRelations).map(_.flatten.toSeq)
}
val recipes = Fetcher.rel(
// By ID
(ctx: SomeContext, ids: Seq[Id[Recipe]]) => recipesById(ids),
// Relation Fetcher, take note of `RelationIds[Recipe]` which is a type
// that contains ALL relations defined for `Recipe` (see companion object of Recipe in models.scala)
(ctx: SomeContext, ids: RelationIds[Recipe]) => recipesByRelation(ctx, ids)
)(HasId(_.RecipeId))
// if you add a new fetcher it must be in this list
val All = List(
recipes,
)
}
import sangria.execution.deferred.{Relation, SimpleRelation}
case class User(id: Int)
case class Food(id: Int)
case class Recipe(id: Int, userId: Int, foodId: Int) {
val RecipeId = Id[Recipe](id)
val UserId = Id[User](userId)
val FoodId = Id[Food](foodId)
}
object Recipe {
private val USER_REL = "byUser"
private val FOOD_REL = "byFood"
val byUser = Relation[Recipe, Id[User]](USER_REL, c => Seq(c.UserId))
val byFood = Relation[Recipe, Id[Food]](FOOD_REL, c => Seq(c.FoodId))
val UserRel = SimpleRelation(USER_REL)
val FoodRel = SimpleRelation(FOOD_REL)
}
object SchemaDefinition {
val RecipeType = deriveObjectType[Context, Recipe]()
val Query = ObjectType(
"Query",
fields[Context, Unit](
Field(
"recipe",
OptionType(RecipeType),
// Normal
resolve = _ => Fetchers.recipes.deferOpt(Id[Recipe](1))
),
Field(
"recipeByFood",
ListType(RecipeType),
// The Magic! .deferRel / .deferRelSeq (1-to-1, 1-to-many) respectively
// Note the first arg is a `Relation`
resolve = _ => Fetchers.recipes.deferRelSeq(Recipe.byFood, Id[Food](5))
),
Field(
"recipeByUser",
ListType(RecipeType),
// The Magic! .deferRel / .deferRelSeq (1-to-1, 1-to-many) respectively
// Note the first arg is a `Relation`
resolve = _ => Fetchers.recipes.deferRelSeq(Recipe.byUser, Id[User](3))
)
)
)
val Definition = Schema(Query)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment