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
264 stars 109 forks source link

How to use ToOneRelationship() and serializeAs() on the same attribute? #175

Open gaetanm opened 7 years ago

gaetanm commented 7 years ago

You will understand just by looking at my class:

import Spine

class Menu: Resource {
    var nbrOfRecipes: Int?
    var userID: User?
    var recipes: LinkedResourceCollection?

    override class var resourceType: ResourceType {
        return "menus"
    }

    override class var fields: [Field] {
        return fieldsFromDictionary([
            "nbrOfRecipes": Attribute().serializeAs("nb-recipes"),
            "userID": Attribute().serializeAs("user-id"),
            "userID": ToOneRelationship(User.self),
            "recipes": ToManyRelationship(Recipe.self)
            ])
    }
}

As you can guess, this result in a fatal error: Dictionary literal contains duplicate keys.

How to do to apply both ToOneRelationship(User.self) and Attribute().serializeAs("user-id") on the same attribute?

markst commented 7 years ago

@gaetanm why do you need to apply both attributes on the same property? why not have two properties one for the user relationship & the other for user-id?

class Menu: Resource {
    var nbrOfRecipes: Int?
    var userID: String?
    var user: User?
markst commented 7 years ago

Perhaps you could provide an example of your response JSON to make it clear?

gaetanm commented 7 years ago

I don't know enough about how Spine works internally to know that it was possible to use 2 properties for the same JSON data. Interesting!

About the JSON response, it's like this:

"data": {
    "id": "4",
    "type": "menus",
    "attributes": {
      "nb-recipes": 4,
      "created-at": "2016-11-27 19:50:07",
      "user-id": 1
    }

By using 2 properties like you showed me, I now have an assertion failure:

Resource of type menus does not contain a field named user-id

My query is:

var query = Query(resourceType: Menu.self)
query.whereAttribute("user-id", equalTo: userID!)

My class is now:

class Menu: Resource {
    var nbrOfRecipes: Int?
    var user: User?
    var userID: String?
    var recipes: LinkedResourceCollection?

    override class var resourceType: ResourceType {
        return "menus"
    }

    override class var fields: [Field] {
        return fieldsFromDictionary([
            "nbrOfRecipes": Attribute().serializeAs("nb-recipes"),
            "userID": Attribute().serializeAs("user-id"),
            "user": ToOneRelationship(User.self),
            "recipes": ToManyRelationship(Recipe.self)
            ])
    }
}

It's really a black box for me. Do I have this error because of those 2 properties?

markst commented 7 years ago

to resolve the error:

Resource of type menus does not contain a field named user-id You'll need to pass the class property name into the query:

query.whereAttribute("userID", equalTo: userID!)

Spine will perform the appropriate transform/formatting from property name to field name as described in serializeAs().

markst commented 7 years ago

@gaetanm please see examples on http://jsonapi.org/ for how relationships responses should look. I think your JSON should look more like this:

"data": [{
    "id": "4",
    "type": "menus",
    "attributes": {
      "nb-recipes": 4,
      "created-at": "2016-11-27 19:50:07",
    },
    "relationships": {
      "user": {
        "data": { "type": "users", "id": "1" }
      },
    }
  }],
  "included": [{
    "type": "users",
    "id": "1",
    "attributes": {
      "first-name": "Dan",
      "last-name": "Gebhardt",
    },

Which includes the relationships properties & also the associated included object

Without needing the additional user-id attribute, you can access this data through the Resource relationship data: https://github.com/wvteijlingen/Spine/blob/master/Spine/Resource.swift#L104 Whether your associated User has been included or not.

gaetanm commented 7 years ago

Thanks, it's more clear now.

I have a last error message:

'[<DoEat.Menu 0x6100002e4b80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nbrOfRecipes.'

No clue about why I have that error. I already had a similar error message in the past but it was caused by the storyboard, which seems to not be the case here.

markst commented 7 years ago

@gaetanm nbrOfRecipes must be a NSNumber. see documentation for using primitive types