devour-client icon indicating copy to clipboard operation
devour-client copied to clipboard

Recursive/Arc Relationships

Open webdev778 opened this issue 7 years ago • 3 comments

Hello, First of all, I thanks for your DEVOUR. devour json api is a great package, I think. I've seen all approaches in the JavaScript client implementations but jsonapi-datastore and json-api-store are a little useful and the others not.

Finally, I 've determined to use your package DEVOUR.

it works fine so far. but one issue.

In my database, there is "categories" table and it represents both category and subcategory using parent_id field(Arc Model).

and with ruby on rails, I've built JSON API Server.

when hits the url http://192.168.0.124:3003/api/v1/categories?include="sub-categories", it returns as follows. ( "id" : "1", category, in the sub-categories section, it has sub categories information)

{

"data": [
  {
    "id": "1",
    "type": "categories",
    "links": {
      "self": "http://192.168.0.124:3003/api/v1/categories/1"
    },
    "attributes": {
      "name": "Cat1",
      "category-type": 0,
      "sort": 1,
      "deleted": false,
      "created-at": "2017-07-31T10:48:48.000Z",
      "updated-at": "2017-07-31T10:48:50.000Z"
    },
    "relationships": {
      "sub-categories": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/1/relationships/sub-categories",
          "related": "http://192.168.0.124:3003/api/v1/categories/1/sub-categories"
        },
        "data": [
          {
            "type": "categories",
            "id": "3"
          },
          {
            "type": "categories",
            "id": "4"
          }
        ]
      },
      "parent": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/1/relationships/parent",
          "related": "http://192.168.0.124:3003/api/v1/categories/1/parent"
        }
      },
      "items": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/1/relationships/items",
          "related": "http://192.168.0.124:3003/api/v1/categories/1/items"
        }
      }
    }
  },
  {
    "id": "2",
    "type": "categories",
    "links": {
      "self": "http://192.168.0.124:3003/api/v1/categories/2"
    },
    "attributes": {
      "name": "Cat2",
      "category-type": 0,
      "sort": 2,
      "deleted": false,
      "created-at": "2017-07-31T11:21:14.000Z",
      "updated-at": "2017-07-31T11:21:15.000Z"
    },
    "relationships": {
      "sub-categories": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/2/relationships/sub-categories",
          "related": "http://192.168.0.124:3003/api/v1/categories/2/sub-categories"
        },
        "data": []
      },
      "parent": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/2/relationships/parent",
          "related": "http://192.168.0.124:3003/api/v1/categories/2/parent"
        }
      },
      "items": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/2/relationships/items",
          "related": "http://192.168.0.124:3003/api/v1/categories/2/items"
        }
      }
    }
  },
  {
    "id": "3",
    "type": "categories",
    "links": {
      "self": "http://192.168.0.124:3003/api/v1/categories/3"
    },
    "attributes": {
      "name": "Sub Cat1",
      "category-type": 1,
      "sort": 1,
      "deleted": false,
      "created-at": "2017-07-31T11:27:21.000Z",
      "updated-at": "2017-07-31T11:27:22.000Z"
    },
    "relationships": {
      "sub-categories": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/3/relationships/sub-categories",
          "related": "http://192.168.0.124:3003/api/v1/categories/3/sub-categories"
        },
        "data": []
      },
      "parent": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/3/relationships/parent",
          "related": "http://192.168.0.124:3003/api/v1/categories/3/parent"
        }
      },
      "items": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/3/relationships/items",
          "related": "http://192.168.0.124:3003/api/v1/categories/3/items"
        }
      }
    }
  },
  {
    "id": "4",
    "type": "categories",
    "links": {
      "self": "http://192.168.0.124:3003/api/v1/categories/4"
    },
    "attributes": {
      "name": "Sub Cat2",
      "category-type": 1,
      "sort": 1,
      "deleted": false,
      "created-at": "2017-07-31T11:27:54.000Z",
      "updated-at": "2017-07-31T11:27:55.000Z"
    },
    "relationships": {
      "sub-categories": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/4/relationships/sub-categories",
          "related": "http://192.168.0.124:3003/api/v1/categories/4/sub-categories"
        },
        "data": []
      },
      "parent": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/4/relationships/parent",
          "related": "http://192.168.0.124:3003/api/v1/categories/4/parent"
        }
      },
      "items": {
        "links": {
          "self": "http://192.168.0.124:3003/api/v1/categories/4/relationships/items",
          "related": "http://192.168.0.124:3003/api/v1/categories/4/items"
        }
      }
    }
  }
]

}

