Alserda Alserda - 1 month ago 9
Swift Question

Realm, Swift, Many-to-many relationship

On my API I've got a relationship between Stands and Products. In which stands have products, but products can be found in different stands aswell. I'm trying to replicate this relationship on my iOS-application, using Realm but I can't seem to get it working.

The goal of having this relationship is being able to search for Stands that sell particular products.

My model:

class Stand: Object {
dynamic var id : Int = 0
dynamic var name : String = ""
dynamic var latitude : Double = 0.0
dynamic var longitude : Double = 0.0
let products = List<Product>()

override static func primaryKey() -> String? {
return "id"
}
}

class Product: Object {
dynamic var id : Int = 0
dynamic var name : String = ""
let stands = List<Stand>()

override static func primaryKey() -> String? {
return "id"
}
}


When doing my API request for Stands I retrieve the associated Products with it aswell. When I append these to the Stands it works fine for my Stands model as the products are just normally added in the List().

But all of the products are individually created, without having any Stands attached to them.

Is there a way to directly assign these Stands to the Products upon creation of the Products? Just like it's happening the other way around?

My current solution is this..

func retrieveAndCacheStands(clearDatabase clearDatabase: Bool?) {
backend.retrievePath(endpoint.StandsIndex, completion: { (response) -> () in
let listOfProducts : List<(Product)> = List<(Product)>()

func addProducts(stand: Stand, products: List<(Product)>?) {
for product in products! {
print(product.name)
let newProduct = Product()
newProduct.id = product.id
newProduct.name = product.name
newProduct.stands.append(stand)

try! self.realm.write({ () -> Void in
self.realm.create(Product.self, value: newProduct, update: true)
})
}

listOfProducts.removeAll()
}

for (_, value) in response {
let stand = Stand()
stand.id = value["id"].intValue
stand.name = value["name"].string!
stand.latitude = value["latitude"].double!
stand.longitude = value["longitude"].double!
for (_, products) in value["products"] {
let product = Product()
product.id = products["id"].intValue
product.name = products["name"].string!
stand.products.append(product)
listOfProducts.append(product)
}

try! self.realm.write({ () -> Void in
self.realm.create(Stand.self, value: stand, update: true)
})

addProducts(stand, products: listOfProducts)
}

print(Realm.Configuration.defaultConfiguration.path!)

}) { (error) -> () in
print(error)
}
}


This stores the Stands and adds Products to them. It also creates all the products and adds 1 Stand per 10 ish Products (?).

I can't seem to figure out how to make this work. Does anyone else know how to solve this? Or a better solution?

Answer

Instead of doing the double-bookkeeping necessary to maintain inverse relationships manually, you should use Realm's inverse relationships mechanism, which provides you with all of the objects pointing to another object using a given property:

class Product: Object {
    dynamic var id: Int = 0
    dynamic var name: String = ""
    // Realm doesn't persist this property because it is of type `LinkingObjects`
    // Define "stands" as the inverse relationship to Stand.products
    let stands = LinkingObjects(fromType: Stand.self, property: "products")

    override static func primaryKey() -> String? {
        return "id"
    }
}

See Realm's docs on Inverse Relationships for more information.