Ember + JsonAPI - How to correctly handle relationships?


#1

Hello I have a Post object which has zero-or-more Comment. My API looks as follows:

{
	"data": {
		"type": "post",
		"id": "1",
		"attributes": {
			"title": "Hello World"
		},
		"relationships": {
			"comment" : {
				"links": {
					"self": "/api/posts/1/relationships/comments",
					"related": "/api/posts/1/comments"
				},
				"data": [
					{ "type": "comment", "id": "1" },
					{ "type": "comment", "id": "2" }
				]
			}
		}
	}
}

My API URLs are as follows;

The following URL gets the posts: http://example.com/api/posts/

The following URL gets a post by it’s id: http://example.com/api/posts/1

The following URL gets all the comments of a particular post: http://example.com/api/posts/1/comments/

In Ember I have created my models as follows:

// in file /app/models/post.js
import DS from 'ember-data';
export default DS.Model.extend({
	title: DS.attr(),
	comment: DS.hasMany('comment')
});

// in file /app/models/comment.js
import DS from 'ember-data';
export default DS.Model.extend({
	text: DS.attr()
});

However when Ember tries to load the comments of a particular post rather then trying to load this using the related link in the comment relationships element, it goes and tries to load this from the following URL:

http://example.com/api/comments/

However my API does not provide that URL as a comment only exists as part of a post. I have searched on the internet and found some examples which say to override serializer for post. So I did as follows:

// in file /app/serializers/post.js
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
	normalizeResponse(store, primaryModelClass, payload, id, requestType) {
		if(id == null) return this._super(...arguments);
		delete payload.data.relationships['comment'].data;
		payload.data.relationships['comment'].links = {
			related: "/api/posts/" + payload.data.id + "/comments"
		};
		return this._super(...arguments);
	},
});

The above code works, but, on first load always gives an error because still Ember goes to look for the comments in /api/comments rather than /api/posts/:id/comments (I don’t understand why). However page still loads, and after this the error no longer pops up until I restart again.

However I wonder if this is the right way to do this.

Is there an out of the box way how to make Ember automatically use relationships link?


#2

You can use an adapter for your model instead of a serializer. I have the same setup but with teams and members. I have a serializer that looks like :

import { computed, get } from '@ember/object';
import { inject as service } from '@ember/service';
import ENV from '../config/environment';
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';

export default JSONAPIAdapter.extend(DataAdapterMixin, {
  host: ENV.your_host,
  authorizer: 'your authorizer',

  namespace: computed('session.current_team', λ() {
  ⇚ `teams/${get(@, 'session.current_team')}`;
  }),

  session: service('session'),
  headers: computed('session.your_auth_header_value', λ() {
    ⇚ {
    'Auth-Header': get(@, 'session.your_auth_header_value')
    };
  })
});

And all my requests for members go to /teams/:id/members/(:id)


#3

Ember Data will normally use the relationships link, but when data is also provided, Ember Data prefers the data.

In other words, I believe the following would also work to resolve your issue (I haven’t tried it though).

// in file /app/serializers/post.js
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
	normalizeResponse(store, primaryModelClass, payload, id, requestType) {
		if(id == null) return this._super(...arguments);
		delete payload.data.relationships['comment'].data;
		return this._super(...arguments);
	},
});

In case it is also useful, I wrote a bit about how Ember Data loads these asynchronous relationships: http://www.amielmartin.com/blog/2017/08/31/how-ember-data-loads-async-relationships/

In particular, check out Part 2, which specifically discusses this case: http://www.amielmartin.com/blog/2017/05/17/how-ember-data-loads-async-relationships-part-2/.