1

Based on the following code I would like to be able to create a new ItemList from an existing one. In other words I have an ItemList called First List and I want to create a new ItemList, call it Second List and fill it with the Items from First List.

The way I have it right now is that it creates the Second List as expected, the Items from the First List show in Second List but what doesn't work is when I want to delete only the Items from First List, it deletes Items from both lists. I guess I'm not truly copying the items.

So the question is, how can I copy Items from First List to Second List?

Object Models:

class ItemList: Object {
    dynamic var listName = ""
    dynamic var createdAt = NSDate()
    let items = List<Item>()
}

class Item:Object{
    dynamic var productName:String = ""
    dynamic var createdAt = NSDate()
}

Code to create Second List from First List

This Works fine, it creates Second List and adds the items from First List but I don't think I'm making copies just showing them in Second List.

        let newList = ItemList()
        newList.listName = "Second List"

        if let selectedList = realm.objects(ItemList.self).filter("listName = %@", "First List").first{
            let itemsFromFirstList = selectedList.items
            newList.items.append(objectsIn:itemsFromFirstList)
        }

        try! realm.write {
            realm.add(newList)
        }

This code is supposed to delete only the items from First List

This actually deletes items from both First List and Second List

    let listToDelete = realm.objects(ItemList.self).filter("listName = %@", "First List").first

    try! realm.write {
        for item in (listToDelete?.items)! {
            realm.delete(realm.objects(Item.self).filter("productName = %@", item.productName).first!)

        }
    }
fs_tigre
  • 10,650
  • 13
  • 73
  • 146

3 Answers3

2

What you want to do is use:

for record in postsDB.objects(PostModel.self) {
    if !combinedDB.objects(PostModel.self).filter("postId == \(record.parentId)").isEmpty {
          combinedDB.create(PostModel.self, value: record, update: false)
    }
}

The create method is inherited from Object. It tells the target to create a new object. Use true if you want it to look to see if there is already a record there, and update it if there is. PostModel is the Object type, record is what you want copied.

Edit: I added the if statement to provide more context. You didn't show your class definitions, so I was guessing. This is a working example. I ask for a set of records from DatabaseA and copy it to DatabaseB (postsDB to combinedDB).

So if the type of the object you're trying to insert is a List, I'd recommend you define a subclass of Object, and have at least the list you need as a property.

class TagList: Object {
    dynamic var tag = ""
    var list = List<PostModel>()

    override class func primaryKey() -> String? {
        return "tag"
    }
}

