Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove type check that prevents polymorphic types in API responses #7421

Closed
wants to merge 1 commit into from

Conversation

ryandoherty
Copy link

@ryandoherty ryandoherty commented Jan 24, 2021

This is a PR for the request (#7357) to remove the assertion that prevents different types from being returned in API responses. Tests pass but I think there might need to be some tests added that assert Ember Data does 'the right thing' with varying types returned. (I have no idea how to do that :) )

Details:

tldr; This block:

if (type && type !== identifier.type) {
which verifies that the returned type matches the requested type is a problem for APIs that have polymorphic types. When we fetch data from the parent type's API endpoint, ember-data throws an exception because the api endpoint returns multiple types. But we do want all the subtypes of the parent type.

Example:

Given the JSON api:

/api/animals
/api/dogs
/api/cats

In our backend, 'dogs' and 'cats' are a subclass of 'animal', and queries to the /api/animals endpoint returns instances of cats and dogs. (This is via flask-restless & SQLAlchemy using the JSONAPI spec). Our Adapter is DS.JSONAPIAdapter

/api/animals returns:

{
  "data": [
    {
      "attributes": {
        "animal_type": "cat", <-- type column that SQLAlchemy uses to track the type of object to instantiate from the db row
        "created_at": "2020-10-19T13:29:07",
        "description": "my cat",
        "last_modified": "2020-10-21T15:04:08",
        "name": "foo"
      },
      "id": "1"
      },
      "type": "cat"
    },
    {
      "attributes": {
        "animal_type": "dog",
        "created_at": "2020-10-19T13:29:07",
        "description": "my dog",
        "last_modified": "2020-10-21T15:04:08",
        "name": "bar"
      },
      "id": "2"
      },
      "type": "dog"
    },
],
  "jsonapi": {
    "version": "1.0"
  },
  "links": {
    "first": "http://localhost:5000/api/animals?page[size]=10&page[number]=1",
    "last": "http://localhost:5000/api/animals?page[size]=10&page[number]=1",
    "next": null,
    "prev": null,
    "self": "/api/animals"
  },
  "meta": {
    "total": 2
  }
}

We have setup our ember models to have a parent type of 'Animal', 'Dog' and 'Cat' extend it. When we query the store for all animals like so: this.store.fetchAll('animals'), the reference error above is thrown because ember data expects only the type 'animal', but received 'dog' and 'cat'.

What we'd like ember to do is handle this and return an array (or individual object) of the types received. There are many use cases where we want to query and display all animals in the database, but we have to query each subtype separately. We've done some hacks to our API and ember app to support this (forcing all types for /api/animals to be 'animal' and jumping through some hoops after loaded to recreate the actual type on the frontend)

There are also situations when we know the id of the animal, but not type and would like to use this.store.findRecord('animal', id) and get back an instance of whatever subtype it is.

I hope this is clear. From the comment in ember-data's code where the exception is thrown, it appears there is awareness that it would help to remove the check. Thank you!

@ryandoherty ryandoherty changed the title Remote type check that prevents polymorphic types in API responses Remove type check that prevents polymorphic types in API responses Jan 25, 2021
@snewcomer snewcomer requested a review from igorT January 27, 2021 03:38
@snewcomer snewcomer added the code-review Tracks PRs that require a code-review label Jan 27, 2021
`The 'type' for a RecordIdentifier cannot be updated once it has been set. Attempted to set type for '${wrapper}' to '${type}'.`
);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ryandoherty! Do you have a test addition we can verify this results in added behaviour?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not, I can give it a whirl. First time working on anything Ember, happy to take pointers to where/how I should do this. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd note that we do have tests for single-table polymorphism support in place already. We might be able to improve our default implementation but the intent with identifiers is that you'll configure the cache in your own app to handle edge cases around one record having multiple potential identities.

@ryandoherty
Copy link
Author

Update on this: I am writing tests, my test case is failing because the way the store updates it passes the type that corresponds to the url and not the type that is returned to some functions to update from the api response. Will have an update in a few days.

@ryandoherty
Copy link
Author

So I spent more than a few hours trying to get tests working only to learn that doing this will require a substantial rewrite of the store.

The primary reason is the problem of how the store returns data from a findAll (and I'm assuming other calls). It loads up the RecordArray of the type of model it thinks it's fetching and returns it after the store has been populate with the response. But with this change, we could get back many types of objects, meaning we need to somehow merge all the RecordArrays together (I don't think this is possible b/c RecordArrays contain their type) or return a set of them.

I get the feeling that this change would require a substantial rewrite and new API to handle the case of "I asked the store to find items of typeA and it returned typeB and typeC that are subclasses of typeA."

I'm pretty new to Ember so unless I'm missing something I think this is as far as I can take this. I'd love to hear if I've understood all this correctly though, it was an interesting dive into Ember.

This is the code I'm looking at

Thanks!

@snewcomer
Copy link
Contributor

@ryandoherty Great job digging in! What version of e-d are you running? We do have some relevant tests. It looks like we do have a test for "changing type".

Do you happen to have a minimal reproduction (new ember app that reproduces this problem) we can use to make the test case? Or perhaps more insight on how these tests don't cover your scenario?

https://github.com/emberjs/data/blob/master/packages/-ember-data/tests/integration/identifiers/polymorphic-scenarios-test.ts

@ryandoherty
Copy link
Author

@snewcomer I just pushed up my test case I created. Any pointers is appreciated, but if I'm correct then this request may be a big rewrite beyond a simple PR.

@runspired
Copy link
Contributor

@ryandoherty in your app/tests you want query for this not findAll, query will return whatever records were listed in the primary data field of the JSON:API formatted response.

let handles = await store.findAll('handle');

let twitter_handles = store.peekAll('twitter-handles');
let github_handles = store.peekAll('github-handles');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be singular

{
data: [
{
type: 'github-handles',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be singular too, though this may not matter for the test

@runspired runspired self-assigned this Apr 10, 2021
@snewcomer
Copy link
Contributor

@ryandoherty I'm interested in getting this across the line. Do you want to pick it back up or should I take the baton? Happy to do whatever.

@ryandoherty
Copy link
Author

ryandoherty commented Feb 8, 2022 via email

@runspired
Copy link
Contributor

Closing in favor of #7955 for relationships and further RFC work for anything else. I'd note query likely "just works" and for peekAll / findAll a simple array concat would also "just work".

@runspired runspired closed this Sep 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
5.0 Roadmap code-review Tracks PRs that require a code-review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants