How can you create Results after creating records? - swift

I have a method that should return Results, either by successfully querying, or by creating the records if they don't exist.
Something like:
class MyObject: Object {
dynamic var token = ""
static let realm = try! Realm()
class func findOrCreate(token token: String) -> Results<MyObject> {
// either it's found ...
let tokenResults = realm.objects(MyObject.self).filter("token = '\(token)'")
if !tokenResults.isEmpty {
return tokenResults
}
// ... or it's created
let newObject = MyObject()
newObject.token = token
try! realm.write {
realm.add(newObject)
}
// However, the next line results in the following error:
// 'Results<_>' cannot be constructed because it has no accessible initializers
return Results(newObject)
}
}
Maybe I should just be returning [MyObject] from this method. Is there any benefit to trying to keep it as Results instead of Array? I guess I'd lose any benefit of postponed evaluation since I'm already using isEmpty within the method, correct?

Results is an auto-updating view into underlying data in a Realm, which is why you can't construct it directly. So instead of return Results(newObject), you should return tokenResults, which will contain your newly added object, again because Results is an auto-updating view.

Related

Realm and Swift - parameters to pass for the model to be updated

In the beginning of the project, Realm is great and easy to work and the project is getting complicated so I need to figure out how to decouple the realm layer and uiviewcontroller.
There is some awkwardness by writing a realm object with parameters. I would like to have object updated with the parameter then pass to the realm database to update object in (table?). Initially, I have a function to write a realm object by -
func createOrUpdateNote(note : Note, body : String, textSize : Float, toggleColor : Int) {
let realm = try! Realm()
do {
try realm.write {
if note.id == -1 {
note.id = NoteManager.createNotePrimaryId()
}
note.body = body
note.textSize = textSize
note.toggleColor = toggleColor
realm.add(note, update: true)
}
}
catch {
print(error.localizedDescription)
}
}
I would like to have like this function. Hope it clears up my question here.
func createOrUpdateNote(note : Note) {
let realm = try! Realm()
do {
try realm.write {
realm.add(note, update: true)
}
}
catch {
print(error.localizedDescription)
}
}
Now I have another viewcontroller to update the object with their preference of using language below.
func createOrUpdateNote(note : Note, language : String) {
let realm = try! Realm()
do {
try realm.write {
if note.id == -1 {
note.id = NoteManager.createNotePrimaryId()
}
note.language = language
realm.add(note, update: true)
}
}
catch {
print(error.localizedDescription)
}
}
There will be two similar functions (dup functions) in database layer and that will not work well when the project is getting more features so I'm stepping back to see if I can redesign the create or update the object approach.
I googled and there are several solutions like making UI object and copy the ui values over to the realm objects each time I do CRUD, create object with internal realm object (1 to 1 mapping), or I'm thinking about partial update but not sure how can I approach this situation. Ideally, I would prefer only object to carry over to be updated. Any suggestions?
If you have an existing (managed) Realm object, its properties can only be modified within a write block. However, ANY of it's properties can be modified and you really only need one block 'style' to do it.
So for example, if we have a Note Realm object
Note: Object {
#objc dynamic var body = ""
#objc dynamic var textSize = 12
#objc dynamic var language = "English"
}
any time we have access to that Note, we can modify it's properties within a write closure. Let's say a user is editing an existing note and changes the body, then clicks Save.
let realm = try Realm()
try! realm.write {
myNote.body = updatedBodyText
realm.add(myNote, update: true)
}
or they change the text size
let realm = try Realm()
try! realm.write {
myNote.textSize = updatedTextSize
realm.add(myNote, update: true)
}
Notice that those blocks are identical, other than which property is updated. The key is to hang on to a reference to the note when loaded, so you can then modify it's properties in a write block when saving.
There's no problem having multiple write blocks depending on what property you're saving. It really depends on your use case but that's common practice.
Generically speaking it could also be rolled into one function, something like this:
func saveMyNote(myNote: NoteClass, updatedData: String, fieldType: NoteFieldTypes) {
try! realm.write {
switch fieldType:
case .body:
myNote.body = updatedData
case .language:
myNote.language = updatedData
etc etc
realm.add(myNote, update: true)
}
You could also extend the class or a variety of other solutions. In general the write code is so small I would just use it wherever you need to update an objects fields.

Swift - Get the parent of a Realm object; Always empty

I've got a relationship where:
A Parent has many Children
ie:
class Factory: Object {
public let engines = List<Engine>()
}
class Engine:Object {
private let parents:LinkingObjects<Factory> = LinkingObjects(fromType: Factory.self, property: "engines")
var parent:Factory? {
return self.parents.first
}
}
I read the factories via JSON and create the children (Engine) manually in a for-loop, similar to this:
var engines:[Engine] = [Engine]()
for _ in stride(from:0, to: 3, by: 1) {
let engine: Engine = Engine.init()
engines.append(engine)
}
return engines
In my test I want to query the parent of a given engine to ensure that the parent is correct; or perhaps get a parent attribute.
However, whenever I try to grab an attribute via the parent its always empty;
for (_, element) in (factories.enumerated()) {
for (_, eng) in element.engines.enumerated() {
print (eng.parent ?? "N/A" as Any) // Always prints out N/A
}
}
Ideally I want to be able to access the parent's data; like the name of the parent, perhaps costs, etc.
I've tried resetting simulator and also deleting derived data; but regardless of what I do the results are always N/A or empty.
How can I query the given element and ensure that I can grab the parent data?
Many thanks
Turns out there were a number of issues that I had to do to resolve this.
I was using XCTest and Realm was causing issues where there were multiple targets.
Make all my model classes' public
Remove the models from the test target, this included a file where the JSON data was being loaded into memory
I had to write my data into Realm, which I had not done;
let realm = try! Realm()
try! realm.write {
for parent:EYLocomotive in objects {
for _ in stride(from:0, to: parent.qty, by: 1) {
let engine : EYEngine = EYEngine.init()
parent.engines.append(engine)
}
realm.add(parent)
}
}

Swift Realm getting stuck on re-adding an Object in a write block

I'm using Realm, the project is on version 1.0.0.
When I create a list of Realm Objects (with data obtained from a web API), then try to save them to the Realm using this utility function in a struct:
static func saveRealmObjects(objects: [Object]) {
defer {
// Never entered
}
for object in objects {
let realm = try! Realm()
do {
try realm.write {
print("TEST: 1: object: \(object)")
realm.add(object)
print("TEST: 2")
}
} catch {
// Never entered
}
}
}
(Please don't judge me on the exact structure, I've been toying around seeing if anything will work).
I can tell from liberal use of print statements (mostly removed above) that the function gets to TEST: 1 okay, but fails to make it to TEST: 2, for the very first Object in the list I pass to the function.
I should note this function does work the first time I use it with the data (say after wiping the simulator and launching the app afresh), but then if I recreate the Objects and try to save them again it gets stuck.
I assumed Realm would use the private key on the Objects and overwrite any if necessary. But it seems to just get stuck.
-
Then - after it's stuck - if I try and get another set of results from Realm (using a different Realm object) I get the following error:
libc++abi.dylib: terminating with uncaught exception of type realm::InvalidTransactionException: Cannot create asynchronous query while in a write transaction
FYI I'm creating a different Realm object using try! Realm()
-
For reference, here is the Object I'm trying to save:
import Foundation
import RealmSwift
class MyObject: Object {
// MARK: Realm Primary Key
dynamic var id: String = ""
override static func primaryKey() -> String? {
return "id"
}
// MARK: Stored Properties
dynamic var date: NSDate? = nil
dynamic var numA = 0
dynamic var numB = 0
dynamic var numC = 0
dynamic var numD = 0
dynamic var numE = 0
dynamic var numF = 0
dynamic var numG = 0
dynamic var numH = 0
// MARK: Computed Properties
var computedNumI: Int {
return numD + numE
}
var computedNumJ: Int {
return numF + numG
}
}
(The variable names have been changed.)
-
Hopefully I'm doing something obviously wrong - this is my first time using Realm after all.
If you have any ideas why it's sticking (perhaps it's a threading issue?), or want more info, please answer or comment. Thank you.
Being the clever clogs I am, I've literally just found the answer by reading the documentation:
https://realm.io/docs/swift/latest/#creating-and-updating-objects-with-primary-keys
The add to Realm line needed to look like this:
realm.add(object, update: true)
Where the update flag will update Objects already saved with that primary key.
-
Although it would have been nice if it either gave some sort of obvious warning or crash upon trying to add the same object, or didn't cause other queries and writes to Realm to crash.

