Christian Zosel
Christian Zosel

Reputation: 1424

Ember data JSONAPIAdapter: fetch nested resources

I'm trying to get Ember Data's JSONAPIAdapter to work with nested resources. For the server part django-rest-framework-json-api is used.

My (simplified) ember models:

case.js

export default Model.extend({
  firstName: attr('string'),
  lastName: attr('string'),
  comments: hasMany('comment'),
})

comment.js

export default Model.extend({
  text: attr('string'),
  case: belongsTo('case'),
})

The server's response for /api/v1/cases/4 looks like this:

{
  "data": [
    {
      "type": "cases",
      "id": "4",
      "attributes": {
         "first-name": "Hans",
         "last-name": "Peter",
      },
      "relationships": {
        "comments": {
          "meta": {
            "count": 1
          },
          "data": [
            {
              "type": "comments",
              "id": "5"
            }
          ],
          "links": {
            "related": "http://localhost:8000/api/v1/cases/4/comments"
          }
        }
      }
    }
  ]
}

Now, if i understand Ember Data and the JSON-API spec correctly, ember should request /api/v1/cases/4/comments when i reference the comments. Instead, it requests /api/v1/comments/5, which obviously returns a 404.

My questions in summary:

I'm using ember v2.8.

Bonus question: I face the same problem for creating a new comment - how do i get ember to POST to /case/4/comments instead of /comments?

Upvotes: 0

Views: 522

Answers (2)

Jonathan Jansen
Jonathan Jansen

Reputation: 133

Yes this works it should be setup as follows

models/client.js

export default DS.Model.extend({
    name: DS.attr('string'),
    telno: DS.attr('string'),

    campaigns: hasMany()
});

models/client.js

export default DS.Model.extend({
    name: DS.attr('string'),
    startdate: DS.attr('date'),
    enddate: DS.attr('date'),

    client: DS.belongsTo('client')
});

/templates/client/edit.bhs

    <table class="table table-bordered table-striped table-condensed">
        <thead>
            <tr>
                <th></th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            {{#each model.campaigns as |campaign|}}
                <tr>
                    <td>{{campaign.name}}</td>
                </tr>
            {{/each}}
        </tbody>
    </table>

http://localhost:3000/api/clients/1

{
{
  "links": {
    "self": "http://localhost:3000/api/clients/1"
  },
  "data": {
    "type": "clients",
    "relationships": {
      "campaigns": {
        "links": {
          "related": "http://localhost:3000/api/clients/1/campaigns"
        }
      }
    },
    "id": "1",
    "attributes": {
      "name": "Test",
      "telno": "123"
    },
    "links": {
      "self": "http://localhost:3000/api/clients/1"
    }
  }
}

http://localhost:3000/api/clients/1/campaigns

{
  "links": {
    "self": "http://localhost:3000/api/clients/1/campaigns"
  },
  "data": [
    {
      "type": "campaigns",
      "relationships": {
        "client": {
          "links": {
            "related": "http://localhost:3000/api/campaigns/1/client"
          }
        }
      },
      "id": "1",
      "attributes": {
        "enddate": "2019-01-01T00:00:00.000Z",
        "name": "test",
        "startdate": null
      },
      "links": {
        "self": "http://localhost:3000/api/campaigns/1"
      }
    }
  ]
}

Upvotes: 1

Chris Peters
Chris Peters

Reputation: 18090

The JSON API spec does not enforce any specific URL pattern, so what you're trying to do is compliant. However, I find that working with a flat URL structure is easier with Ember Data, though there is a workaround.

You'll want to look at the ember-data-url-templates addon and add some logic from it to your model's adapter.

With that addon, here is what you can do with app/adapters/comment.js:

import ApplicationAdapter from './application';
import UrlTemplates from 'ember-data-url-templates';

export default ApplicationAdapter.extend(UrlTemplates, {
  namespace: 'api/v1', // You may or may not need this namespace setting:
                       // I'm a little rusty in this area :)

  urlTemplate: '{+host}/case/{caseId}/comments{/id}',

  urlSegments: {
    contentId: function(type, id, snapshot/*, query */) {
      return snapshot.belongsTo('case', { id: true });
    }
  }
});

Unless there is something else that the addon allows to get around this, I believe that this then locks you into that URL structure for comments across your entire app. So definitely weigh that tradeoff before deciding to go down this route.

Upvotes: 1

Related Questions