Swift Realm - Creating child Realm objects and linking them to their parent

1.6k views Asked by At

I am currently learning Realm and am converting my experimental app/game which uses arrays to Realm;

It loads pre-seeding data via a local JSON file and ObjectMapper; then creates objects in realm; this part seems to work.

// Parse response
let json = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as!  Array<Dictionary<String, AnyObject>>

let factories = Mapper<Factory>().mapArray(JSONArray: json)!
do {
    let realm = try Realm()
    try realm.write {
        for factory in factories
        {
            realm.add(factory, update: true)
        }
    }
} catch let error as NSError {
    print(error.localizedDescription as Any)
}

The issue I'm having is that when it maps; I'd like it to create its child objects at the same time and link them to parent.

Each parent (Factory) has about between 4 children (Engine) linked to it.

// Factory is parent object
class Factory: Object, Mappable {
    dynamic var name: String = ""
    let engines = List<Engine>()

    //Impl. of Mappable protocol
    required convenience init?(map: Map) {
        self.init()
    }

    // Mappable
    func mapping(map: Map) {
        name    <- map["name"]
    }
}


// Engine is a child to Factory
class Engine: Object {
    dynamic var production: Int = 0

   // create children and add to the parent factory
    static func createEngines(parent:Factory) -> [Engines]
    {
      var engines:[Engine] = [Engine]()
      for _ in stride(from:0, to: 3, by: 1) {
          //let engine : Engine = Engine.init(parent: element)
          //engines.append(engine)
      }
      return engines
    }
}

If I attempt to put this in my mappable

engines = Engine.createEngines(parent: self)

and make a change in my Factory model;

`var engines = List<Engine>()`

I get this error:

Cannot assign value of type '[Engine]?' to type 'List<Engine>'

The problem here is that simply creating an array of engines (children), appending it to an array doesn't seem to work with Realm and I'm not sure how to do this.

Hence, my question is how do I bulk create children, assign it to a given parent and add it to the current realm write/save?

Many thanks.

2

There are 2 answers

0
zardon On BEST ANSWER

I changed my code to do this;

  1. Read all the factories from JSON
  2. Loop through the factories, creating engines
  3. Link the parent object up.

I'm not sure if I did it right but it seems to be working.

I just don't like how I'm having to hardwire the parent; as I thought Realm/ObjectMapper could do that for me. But its not a major issue as there is only about 3 or 4 relationships.

let factories = Mapper<Factory>().mapArray(JSONArray: json)!

do {
    let realm = try Realm()
    try realm.write {

        for f in factories
        {
            realm.add(f, update: true)
        }

        let factories = realm.objects(Factory.self)
        print (factories.count) // for debug purposes
        for f in factories {
            for _ in stride(from: 0, to: f.qty, by: 1) {
                let engine : Engine = Engine.init()
                engine.parent = f 
                f.engines.append(engine)
            }
        }
    }
} catch let error as NSError {
    print(error.localizedDescription as Any)
}

This above code seems to do the work for me; although I do wish I didn't have to manually set the parent (engine.parent = f)

Anyhow, I've accepted @BogdanFarca's answer.

4
Bogdan Farca On

There is a very nice solution by Jerrot here on Githib Gist

The mapping should be defined in your main model object like this:

func mapping(map: Map) {
    title     <- map["title"]
    products  <- (map["products"], ArrayTransform<ProductModel>())
}

The real magic is happening in the ArrayTransform class:

func transformFromJSON(value: AnyObject?) -> List<T>? {
    var result = List<T>()
    if let tempArr = value as! Array<AnyObject>? {
        for entry in tempArr {
            let mapper = Mapper<T>()
            let model : T = mapper.map(entry)!
            result.append(model)
        }
    }
    return result
}