Fetching child records for non-JSON API compliant endpoints


#1

My API currently structures data like this.

Authors: [
  { id: 1, name: 'Jane Austen' },
  { id: 2, name: 'Stephen King' }
]
Books: [
  { id: 1, author-id: 1, title: 'Pride & Prejudice' }, 
  { id: 2: author-id: 2, title: 'IT' }, 
  { id: 3, author-id: 2, title: 'Cujo' }
]

The child records (sub-records? sub-models? child relationships? Not sure what the best term is) have references to the parent records, and not the other way around. This sort of reverse referenced relationship doesn’t seem to jive well with Ember.

I’m trying to figure out if there’s any way to easily fetch the child books when running a store.findAll('author') in Ember. Ideally, my authors would all come back with their respective embedded books for use in my template.

Right now, I’m manually fetching/setting books on authors in my route. It certainly works, but seems a little hokey.

  model() {
    return Ember.RSVP.hash({
      authors: this.get('store').findAll('author'),
      books: this.get('store').findAll('book')
    });
  },
  
  afterModel(model) {
   model.authors.map((author) => {
     let books = model.books.filterBy('authorId', author.id);
     author.set('books', books);
   });
  }

I’m assuming this is probably the only sort of approach that’ll work based on my API architecture, but I wanted to ask, is there some convention/serialization method/adapter magic I could potentially use to get what I want out of the box with Ember data?

https://ember-twiddle.com/67cbaeb149a68017e94ae07729bd3d74?openFiles=routes.application.js%2Ctemplates.components.author-books.hbs


#2

Sounds like the REST Adapter is closer to what your endpoint is doing (and is what I use in my apps). Unfortunately I think your author-id > author difference will need extra setup. (Haven’t done this myself), but I think you’ll need to define a REST Serializer for each model that has a difference and use attrs property or keyForAttribute() hook to define the mapping/difference. Ex:

import DS from 'ember-data';

export default DS.RESTSerializer.extend({
  attrs: {
    author: 'author-id'
  }
});

#3

Sorry, re-reading your post, my first post wasn’t what you were asking (but is still a valid point). I think if you define the reverse relationship yet on the author model, then it should still work (for what books have been loaded anyway). I could be completely wrong on this, but there should be a way to handle this since it’s a common pattern.

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  books: DS.hasMany('book')
});

#4

Thanks so much! That was a huge help in getting an answer. I think the core misunderstanding I had was around how to glue the two models together in Ember Data using my API response.

There may be better ways to do this, but I ended up customizing the normalize() method for my book serializer to format the authorId in such a way that Ember Data could pick up on the relationship.

{
      id: data.id, 
      type: type.modelName,
      attributes: {
        title: data.attributes.title
      },
      relationships: {
        author: {
          data: { type: 'author', id: data.attributes.authorId }
        }
      }
    };

Once I did that, the relationships were fulfilled properly in my templates. Your caveat still stands though. The books must be loaded in advance since the author records have no knowledge of the books.

These resources were also really helpful.

I updated my twiddle accordingly.