I have two Realm model classes
class ModelA: Object {
let id = RealmOptional<Int>()
dynamic var name: String!
// some other variables that are also String! type
}
class ModelB: Object {
let id = RealmOptional<Int>()
let models = List<ModelA>()
// other variables
}
And I have some JSON object which contains data for that models. I create ModelB instance then populate it with ModelA instances list the following way:
let json: JSON = ... // get it from somewhere, then use SwiftyJSON
let myModelB = ModelB()
myModelB.id.value = json["id"].object as? Int
// set other properties
let modelsA = json["models"].map { ModelA(value: $0.1.object) }
myModelB.models.appendContentsOf(modelsA)
The reason why I am using different approaches here is that propery names in JSON doesn't match to my property names for ModelB but for ModelA it is ok.
Somewhere later I use realm.add(objects, update: true) (inside realm.write) and that leads to the following exception:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key (null).'
According to the documentation:
If your model class includes a primary key, you can have Realm intelligently update or add objects based off of their primary key values using Realm().add(_:update:).
so both ModelA and ModelB has primaryKey() function and I believe that should work but it is not.
Furthermore, I removed update parameter in the call and added call to the realm.deleteAll() before adding new objects (both in write callback). In that case I get the following exception:
Terminating app due to uncaught exception 'RLMException', reason: 'Can't set primary key property 'id' to existing value 'xxxxxxx'.
Additionally if I try to go through the call stack, Xcode crashes. It also crashes if I try to inspect any Realm object in the debugger. I already have installed Realm Xcode plugin but nothing changes. I cannot understand what is going wrong here and why I get so strange behavior. Could someone tell me where is my mistake please?
I start project from scratch, base on example project. So I finished with:
import UIKit
import RealmSwift
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var owner: Person? // Properties can be optional
override class func primaryKey() -> String? { return "name" }
}
// Person model
class Person: Object {
dynamic var name = ""
let dogs = List<Dog>()
override class func primaryKey() -> String? { return "name" }
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
do {
try NSFileManager.defaultManager().removeItemAtPath(Realm.Configuration.defaultConfiguration.path!)
} catch {}
let dogRexJSON: AnyObject = ["name": "Rex", "age" : 20]
let dogLuckyJSON: AnyObject = ["name": "Lucky", "age" : 25]
var somePerson = Person(value: ["name" : "Shurik", "dogs" : [dogRexJSON]])
// Realms are used to group data together
let realm = try! Realm() // Create realm pointing to default file
// Save your object
realm.beginWrite()
realm.add(somePerson)
try! realm.commitWrite()
somePerson = Person(value: ["name" : "Shurik", "dogs" : [dogRexJSON, dogLuckyJSON]])
try! realm.write { () -> Void in
realm.add([somePerson], update: true)
return
}
let val = realm.objectForPrimaryKey(Dog.self, key: "Lucky")
print(val!.name) // as expected log >> Lucky
return true
}
}
Seems all works fine.
Related
I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.
You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
I am trying to create compound primary key from two keys. Using lazy for compoundKey will raise an exception - either remove lazy or add to ignore property list
So when I try to add ignore property list I am getting following exception - Terminating app due to uncaught exception 'RLMException', reason: 'Primary key property 'compoundKey' does not exist on object 'Collection'
Removing lazy and setting the empty string will add empty key and hence single row which will treat all primary key value as empty.
This is my code
class Collection : Object {
#objc dynamic var count: Int = 0
#objc dynamic var nextURL: String?
#objc dynamic var previousURL: String?
func setCompoundNextURL(nextURL: String) {
self.nextURL = nextURL
compoundKey = compoundKeyValue()
}
func setCompoundTourPreviousURL(previousURL: String) {
self.previousURL = previousURL
compoundKey = compoundKeyValue()
}
public dynamic lazy var compoundKey: String = self.compoundKeyValue()
override static func primaryKey() -> String? {
return "compoundKey"
}
override static func ignoredProperties() -> [String] {
return ["compoundKey"]
}
func compoundKeyValue() -> String {
return "\(nextURL ?? "")\(previousURL ?? "")"
}
}
Please help. I am not able to figure where I went wrong.
You cannot tell Realm to use an ignored property as a primary key. An ignored property isn't persisted to the Realm. The primary key property must be persisted to the Realm. Additionally, the primary key property's value cannot be changed after the object is created. For this reason I'd suggest computing the value inside a convenience initializer and assigning it to the property at that time.
I am creating a project. I want if the userID already exist, it doesn't add the user. But somehow my code isn't working properly.
This is my Realm Model Object (User.swift):
import Foundation
import RealmSwift
class User: Object {
#objc dynamic var userID = Int()
#objc dynamic var username = ""
#objc dynamic var full_name = ""
#objc dynamic var myBool = Bool()
override static func primaryKey() -> String? {
return "userID"
}
}
And this is the button to add users:
#IBAction func add(_ sender: Any) {
let myUser = User()
let JSON_userID = Int(arc4random_uniform(5)) // This is temporary. I am going to get code from JSON, but using random for testing purpose.
if (myUser.userID != JSON_userID) {
myUser.userID = JSON_userID
myUser.username = "myUsername"
myUser.full_name = "My Name"
let realm = try! Realm()
try! realm.write {
realm.add(myUser)
}
}
else {
print("Already exist")
}
}
Sometimes it runs the code, but most of the times it crashes with error:
libc++abi.dylib: terminating with uncaught exception of type NSException.
As you defined a primary key in your User object, Realm can handle this automatically if you set the update parameter to true inside the write closure.
let realm = try! Realm()
try! realm.write {
realm.add(myUser, update: true)
}
If the update parameter is not set or false, Realm will throw an exception when you try to add an object with an existing primary key.
This makes the if / else condition useless. It can be removed.
If you need to know if the user already exists, you can request the Realm with the primary key value:
realm.object(ofType: User.self, forPrimaryKey: JSON_userID)
The result will be nil if the user does not exist.
I'm facing an issue where a Realm object has another Realm object as member which is always nil after adding to the database.
class MedPack: Object {
dynamic var uuid = NSUUID().UUIDString
dynamic var medicine: Medicine?
convenience init(medicine: Medicine) {
self.init()
self.medicine = medicine
}
override static func primaryKey() -> String? {
return "uuid"
}
}
The reference to the object Medicine is always nil after adding.
class Medicine: Object {
var uuid = NSUUID().UUIDString
var name: String?
override static func primaryKey() -> String? {
return "uuid"
}
}
Creation of object
let medPack = MedPack(medicine: med)
Adding to database
static let sharedInstance = DBHelper()
var realmDb: Realm!
private init() {
realmDb = try! Realm()
}
func store(object: Object) {
try! self.realmDb.write {
self.realmDb.add(object)
}
}
After comparing this code to one of the Realm sample projects, it would appear that simply setting an Object as a child of another does not implicitly write it to the database as well.
Instead, you may need to refactor your code slightly, but make sure you explicitly add your Medicine object to Realm in a write transaction, before you set its relation to MedPack and then write MedPack to the database.
I ran into some errors when I'm trying to compile my realm code, this is the first version of the code:
import RealmSwift
class Test: Object {
dynamic var name = ""
dynamic var dict = Dictionary<String, Int>()
dynamic var owner: TestList?
}
class TestList: Object{
dynamic var name = ""
let tests = List<Test>().filter("ALL Test.dict[hello] != nil")
}
//ViewController
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let realm = Realm()
let test1 = Test()
test1.name = "test1"
test1.dict = ["hello": 1]
realm.write{realm.add(test1)}
let test2 = Test()
test2.name = "test2"
test2.dict = ["nihao": 2]
realm.write{realm.add(test2)}
let test3 = Test()
test3.name = "test3"
test3.dict = ["hello": 3]
realm.write{realm.add(test3)}
}
#IBOutlet weak var label: UILabel!
#IBAction func set(sender: UIButton){
let test = Test()
let realm = Realm()
test.name = "not using CoreData"
realm.write{realm.add(test)}
text = "abc"
}
#IBAction func show(sender: UIButton){
let test = Realm().objects(TestList)
var str = ""
println("got to here")
for i in test{
str += " \(i.name)"
}
label.text = str
}
}
With this setup I got an error in my log that says:
Terminating app due to uncaught exception 'RLMException', reason: 'This method can only be called on RLMArray instances retrieved from an RLMRealm'
When I removed the filtering in my TestList object, the error became: Terminating app due to uncaught exception 'RLMException', reason: 'Migration is required for object type 'Test' due to the following errors:
- Property 'dict' has been added to latest object model.
- Property 'owner' has been added to latest object model.'
Am I understanding Realm's documentation completely wrong?
The first error is right. You can't filter on properties. Furthermore Realm doesn't allow Dictionary properties at the moment. You would need to explicitly model that as a list property of a dedicated Realm object entity, which has fields to hold a String and an Int.
class Test: Object {
dynamic var name = ""
dynamic var dict = List<TestRelatedThing>()
dynamic var owner: TestList?
}
class TestRelatedThing : Object {
dynamic var key: String
dynamic var value: Int
}
class TestList: Object{
dynamic var name = ""
let tests = List<Test>()
}
You can then query at runtime by:
Realm().objects(TestList).filter("ANY tests.key == %#", "hello")
Hint: The aggregate operator ALL is not supported for Realm. See our predicate cheat sheet for a full list of supported operators. Here it makes anywhere more sense to existential quantify that operation, because of the changed data structure, which doesn't allow an entry of TestRelatedThing with it's value property to equal nil to exist in the first place. So you can just make sure that there is an entry with key "hello" instead.
For the second error: I guess you tried to run your code before and added after that first run further fields to your model objects.
You can reset the simulator state or might just add one of the following lines of code to ensure to start with a fresh Realm on each run while development.
# Swift 1.2
var error: NSError?
NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath, error:&error)
# Swift 2
try! NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath)