Do Realm writes cascade? - swift

I'm currently using Realm Swift 1.0.1.
Say you have a Realm Object, that has another Realm Object as a property, like so:
import RealmSwift
class Car: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var model = ""
}
class Garage: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var carStored: Car?
}
If you then create new Car and Garage objects, with the Car being a property of the Garage... but only write the new Garage to the Realm, like so...
let teslaCar = Car()
teslaCar.id = 1
teslaCar.model = "Tesla"
let myGarage = Garage()
myGarage.id = 1
myGarage.carStored = teslaCar
let realm = try! Realm()
try! realm.write {
realm.add(myGarage, update: true)
}
... will the write cascade, and also save the teslaCar to the Realm as well as myGarage?
The Realm Swift write docs: https://realm.io/docs/swift/latest/#writes

I just tested it in one of the Realm sample apps to be absolutely sure. Yes, if you set an object as a linked object of another Realm object, both will be added to Realm in the next write transaction.

Related

How I can get new unique id for each new object in Realm?

I try create objects in Realm with unique id. I use this code:
class Persons: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
and in my StorageManager I use this code:
import RealmSwift
let realm = try! Realm()
class StorageManager {
static func saveObject(_ person: Persons) {
try! realm.write {
realm.add(person)
}
}
static func deleteObject(_ person: Persons) {
try! realm.write {
realm.delete(person)
}
}
}
But when I add second new object, I get error:
Terminating app due to uncaught exception 'RLMException', reason:
'Attempting to create an object of type 'Persons' with an existing
primary key value '0'.'
How I can get new unique id for each new object?
Your best bet is to let your code define that for you
class PersonClass: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
UUID().uuidString returns a unique string of characters which are perfect for primary keys.
HOWEVER
That won't work for Realm Sync in the future. MongoDB Realm is the new product and objects need to have a specific primary key property called _id which should be populated with the Realm ObjectID Property. So this is better if you want future compatibility
class PersonClass: Object {
#objc dynamic var _id = ObjectId()
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "_id"
}
}
You can read more about it in the MongoDB Realm Getting Started Guide
Note that after RealmSwift 10.10.0, the following can be used to auto-generate the objectId
#Persisted(primaryKey: true) var _id: ObjectId
Keeping in mind that if #Persisted is used on an object then all of the Realm managed properties need to be defined with #Persisted
Note if using #Persisted then the override static func primaryKey() function is not needed
The better answer since Realm v10 came out is to use the dedicated new objectID class
A 12-byte (probably) unique object identifier.
ObjectIds are similar to a GUID or a UUID, and can be used to uniquely identify objects without a centralized ID generator. An ObjectID consists of:
A 4 byte timestamp measuring the creation time of the ObjectId in seconds since the Unix epoch.
A 5 byte random value
A 3 byte counter, initialized to a random value.
ObjectIds are intended to be fast to generate. Sorting by an ObjectId field will typically result in the objects being sorted in creation order.

How to map a Firestore DocumentID to a RealmObject attribute?

