-
Notifications
You must be signed in to change notification settings - Fork 36
Description
I've noticed there is a non-standard behavior (feature?) that's breaking the json api spec when including different combinations of fields and include parameters.
For example, given three models Author, Book, and Article where an author has many books as well as many articles and both books and articles belong to an author:
if you request /authors/1 you will get a payload that looks like:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
},
"relationships": {
"books": {
"meta": {
"included": false
}
},
"articles": {
"meta": {
"included": false
}
}
}
}The fact that "relationships" is present when I didn't request anything in the include param is a little weird, but I guess it's helpful? More on this in a bit...
Next hitting /authors/1?include=books outputs something like:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
},
"relationships": {
"books": {
"data": [
{
"type": "books",
"id": 1
},
{
"type": "books",
"id": 2
}
]
}
}
},
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
}
]
}At first glance that seems pretty much as expected, and actually in my opinion this is correct. But what's weird is that the relationships node no longer includes:
"articles": {
"meta": {
"included": false
}
}meta is a non-standard, anything goes kinda field regardless, and relationships may or may not exist at any given time according to the spec so I guess this might be technically correct, but it's not what I would consider to be good consistency.
But it gets worse:
Go ahead and request /authors/1?include=books&fields[books]=name and the linkage between relationships and included is straight up broken:
{
"data": {
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
}
},
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
}
]
}You might say "Oh but you can just infer the books belong to the main resource," except the same thing happens on the index action as well (/authors?include=books&fields[books]=name):
{
"data": [
{
"id": 1,
"type": "authors",
"attributes": {
"name": "Neal Stephenson"
}
},
{
"id": 2,
"type": "authors",
"attributes": {
"name": "R.A. Salvatore"
}
}
],
"included": [
{
"id": 1,
"type": "books",
"attributes": {
"name": "Snow Crash"
}
},
{
"id": 2,
"type": "books",
"attributes": {
"name": "Cryptonomicon"
}
},
{
"id": 3,
"type": "books",
"attributes": {
"name": "The Legend of Drizzt"
}
}
]
}That ain't gunna work for anybody.
Anyway, I've been able to break this using various combinations of include and fields params and it all seems to boil down to one culprit (which is a combination of two conflicting ideas):
- The attempt to be extra helpful with non-standard meta information in the
relationshipssection when no relationships were asked for - This line passing
fieldsinstead ofincludeinto therequested_relationshipsmethod.
A naive fix is to modify two methods in lib/jsonapi/serializable/resource.rb
rels = requested_relationships(fields).each_with_object({}) do |(k, v), h|
h[k] = v.as_jsonapi(include.include?(k))
endto
rels = requested_relationships(include).each_with_object({}) do |(k, v), h|
h[k] = v.as_jsonapi(include.include?(k))
endand
def requested_relationships(fields)
@_relationships.select { |k, _| fields.nil? || fields.include?(k) }
endto
def requested_relationships(includes)
return {} if includes.empty?
@_relationships.select { |k, _| includes.include?(k) }
endThis has one caveat: relationships is no longer returned unless a user requested one or more via the include param (which in turn breaks a bunch of specs). However, this is still technically correct according to spec.
So what do you say? If I put in a PR to fix it can we live without
"relationships": {
"books": {
"meta": {
"included": false
}
},
"articles": {
"meta": {
"included": false
}
}
}which really wasn't helping anyone anyway?