wvteijlingen / Spine

A Swift library for working with JSON:API APIs. It supports mapping to custom model classes, fetching, advanced querying, linking and persisting.
MIT License
266 stars 109 forks source link

ToManyRelationship and included resources #65

Closed alak closed 8 years ago

alak commented 8 years ago

Hey,

Here is my json :

{
    "data": {
        "type": "ads",
        "id": "671",
        "attributes": {
            "id": 671,
            "title": "SONY TV (de 32 pouces)",
        },
        "relationships": {
            "seller": {
                "data": {
                    "type": "persons",
                    "id": "95vl7"
                }
            },
        }
    },
    "included": [
        {
            "type": "persons",
            "id": "95vl7",
            "attributes": {
                "hash": "95vl7",
                "surname": "Prawidlo",
                "given_name": "Marine",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "87aY7"},
                        {"type": "persons", "id": "xN21b"}
                    ]
                }
            }
        },
        {
            "type": "persons",
            "id": "87aY7",
            "attributes": {
                "hash": "87aY7",
                "surname": "Abergel",
                "given_name": "Arthur",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            }
        },
        {
            "type": "persons",
            "id": "xN21b",
            "attributes": {
                "hash": "xN21b",
                "surname": "Dubreuil",
                "given_name": "Jeremy",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            }
        }
    ]
}

My query code :

let spine = Spine(baseURL: url)

// Registering entities
spine.registerResource(Ad)
spine.registerResource(User)
var query = Query(resourceType: Ad.self, resourceIDs: ["691"])
query.include("evaluations", "seller.friends")
spine.find(query).onSuccess(callback: { (resources, meta, jsonapi) -> Void in
      print("success")
}).onFailure { (error) -> Void in
      print(error)
}

my errors :

❕ Info    - Cannot resolve to-many link persons:87aY7 - friends because the linked collection is not fetched.
❕ Info    - Cannot resolve to-many link persons:xN21b - friends because the linked collection is not fetched.

I don't really understand why. Any idea?

Thanks.

wvteijlingen commented 8 years ago

These are info logs, they are not errors. The resource persons has a relationship friends, but the JSON for persons:87aY7 and persons:xN21b does not contain any info for that relationship. Therefore Spine cannot set any relationship data on those deserialised person resources. These logs are just there to let you know of this.

The find operation should still just resolve to success. If this is not the case, there is a bug somewhere.

alak commented 8 years ago

The find operation resolve to success but I don't understand why the link can't be resolve, in the json you can see that data for persons:87aY7 and persons:xN21b are included.

wvteijlingen commented 8 years ago

It should resolve the friends link from persons:95v17, because the json for that contains the relationship data. However it cannot resolve the link from persons:87aY7 and persons:xN21b, because the json only includes the attributes.

alak commented 8 years ago

So it mean that the JSON should look something like (with sub included object)? :

{
    "data": {
        "type": "ads",
        "id": "671",
        "attributes": {
            "id": 671,
            "title": "SONY TV (de 32 pouces)"
        },
        "relationships": {
            "seller": {
                "data": {
                    "type": "persons",
                    "id": "95vl7"
                }
            },
        }
    },
    "included": [
        {
            "type": "persons",
            "id": "95vl7",
            "attributes": {
                "hash": "95vl7",
                "surname": "Prawidlo",
                "given_name": "Marine",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "87aY7"},
                        {"type": "persons", "id": "xN21b"}
                    ]
                }
            },
            "included": [
              {
                  "type": "persons",
                  "id": "87aY7",
                  "attributes": {
                      "hash": "87aY7",
                      "surname": "Abergel",
                      "given_name": "Arthur",
                      "verified": true,
                      "gamification_level": 3,
                      "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
                  }
              },
              {
                  "type": "persons",
                  "id": "xN21b",
                  "attributes": {
                      "hash": "xN21b",
                      "surname": "Dubreuil",
                      "given_name": "Jeremy",
                      "verified": true,
                      "gamification_level": 3,
                      "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
                  }
              }
            ]
        }
    ]
}
wvteijlingen commented 8 years ago