I'm trying to provide some data in the cloud with Firestore that can be downloaded and stored in a Realm database on an iOS device.
The structure of my object that I want to store is:
import Foundation
import RealmSwift
import FirebaseFirestore
import FirebaseFirestoreSwift
#objcMembers class Flashcard: Object, Codable{
#objc dynamic var id: String? = NSUUID().uuidString
#objc dynamic var character: String?
#objc dynamic var title: String?
#objc dynamic var translation: String?
#objc dynamic var created: Date = Date()
let deck = LinkingObjects<FlashcardDeck>(fromType: FlashcardDeck.self, property: "cards")
override class func primaryKey() -> String? {
return "id"
}
private enum CodingKeys: String, CodingKey {
case id
case character
case translation
case created
case title
}
If I try to map the documentID to my id attribute with
#DocumentID #objc dynamic var id: String? = NSUUID().uuidString
If get the following error:
'Primary key property 'id' does not exist on object 'Flashcard'
How can I solve this problem?
EDIT: To make it more understandable here is a screenshot of my Firestore database:
The collection "PredifinedDecks" will store many decks. For example the id = DF59B1B3-BD22-47CE-81A6-04E7A274B98F represents one deck. Each deck will store an array/List with cards in it.
Not sure I fully understand the question but let me address this at a high level.
It appears there is a PredefinedDecks (a collection) that contains documents. Each document has a field (an array) of cards and some other field data. If the goal is to read in all of the documents (the decks) and their child data and store them as Realm objects, here's one solution. Start with a Realm object to hold the data from Firestore
class DeckClass: Object {
#objc dynamic var deck_id = ""
#objc dynamic var created = ""
#objc dynamic var title = ""
let cardList = List<CardClass>()
convenience init(withDoc: QueryDocumentSnapshot) {
self.init()
self.deck_id = withDoc.documentID
self.title = withDoc.get("title") as? String ?? "no title"
self.created = withDoc.get("created") as? String ?? "no date"
let cardArray = withDoc.get("cards") as? [String]
for card in cardArray {
let card = CardClass(withCard: card) {
self.cardList.append(card)
}
}
}
}
With this, you simply pass the documentSnapshot from Firestore for each document and the class will populate its properties accordingly.
and the code to read Firestore
func readDecks() {
let decksCollection = self.db.collection("PredefinedDecks")
decksCollection.getDocuments(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
for doc in documentSnapshot!.documents {
let deck = DeckClass(withDoc: doc)
self.decksList.append(deck) //a Realm List class object? Something else?
}
})
}

How to save and load GKGameModelPlayer from Realm in Swift?

I am attempting to implement a GKGameModel in my application. In it, it holds variables to a few things, but for the purposes of my question I'm interested in the following two variables:
import GameplayKit
final class GameModel: NSObject, GKGameModel {
var players: [GKGameModelPlayer]?
var activePlayer: GKGameModelPlayer?
}
I do something like this to initialise the game with 3 players (not exact)
let game = GameModel.init()
game.players = [Player(),Player(),Player()] // Create 3 players
guard let firstPlayer = game.players.first else {
return
}
game.activePlayer = firstPlayer
A player class is defined as:
class Player : NSObject, GKGameModelPlayer {
var playerId: Int // GKGameModelPlayer protocol variable
let name: String
var cash: Int = 0
}
In my project I have Realm Entities and the models seperated. So there will be a PlayerEntity and a Player class.
I'm wanting to use RealmSwift to save and load the GKGameModelPlayer data, and more specifically the ability to store/re-store the active player.
I think the key here is the playerId variable; but I am not sure.
But what I'm not sure about is retrieving this information and then re-mapping it into a valid GKGameModelPlayer format
My current idea/theory is that I need to map my model to an entity class and vice-versa.
Ie:
// [REALM] Player entity
class PlayerEntity: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
override static func primaryKey() -> String {
return "id"
}
}
And then I extend this class to do some "mapping":
extension PlayerEntity {
// Map model -> entity
convenience init(model: Player) {
self.init()
self.playerId = model.playerId
self.name = model.name
self.cash = model.cash
}
}
extension Player {
// Map entity -> model
convenience init(entity: PlayerEntity) {
let playerId = entity.playerId
let name = entity.name
let cash = entity.cash
self.init(id: playerId, name: name, cash: cash)
}
}
Right now, the playerId is always zero (0) because I'm not really sure how to set it.
I can save a player to realm.
The issue comes from when I try to restore the player, and I want to restore the activePlayer variable in the GameModel
Therefore, my question is:
How would I go about saving and restoring the activePlayer variable so that it continues to comply to GKGameModelPlayer?
I appreciate any assistance on this.
With thanks
While you could use those extensions, sometimes simpler is better. Here's a rough example:
class PlayerEntity: Object {
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
convenience init(withPlayer: PlayerClass) {
self.init()
self.playerId = withPlayer.playerId
self.name = withPlayer.name
self.cash = withPlayer.cash
}
func getPlayer() -> Player {
let p = Player()
p.playerId = self.playerId
p.name = self.name
p.cash = self.cash
return p
}
override static func primaryKey() -> String {
return "playerId"
}
}
to load all the players into an array... this will do it
let playerResults = realm.objects(PlayerEntity.self)
for player in playerResults {
let aPlayer = player.getPlayer()
self.playerArray.append(aPlayer)
}
Notice the removal of
#objc dynamic var id = UUID().uuidString
because it's not really being used to identify the object as a primary key.
The primary key is really
var playerId: Int // GKGameModelPlayer protocol variable
which is fine to use as long as it's unique.

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

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.
I changed my code to do this;
Read all the factories from JSON
Loop through the factories, creating engines
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.
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
}