and in the client side,

this.jsonApi.define('category',{
  name: '',
  categoryType: '',
  sort: '',
  deleted: '',
  createdAt: '',
  updatedAt: '',
  items:{
    jsonApi: 'hasMany',
    type: 'items'
  },
  subCategories:{
    jsonApi: 'hasMany',
    type: 'categories'
  },
  parent:{
    jsonApi: 'hasOne',
    type: 'category'
  }
})

and when I call below function it returns unexpected results.

this.jsonApi.findAll('category', {include: ['sub-categories']})

[{
  id: '1',
  type: 'categories',
  name: 'Cat1',
  categoryType: 0,
  sort: 1,
  deleted: false,
  createdAt: '2017-07-31T10:48:48.000Z',
  updatedAt: '2017-07-31T10:48:50.000Z',
  subCategories: [],
  parent: null,
  items: [],
  links: { self: 'http://192.168.0.124:3003/api/v1/categories/1' }
},
{
  id: '2',
  type: 'categories',
  name: 'Cat2',
  categoryType: 0,
  sort: 2,
  deleted: false,
  createdAt: '2017-07-31T11:21:14.000Z',
  updatedAt: '2017-07-31T11:21:15.000Z',
  subCategories: [],
  parent: null,
  items: [],
  links: { self: 'http://192.168.0.124:3003/api/v1/categories/2' }
},
{
  id: '3',
  type: 'categories',
  name: 'Sub Cat1',
  categoryType: 1,
  sort: 1,
  deleted: false,
  createdAt: '2017-07-31T11:27:21.000Z',
  updatedAt: '2017-07-31T11:27:22.000Z',
  subCategories: [],
  parent: null,
  items: [],
  links: { self: 'http://192.168.0.124:3003/api/v1/categories/3' }
},
  {
    id: '4',
    type: 'categories',
    name: 'Sub Cat2',
    categoryType: 1,
    sort: 1,
    deleted: false,
    createdAt: '2017-07-31T11:27:54.000Z',
    updatedAt: '2017-07-31T11:27:55.000Z',
    subCategories: [],
    parent: null,
    items: [],
    links: { self: 'http://192.168.0.124:3003/api/v1/categories/4' }
  }]

as you can see, it doesn't take sub categories from server.

what's missing? and wrong?

Hope your help.

Best Regards.

webdev778 avatar Aug 02 '17 15:08 webdev778

I think it's because you used camelCase in the API definition when you should be using kebab-case. If you enclode the property key in brackets, that is allowed.

So devour looks for "subCategory" and see there are none, and does not look for "sub-category" as your API is providing it.

this.jsonApi.define('category',{
  name: '',
  categoryType: '',
  sort: '',
  deleted: '',
  createdAt: '',
  updatedAt: '',
  items:{
    jsonApi: 'hasMany',
    type: 'items'
  },
  'sub-categories':{
    jsonApi: 'hasMany',
    type: 'categories'
  },
  parent:{
    jsonApi: 'hasOne',
    type: 'category'
  }
})

GregPeden avatar Oct 08 '17 20:10 GregPeden

I'm having this same issue, but with comments so camelCase vs kebab-case isn't the issue for me at least.

I get all comments in a flat array, rather than being parent -> child like they should be. I'd be happy to handle re-organising them on my side, but I can't get access to the links between them, so at this point I'm unable to determine on the client what the structure should be.

thekevinbrown avatar Oct 21 '17 06:10 thekevinbrown

To prevent the issue that multi-worded attributes are automatically converted to camelCase while relationships are kept in kebab-case, I added the following middleware. Maybe this is helpful for some (@yaodev778, @SirLamer):

  const dashToCamelCase = str => {
    return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
  }

  const noteRelationshipsInCamelCase = items => {
    for (const item of items) {
      const convertedRelationships = {}
      for (const relationship in item.relationships) {
        if (item.relationships.hasOwnProperty(relationship)) {
          convertedRelationships[dashToCamelCase(relationship)] = item.relationships[relationship]
        }
      }
      item.relationships = convertedRelationships
    }
    return items
  }

  const responseMiddleware = {
    name: 'note-relationships-in-camel-case-notation',
    res: (payload) => {
      payload.res.data.data = noteRelationshipsInCamelCase(payload.res.data.data)
      return payload
    },
  }

  jsonApi.insertMiddlewareBefore('response', responseMiddleware)

Wouldn't it make sense for devour to do this be default like it does for attributes?

ArneZsng avatar Apr 16 '18 12:04 ArneZsng