Replaced List<T> object not persisting consistently in Realm

I have a List<Workout> object that occasionally needs to be sorted (e.g., if a user adds a Workout out of order), but I can't seem to get the new sorted List<Workout> to persist. My code works the moment it runs (i.e., it shows up on the view as sorted), but when I exit the ViewController or restart the app, I see nothing. The nothing is due to the exercise.workoutDiary.removeAll() persisting, but apparently the subsequent assignment to the exercise.workoutDiary = sortedWorkoutDiary is not persisting. Any ideas why?
Everything else works just fine. The typical recordWorkout() case works assuming nothing is entered out of order. So the persisting is working in nearly all cases except for this overwrite of the sorted List.
The update happens here:
struct ExerciseDetailViewModel {
private let exercise: Exercise!
func recordWorkout(newWorkout: Workout) {
let lastWorkout = exercise.workoutDiary.last // grab the last workout for later comparison
let realm = try! Realm()
try! realm.write {
exercise.workoutDiary.append(newWorkout) // write the workout no matter what
}
if let secondToLastWorkout = lastWorkout { // only bother checking out of order if there is a last workout...
if newWorkout.date < secondToLastWorkout.date { // ...and now look to see if they are out of order
let sortedWorkoutDiary = exercise.sortedWorkouts
try! realm.write {
exercise.workoutDiary.removeAll()
exercise.workoutDiary = sortedWorkoutDiary
}
}
}
}
}
final class Exercise: Object {
var workoutDiary = List<Workout>()
var sortedWorkouts: List<Workout> {
return List(workoutDiary.sorted("date"))
}
}
final class Workout: Object {
dynamic var date = NSDate()
var sets = List<WorkSet>()
}
List<T> properties in Realm Swift must be mutated in place, not assigned to. The Swift runtime does not provide any way for Realm to intercept assignments to properties of generic types. Instead, you should use methods like appendContentsOf(_:) to mutate the List<T>:
exercise.workoutDiary.removeAll()
exercise.workoutDiary.appendContentsOf(sortedWorkoutDiary)
This limitation on assignment to properties of generic types is why the Realm Swift documentation recommends that you declare such properties using let rather than var. This will allow the Swift compiler to catch these sorts of mistakes.
One further note: for your sortedWorkouts computed property, it'd be preferable for it to return Results<Workout> instead to avoid allocating and populating an intermediate List<Workout>.