How to save a struct to realm in swift?

It is easy to use Realm with classes by inheriting from Object. But how would I save a struct containing several fields to realm in Swift? E.g.
struct DataModel {
var id = 0
var test = "test"
}
I know the documentation is clear about supported types. But maybe there is nice workaround or - even better - someone from realm could write about future plans about structs.
I' suggest you to use protocols, to achive what you want.
1) Create your Struct
struct Character {
public let identifier: Int
public let name: String
public let realName: String
}
2) Create your Realm Object
final class CharacterObject: Object {
dynamic var identifier = 0
dynamic var name = ""
dynamic var realName = ""
override static func primaryKey() -> String? {
return "identifier"
}
}
3) Use protocols to transform our struct to Realm Object
public protocol Persistable {
associatedtype ManagedObject: RealmSwift.Object
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
4) Make your struct persistable
extension Character: Persistable {
public init(managedObject: CharacterObject) {
identifier = managedObject.identifier
name = managedObject.name
realName = managedObject.realName
}
public func managedObject() -> CharacterObject {
let character = CharacterObject()
character.identifier = identifier
character.name = name
character.realName = realName
return character
}
}
With these tools in place, we are ready to implement the insertion methods of our persistence layer.
5) Exemple to write datas
public final class WriteTransaction {
private let realm: Realm
internal init(realm: Realm) {
self.realm = realm
}
public func add<T: Persistable>(_ value: T, update: Bool) {
realm.add(value.managedObject(), update: update)
}
}
// Implement the Container
public final class Container {
private let realm: Realm
public convenience init() throws {
try self.init(realm: Realm())
}
internal init(realm: Realm) {
self.realm = realm
}
public func write(_ block: (WriteTransaction) throws -> Void)
throws {
let transaction = WriteTransaction(realm: realm)
try realm.write {
try block(transaction)
}
}
}
5) Use the magic!
let character = Character(
identifier: 1000,
name: "Spiderman",
realName: "Peter Parker"
)
let container = try! Container()
try! container.write { transaction in
transaction.add(character)
}
Amazing source : Using Realm with Value Types & My Article
To save a struct in Realm, means copying the data into a Realm Object. The reason why Realm Objects are classes and not structs is because they are not inert values, but auto-updating objects that represent the persisted data in Realm. This has practical benefits, such as the fact that a Realm Object's data is lazy loaded.
You can take advantage of Realm's approach by responding to the change notifications from a Realm instance. For example if your UITableView data source is based off an array property on a Realm Object, as long as you have an instance of that object, you are guaranteed that after the notification it represents the correct values. Used properly this can simplify your code versus having multiple copies of values as structs.
Swift 4 shortest answer
Save structs as Data in Realm
struct MyStruct : Codable { // Variables here }
class MyRealObject : Object {
#objc private dynamic var structData:Data? = nil
var myStruct : MyStruct? {
get {
if let data = structData {
return try? JSONDecoder().decode(MyStruct.self, from: data)
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
Use the magic
let realm = try! Realm()
try! realm.write {
let myReal = MyRealObject()
myReal.myStruct = MyStruct(....)
realm.add(myReal)
}
You can do what suggests Ludovic, or you can automate that process and get rid of that boilerplate code for each of your structs by using Unrealm.