No, that is not valid JSON:API. It should look like this. Notice the addition of the relationships to the included person.s

{
    "data": {
        "type": "ads",
        "id": "671",
        "attributes": {
            "id": 671,
            "title": "SONY TV (de 32 pouces)",
        },
        "relationships": {
            "seller": {
                "data": {
                    "type": "persons",
                    "id": "95vl7"
                }
            },
        }
    },
    "included": [
        {
            "type": "persons",
            "id": "95vl7",
            "attributes": {
                "hash": "95vl7",
                "surname": "Prawidlo",
                "given_name": "Marine",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "87aY7"},
                        {"type": "persons", "id": "xN21b"}
                    ]
                }
            }
        },
        {
            "type": "persons",
            "id": "87aY7",
            "attributes": {
                "hash": "87aY7",
                "surname": "Abergel",
                "given_name": "Arthur",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "95vl7"}
                    ]
                }
            }
        },
        {
            "type": "persons",
            "id": "xN21b",
            "attributes": {
                "hash": "xN21b",
                "surname": "Dubreuil",
                "given_name": "Jeremy",
                "verified": true,
                "gamification_level": 3,
                "avatar_url_pattern": "/users/{id}/avatars/{width}x{height}/{cropped}"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "95vl7"}
                    ]
                }
            }
        }
    ]
}
alak commented 8 years ago

Ok, thanks for your answers, I'll try it tomorrow :).

But It will create a 2 way relationship. How to handle a one way relationship ?

RomainSauvaire commented 8 years ago

Hi @wvteijlingen, I'm working with @Alak. The idea here is that I have an ad (ads type) with a seller (persons type). The seller has many friends and we only need to define the relationship in this way. Why is that needed/do you recommend to declare the inverse too ?

Simplified JSON :

{
    "data": {
        "type": "ads",
        "id": "671",
        "attributes": {
            "id": 671,
            "title": "SONY TV",
        },
        "relationships": {
            "seller": {
                "data": {
                    "type": "persons",
                    "id": "95vl7"
                }
            },
        }
    },
    "included": [
        {
            "type": "persons",
            "id": "95vl7",
            "attributes": {
                "foo": "bar"
            },
            "relationships": {
                "friends": {
                    "data": [
                        {"type": "persons", "id": "87aY7"},
                        {"type": "persons", "id": "xN21b"}
                    ]
                }
            }
        },
        {
            "type": "persons",
            "id": "87aY7",
            "attributes": {
                "foo": "bar"
            }
        },
        {
            "type": "persons",
            "id": "xN21b",
            "attributes": {
                "foo": "bar"
            }
        }
    ]
}
wvteijlingen commented 8 years ago

It is not needed not specify the inverse relationship data in the JSON. But, Spine does not infer the inverse automatically. So the friends variable of your Seller resource will contain the two other persons, but the friends variable of those persons will be nil.

This is no problem at all, the info log message is just there to let you know that Spine tried to look for friends but didn't find it in the JSON. However, personally I always include the inverse in the JSON if it exists, just so that my local models accurately represent the state of the data on the server. If the relationship is just one-way, then all should be fine as it is now.

alak commented 8 years ago

Ok maybe it's a misunderstanding from me :).

So, how can I access to the [User] from 95vl7 LinkedResourceCollection friends property ?

wvteijlingen commented 8 years ago

seller.friends?[0] should give you the first user, seller.friends?[1] the second, etc.

RomainSauvaire commented 8 years ago

Many thanks to you @wvteijlingen for your awesome support ;)

alak commented 8 years ago

I'll make sure all is working as expected and close this issue later today. Thank you @wvteijlingen

alak commented 8 years ago

All working as expected. Thanks a lot to @wvteijlingen