Skip to content

Circular inclusions can lose data #802

@drakedowsett

Description

@drakedowsett

Verified on 0.8.0.beta2 in a simple testing app with three models: Box, Thing, User, where:

boxes ------> things
       many    | ^
           one | |
               | | many
               v |
              users

When requesting

/api/boxes?include=things,things.user

everything looks ok, but when requesting one level deeper for

/api/boxes?include=things,things.user,things.user.things

so that the User includes its Things, some of the thing-to-user relationship data disappears.

It seems that there's a depth-first search that explores the first thing-to-user relationship via the first Thing it encounters and finds the remaining Things through the user-to-things relationship without including the thing-to-user relationship. Requesting

/api/boxes?include=things,things.user,things.user.things,things.user.things.user

with yet another circular reference resolves this, corroborating the depth-first hypothesis.

An example: one Box with two Things, each with the same User. Requests and results of included list (with links and box relationship removed):

# /api/boxes?include=things,things.user
...
    {
      "id": "1",
      "type": "things",
      "relationships": {
        "user": {
          "data": {
            "type": "users", "id": "1"
          }
        }
      }
    },
    {
      "id": "2",
      "type": "things",
      "relationships": {
        "user": {
          "data": {
            "type": "users", "id": "1"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "users",
      "relationships": {
        "things": {}
      }
    }
...
...
# /api/boxes?include=things,things.user,things.user.things
    {
      "id": "1",
      "type": "things",
      "relationships": {
        "user": {}
      }
    },
    {
      "id": "2",
      "type": "things",
      "relationships": {
        "user": {
          "data": {
            "type": "users", "id": "1"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "users",
      "relationships": {
        "things": {
          "data": [
            {
              "type": "things", "id": "1"
            },
            {
              "type": "things", "id": "2"
            }
          ]
        }
      }
    }
...

...Thing loses its User data. To get both the things-to-user data and also the user-to-things data, I am having to request:

# /api/boxes?include=things,things.user,things.user.things,things.user.things.user
...
    {
      "id": "1",
      "type": "things",
      "relationships": {
        "user": {
          "data": {
            "type": "users", "id": "1"
          }
        }
      }
    },
    {
      "id": "2",
      "type": "things",
      "relationships": {
        "user": {
          "data": {
            "type": "users", "id": "1"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "users",
      "relationships": {
        "things": {
          "data": [
            {
              "type": "things", "id": "1"
            },
            {
              "type": "things", "id": "2"
            }
          ]
        }
      }
    }
...

My testing app setup:

Rails.application.routes.draw do
  namespace :api do
    jsonapi_resources :boxes
  end
end

class Box < ActiveRecord::Base
  has_many :things
end

class Thing < ActiveRecord::Base
  belongs_to :box
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :things
end

class Api::BoxesController < ApplicationController
  include JSONAPI::ActsAsResourceController
end

class Api::BoxResource < JSONAPI::Resource
  has_many :things
end

class Api::ThingResource < JSONAPI::Resource
  has_one :box
  has_one :user
end

class Api::UserResource < JSONAPI::Resource
  has_many :things
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions