ChromaticHQ / jsonmonger

23 stars 1 forks source link

Support nested relationship fetching #11

Open agarzola opened 6 years ago

agarzola commented 6 years ago

A Book, for example, would have a related Author, which in turn might have a related Location record for the place of birth. When fetching a book, it may be useful to have jsonmonger include not only the author, but the author’s location. The tricky part of this is that jsonmonger can’t know which type might be related to a particular property, so the model itself needs to declare this in the related config somehow.

An object might be the way to go here. Using the example above:

const Author = new Model({
  birthplace: 'relationships.location_born',
});

const Book = new Model({
  author: 'relationships.author',
}, {
  related: {
    author: {
      Author: 'birthplace',
    },
  },
});

The result should be that the include query string param would look like this:

?include=author.location_born

which the API should use to include the related author and their related birthplace in the payload.

Suggestions welcome on this interface. I think it’s a bit awkward, but can’t think of another way to accomplish this.

ghost commented 6 years ago

@agarzola Can we also add to this feature the ability to do this recursively? Let me know if you still need help on this. I saw you at Decoupled Days and learned about this project and am really wanting to use it as my management tool!

agarzola commented 6 years ago

Hi, @navenedrob! 👋

So using the example above, let’s say the Location model looks like this:

const Location = new Model({
  category: 'relationships.location_type',
});

Are you wanting to declare that nested relationship in the Author model like so?

const Author = new Model({
  birthplace: 'relationship.location_born',
}, {
  related: {
    birthplace: {
      Location: 'category',
    },
  },
});

And so, when requesting a Book, the query string param would read:

?include=author.location_born.location_type

Is that what you mean?

agarzola commented 6 years ago

This will also require a mechanism to avoid infinite loops. Say Location looks like this:

const Location = new Model({
  category: 'relationships.location_type',
  famous_authors: 'relationships.famous_authors',
}, {
  related: {
    famous_authors: {
      Author: 'birthplace',
    },
  },
});

Fetching a Book would result in a stack overflow as its Author requires a Location, which will require zero or more Authors, which will require Locations, which will require zero or more Authors, etc.

My current thinking on this is that once a Model has been processed as part of nested relationship fetching, subsequent uses of the same model will not include its relationships. In the example above, the Book would get its author, which would get its birthplace, which would get its famous_authors, and it stops there (Book > Author > Location > Author).

It feels a little arbitrary to stop there and not sooner (or later), but since relationships are hydrated Models, getting around it is easy. So if you needed one of those famous_authors’ books, for instance, you could easily fetch them like so:

function get_related_authors(book) {
  const related_authors = book.author.birthplace.famous_authors;
  return Promise.all( related_authors.map(author => author.fetch()) );
}

This could also be implemented as a model method or, if/when we get event hooks implemented, it can be implemented that way.