pgPolymorphic
This step class gives just one of many ways of supporting polymorphism in
@dataplan/pg
; we recommend that you read the polymorphism
documentation before deciding whether or not you need this step, since it is
one of the more verbose options! It's likely that a simpler approach may suit
you.
pgPolymorphic works by taking a record step (a pgSelectSingle
), a specifier
step (an arbitrary step) and a entity type map. The entity type map keys are
the GraphQL type names that are possible, and for each of these the value is an
object with the following properties:
match
- a function that is passed the specifier data and returns true if it's a match for this typeplan
- a function that is passed the specifier step of a matching specifier and the original record step, and should return a step representing the record identified by this specifier.
Example
Imagine we have a database in which you can favourite records from three different tables: "people", "posts" and "comments".
These favourites are stored in a single "favourites" table, which among other
attributes (such as the id of the user doing the favouriting) has three
attributes to indicate which entity was favourited: liked_person_id
,
liked_post_id
, liked_comment_id
. These three attributes implement the "one
of" pattern - exactly one of these three attributes will be set, the others
being null.
We want to retrieve the entity that you have favourited, based on the step
$favourite
which represents the row in the "favourites" table.
Using the above knowledge, we can build a specifier step, $specifier
, that
contains all the data necessary to determine the polymorphic record we wish to
retrieve by representing the three "one of" ids as a tuple (or list).
const $specifier = list([
$favourite.get("liked_person_id"),
$favourite.get("liked_post_id"),
$favourite.get("liked_comment_id"),
]);
We prepare our personFavouriteEntityTypeMap
matcher based around this specifier,
it looks at the tuple and determines:
- if the first entry in the tuple, the one associated with
liked_person_id
, is not null then the record must represent aPerson
, and we can get the related person frompersonResource
whereperson_id
is equal to this first entry in the tuple - otherwise, if the second entry is not null then the record must represent a
Post
, and we can get that post frompostResource
in a similar way - otherwise, if the third entry is not null then the record must represent a
Comment
, and we can get that comment in a similar way
const personFavouriteEntityTypeMap = {
Person: {
match: (specifier) => specifier[0] != null,
plan: ($specifier) => personResource.get({ person_id: $specifier.at(0) }),
},
Post: {
match: (specifier) => specifier[1] != null,
plan: ($specifier) => postResource.get({ post_id: $specifier.at(1) }),
},
Comment: {
match: (specifier) => specifier[2] != null,
plan: ($specifier) => commentResource.get({ comment_id: $specifier.at(2) }),
},
};
Finally we pass our record step ($favourite
), our specifier step
($specifier
) and our matcher (personFavouriteEntityTypeMap
) to
pgPolymorphic
and it will know how to retrieve the associated record - the
person, post or comment:
return pgPolymorphic($favourite, $specifier, personFavouriteEntityTypeMap);