Full working example illustrating: creating new objects, copying all objects to a second list, deleting from second list after copying, adding to first list (which didn't get anything deleted from it.

import Foundation
import RealmSwift

class Letter: Object {
    dynamic var letter = "a"
}

class Letters: Object {
    var letterList = List<Letter>()
}

class ListExample {
    let listRealmStore = try! Realm() // swiftlint:disable:this force_try

    func testThis() {
        print(Realm.Configuration.defaultConfiguration.fileURL!)
        listRealmStore.beginWrite()
        addSingleItems()  // add 3 objects to the DB

        let firstList = Letters()
        let allObjects = listRealmStore.objects(Letter.self)
        for item in allObjects {
            firstList.letterList.append(item)
        }

        let secondList = Letters()
        let itemsToCopy = firstList.letterList
        for item in itemsToCopy {
            let obj = listRealmStore.create(Letter.self)
            obj.letter = item.letter
            secondList.letterList.append(obj)
        }

        let third = Letter()
        third.letter = "Z"
        listRealmStore.add(third)

        firstList.letterList.append(third)
        secondList.letterList.removeLast()
        do {
            try listRealmStore.commitWrite()
        } catch let error {
            print("couldn't commit db writes: \(error.localizedDescription)")
        }

        print("list one:\n\(firstList)")
        print("list two:\n\(secondList)")
    }

    func addSingleItems() {

        for letter in ["a", "b", "c"] {
            let objectToInsert = Letter()
            objectToInsert.letter = letter
            listRealmStore.add(objectToInsert)
        }
    }
}

Results in:

list one:
Letters {
    letterList = List<Letter> (
        [0] Letter {
            letter = a;
        },
        [1] Letter {
            letter = b;
        },
        [2] Letter {
            letter = c;
        },
        [3] Letter {
            letter = Z;
        }
    );
}
list two:
Letters {
    letterList = List<Letter> (
        [0] Letter {
            letter = a;
        },
        [1] Letter {
            letter = b;
        }
    );
}
Mozahler
  • 4,958
  • 6
  • 36
  • 56
  • I'm not sure if I'm doing it right, I tried `newList.items.create(ItemList.self, value: itemsFromDefaultList, update: false)` And I got error `Value of type 'List' has no member 'create'` Thanks. – fs_tigre May 02 '17 at 02:02
  • I thought your newList was a subclass of Object, not a list. In that case, I'd recommend putting the list inside another subclass of object. I updated the example. – Mozahler May 02 '17 at 02:16
  • @ Mozahler - Sorry but I'm a little confused about your suggestion. To clarify any misunderstandings, all I want is to be able to create a new `ItemList` from an existing one. In other words I have an `ItemList` called `First List` and I want to create a new `ItemList`, call it `Second List` and fill it with the `Item`s from `First List`. I'm using `First List` as the default list to create new ones so, eventually First and Second List are going to be different. – fs_tigre May 02 '17 at 12:47
  • OK. I'm writing up a full working example. I'll be back shortly. – Mozahler May 02 '17 at 13:27
  • 1
    Check out the bottom half (I kept the original post intact, but added a fully working solution). It creates new objects, copies all objects from first to a second list, deletes last item from second list after copying, adds new object to first list (which didn't get anything deleted from it). – Mozahler May 02 '17 at 16:03
  • Wow! Thanks a lot for your help! – fs_tigre May 02 '17 at 17:28
  • Glad to help. You contribute a lot to this site. – Mozahler May 02 '17 at 20:09
1

Are you really trying to create copies of your items, or do you just want to be able to remove them from lists independently?

When you do:

newList.items.append(objectsIn: itemsFromFirstList)

you end up with the same objects being in both lists. List<T> just stores references to objects that live within the Realm. Appending an object to a List just references the existing object, it doesn't copy the object.

When you call Realm.delete(_:) you remove that object entirely from the Realm, not just from a single list that it is a member of. To remove an object from a List, you should instead use List.remove(objectAtIndex:).

bdash
  • 18,110
  • 1
  • 59
  • 91
  • @ bdash - Thanks a lot for the good information. I think I want to copy them since the `Item`s in each `ItemList` need to be independent from each other, I know I may end up with the same `Item` in multiple `ItemList`s but that's ok. – fs_tigre May 02 '17 at 12:37
0

One part the solution you are looking for could be like this, make copy objects in the list, or you can just use this idea to clone whole list it self:

Previously answered here

As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.

Here is the one I have been using, and one with less limitations in my opinion.

  1. Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
    @objc dynamic var breed:String = "JustAnyDog"
}
  1. Create this helper class
class RealmHelper {
    //Used to expose generic 
    static func DetachedCopy<T:Codable>(of object:T) -> T?{
       do{
           let json = try JSONEncoder().encode(object)
           return try JSONDecoder().decode(T.self, from: json)
       }
       catch let error{
           print(error)
           return nil
       }
    }
}
  1. Call this method whenever you need detached / true deep copy of your Realm Object, like this:
 //Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
 guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
    print("Could not detach Note")
    return
 }
//Change/mutate object properties as you want
 detachedDog.breed = "rottweiler"

As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.

Though its NOT an ideal solution, but its one of the most effective workaround.

ImShrey
  • 380
  • 4
  • 12