How can I easily duplicate/copy an existing realm object

I have a Realm Object which has several relationships, anyone has a good code snippet that generalizes a copy method, to create a duplicate in the database.
In my case i just wanted to create an object and not persist it. so segiddins's solution didn't work for me.
Swift 3
To create a clone of user object in swift just use
let newUser = User(value: oldUser);
The new user object is not persisted.
You can use the following to create a shallow copy of your object, as long as it does not have a primary key:
realm.create(ObjectType.self, withValue: existingObject)
As of now, Dec 2020, there is no proper solution for this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
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
}
}
}
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 Dog")
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.
I had a similar issue and found a simple workaround to get a copy of a realm object. Basically you just need to make the object conform to the NSCopying protocol, something like:
import RealmSwift
import Realm
import ObjectMapper
class Original: Object, NSCopying{
dynamic var originalId = 0
dynamic var firstName = ""
dynamic var lastName = ""
override static func primaryKey() -> String? {
return "originalId"
}
init(originalId: Int, firstName: String, lastName: String){
super.init()
self.originalId = originalId
self.firstName = firstName
self.lastName = lastName
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Original(originalId: originalId, firstName: firstName, lastName: lastName)
return copy
}
}
then you just call the "copy()" method on the object:
class ViewController: UIViewController {
var original = Original()
override func viewDidLoad() {
super.viewDidLoad()
var myCopy = original.copy()
}
}
The nice thing about having a copy is that I can modify it without having to be in a realm write transaction. Useful when users are editing some data but didn't hit save yet or simply changed their mind.
Since this problem is still alive I post my solution which works but still needs to be improved.
I've created an extension of Object class that has this method duplicate that takes an object objOut and fills the flat properties by looking at self. When a non-flat property is found (aka a nested object) that one is skipped.
// Duplicate object with its flat properties
func duplicate(objOut: Object) -> Object {
// Mirror object type
let objectType: Mirror = Mirror(reflecting: self);
// Iterate on object properties
for child in objectType.children {
// Get label
let label = child.label!
// Handler for flat properties, skip complex objects
switch String(describing: type(of: child.value)) {
case "Double", "Int", "Int64", "String":
objOut.setValue(self.value(forKey: label)!, forKey: label)
break
default:
break
}
}
return objOut
}
Inside the Manager class for my Realms I have the method copyFromRealm() that I use to create my copies of objects.
To give you a practical example this is the structure of my Appointment class:
Appointment object
- flat properties
- one UpdateInfo object
- flat properties
- one AddressLocation object
- flat properties
- one Address object
- flat properties
- one Coordinates object
- flat properies
- a list of ExtraInfo
- each ExtraInfo object
- flat properties
This is how I've implemented the copyFromRealm() method:
// Creates copy out of realm
func copyFromRealm() -> Appointment {
// Duplicate base object properties
let cpAppointment = self.duplicate(objOut: Appointment()) as! Appointment
// Duplicate UIU object
cpAppointment.uiu = self.uiu?.duplicate(objOut: UpdateInfo()) as? UpdateInfo
// Duplicate AddressLocation object
let cpAddress = self.addressLocation?.address?.duplicate(objOut: Address()) as? Address
let cpCoordinates = self.addressLocation?.coordinates?.duplicate(objOut: Coordinates()) as? Coordinates
cpAppointment.addressLocation = self.addressLocation?.duplicate(objOut: AddressLocation()) as? AddressLocation
cpAppointment.addressLocation?.address = cpAddress
cpAppointment.addressLocation?.coordinates = cpCoordinates
// Duplicate each ExtraInfo
for other in self.others {
cpAppointment.others.append(other.duplicate(objOut: ExtraInfo()) as! ExtraInfo)
}
return cpAppointment
}
I wasn't able to find out a good and reasonable way to work with nested objects inside my duplicate() method. I thought of recursion but code complexity raised too much.
This is not optimal but works, if I'll find a way to manage also nested object I'll update this answer.
Swift 5+
Creates a Realm managed copy of an existing Realm managed object with ID
extension RLMObject {
func createManagedCopy(withID newID: String) -> RLMObject? {
let realmClass = type(of: self)
guard let realm = self.realm, let primaryKey = realmClass.primaryKey() else {
return nil
}
let shallowCopy = realmClass.init(value: self)
shallowCopy.setValue(newID, forKey: primaryKey)
do {
realm.beginWriteTransaction()
realm.add(shallowCopy)
try realm.commitWriteTransaction()
} catch {
return nil
}
return shallowCopy
}
}