Polymorphic Associations

Suppose we have a Rating model with a score field (integer/enum between 1-5) and a text field (string):

Facility has_many Ratings, through: FacilityRatings
Customer has_many Ratings, through : CustomerRatings

How do I do this?

So… I’m trying to work out a solution, though the more I work on it the less I like it!

Here is the idea so far. I wrote a custom resolver called ratingEntity(id: String!) where when given an rating id, it returns the entity that entity belongs to.

There are no 8base table relationships. The rating table has the following fields.

Ratings
  entityId: TEXT
  entityType: TEXT
  rating: NUMBER

The query looks likes this:

query {
  ratingEntity(id: "ck1l6il11002i01l755nkfx6h") {
    facility {
      id
      name
    }
    customer {
      id
      nickname
    }
  }
}

and returns:

{
  "data": {
    "ratingEntity": {
      "facility": {
        "id": "ck1l643v5030l01jncwpb4htp",
        "name": "Our House"
      },
      "customer": null
    }
  }
}

Here is the resolver function handler:

type RatingEntityResult = {
  data: {
    facility: {
      id: ID,
      name: String,
      createdAt: Date,
      updatedAt: Date,
      mascot: String
    },
    customer: {
      id: ID,
      name: String,
      createdAt: Date,
      updatedAt: Date,
      nickname: String
    }
  }
};

const fragments = {
  facility: `id name createdAt updatedAt mascot`,
  customer: `id name createdAt updatedAt nickname`
}

const RATING_QUERY = `
  query($id: ID!) {
    rating(id: $id) {
      entityId
      entityType
    }
  }
`;

const ENTITY_QUERY = (type) => (`
  query($entityId: ID!) {
    ${type}(id: $entityId) {
      ${fragments[type]}
    }
  }
`);

export default async (event: any, ctx: any) : Promise<RatingEntityResult> => {
  /* Get the rating entityId and entityType */
  const typeResponse = await ctx.api.gqlRequest(RATING_QUERY, { id: event.data.id })

  /* Unpack required variables */
  const { entityType, entityId } = typeResponse.rating;

  /* Get the entity */
  const entityResponse = await ctx.api.gqlRequest(ENTITY_QUERY(entityType), { entityId })

  return {
    data: entityResponse
  };
};

And the graphql.schema file:

type RatingFacility {
  id: ID,
  name: String,
  createdAt: Date,
  updatedAt: Date,
  mascot: String
}

type RatingCustomer {
  id: ID,
  name: String,
  createdAt: Date,
  updatedAt: Date,
  nickname: String
}

type RatingEntityResult {
  facility: RatingFacility,
  customer: RatingCustomer
}

extend type Query {
  ratingEntity(id: ID!): RatingEntityResult
}

Now there are some obvious limitations here. However, we can keep working on it together.

1 Like

Aye that is a bit of work eh

How about saying (albeit not very DRY-ly):

Facility has many FacilityReviews
Customer has many CustomerReviews
type FacilityReview {
  id: ID,
  rating: Int,
  text: String,
  createdAt: Date,
  updatedAt: Date,
  createdBy: Customer
}

type CustomerReview {
  id: ID,
  rating: Int,
  text: String,
  createdAt: Date,
  updatedAt: Date,
  createdBy: Facility
}
1 Like

Have you tried that solution? I’m not sure whether the system will allow an override of the createdBy field. Also, how would you then go about specifying the response?

I have not, should probably give it a shot.

In pseudo-ORM queries, I would expect it to look a bit like:

someCustomer = customer.find()
currentFacility.customerReviews.create({ customer: someCustomer, rating: 3, text: "Lorem ipsum..." })

someFacility = facility.find()
currentCustomer.facilityReviews.create({ facility: someFacility, rating: 3, text: "Lorem ipsum..." })

Absolutely. The issue we are running into here is that the response has to be typed. So we cant have a response on one query return Facility fields and then on the next query return Customer fields. We need to somehow handle them dynamically.

I believe that there is a way to use fragments to accomplish this… but I’m not 100% sure ATM.