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

Including attributes into relationship object #181

Open mareapp opened 7 years ago

mareapp commented 7 years ago

I am trying to implement save(POST) to my back-end, here is my request body:

  "data": {
    "type": "account",
    "relationships": {
      "account_type": {"data": {"type": "account_type", "id": "1"} },
      "user": {
        "data": {
          "type": "user",
          "attributes": {
            "email": "john@doe.com",
            "firstname": "John",
            "lastname": "Doe",
            "company_name": "test company",
            "phone": "381 11 232 342",
            "address": " Venizelosova 43, 11000 Beograd, Serbia",
            "latitude": 44.8112,
            "longitude": "20.5342",
            "description": "some description",
            "website_url": "ar-ty.com",
            "password": "password123",
            "password_confirmation": "password123"
          },
          "relationships": { "role": { "data": {"type": "role", "id": "3"} } }
        }
      }
    }
  }
}

Does your lib support including attributes into relationship object, like in this json where relationship object has user with attributes. Please could you give me code example how to do this if possible? Thanks in advance

markst commented 7 years ago

Hey @mareapp I'm not sure if this is valid JSONAPI, however I'm currently achieving the same example by creating my own Relationship field type, here's an example:

public struct ToOneLinkage<T: Resource> : Relationship {

    public typealias Linked = T

    public var name: String
    public var serializedName: String
    public var isReadOnly: Bool = false

    private var shouldSerializeAttributes: Bool = false

    public func serializeAttributes() -> ToOneLinkage {
        var newField = self
        newField.shouldSerializeAttributes = true
        return newField
    }

    public init(_ name: String, to linkedType: T.Type) {
        self.name = name
        self.serializedName = name
    }

    public func serialize(from resource: Resource,
                          into serializedData: inout [String: Any],
                          withKeyFormatter keyFormatter: KeyFormatter,
                          withValueFormatters valueFormatters: ValueFormatterRegistry,
                          withOptions options: SerializationOptions) {
        let key = keyFormatter.format(self)

        if options.contains(.IncludeToOne) {
            let linkedResource: Linked?

            let fieldValue = resource.value(forField: self.name)
            if let linkingObjects = fieldValue as? RLMLinkingObjects {
                if linkingObjects.count > 1 {
                    fatalError("Should only use `ToOneLinkage` for to one linkage...")
                }
                linkedResource = linkingObjects.firstObject() as? Linked
            } else {
                linkedResource = fieldValue as? Linked
            }

            var serializedResourceData: [String: Any] = [:]

            // Serialize type
            serializedResourceData["type"] = Linked.resourceType

            // Serialize ID
            if let resourceId = linkedResource?.id {
                serializedResourceData["id"] = resourceId
            }

            // Serialize Attributes
            if self.shouldSerializeAttributes, let fields = linkedResource?.fields {
                for field in fields where !field.isReadOnly && field is Attribute {
                    field.serialize(from: linkedResource!,
                                    into: &serializedResourceData,
                                    withKeyFormatter: keyFormatter,
                                    withValueFormatters: valueFormatters,
                                    withOptions: options)
                }
            }

            let serializedRelationship = [ "data": linkedResource != nil ? serializedResourceData : NSNull() as Any]

            if serializedData["relationships"] == nil {
                serializedData["relationships"] = [key: serializedRelationship]
            } else {
                var relationships = serializedData["relationships"] as! [String: Any]
                relationships[key] = serializedRelationship
                serializedData["relationships"] = relationships
            }
        }
    }

Which can be used in place of your user relationship instead of a ToOneRelationship

mareapp commented 7 years ago

Thanks for answer, yeah its true my back end has not followed standard about JSON API but only when need POST operation, so I dont have any problem with GET but when I need to send this kind of request body it is problem , by the way I tried something like your example but didnt work for me

markst commented 7 years ago

@mareapp ah sorry, my example won't work unless you're using branch here: https://github.com/wvteijlingen/Spine/pull/154

markst commented 7 years ago

Not sure if the above can be archived without modifying or writing your own SerializeOperation https://github.com/wvteijlingen/Spine/blob/13c650d66d59f7404905ba6df765a5e5174dbf26/Spine/SerializeOperation.swift#L169