Deleting with one-to-many relationship - swift

I have a one-to-many relationship:
class GameSystem: Object {
dynamic var gameSystemName = ""
}
class games: Object {
dynamic var gameSystemName = gameSystemName().name
dynamic var gameTitle = ""
dynamic var gameGenre = ""
}
The gameSystemNames are currently displayed on a TableView. If the user deletes a gameSystemName, I want that gameSystemName along with all of that system's games deleted.
The code I'm currently using will only delete the GameSystem, but leaves all the games.
func deleteRowAtIndexPath(indexPath: NSIndexPath) {
let realm = Realm()
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete)
}
gameSystemTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
gameSystems = Realm(path: Realm.defaultPath).objects(GameSystem)
}
I'm assuming there's a simple way to do this.

If you keep your model as it is, the solution would be to query first for the relevant objects of the relation Game:
// …
let objectToDelete = gameSystems[indexPath.row]
let gameSystemName = objectToDelete.gameSystemName
realm.write {
let games = realm.objects(Game).filter("gameSystemName = ?", gameSystemName)
realm.delete(games)
realm.delete(objectToDelete)
}
// …
Model Recommendation
I'd propose instead that you add an explicit link to your model, instead of expressing the relationship through a loosely linked foreign key. But the object-mapping is very individual and may be dependent on further constraints going beyond the scope of your question and this answer. For further reference, that would look like below:
class GameSystem : Object {
dynamic var name = ""
let games = List<Game>()
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var gameSystem: GameSystem? {
return linkingObjects(GameSystem.self, forProperty: "games").first
}
}
If you setup your model like this, you can delete your games very easy:
// …
let objectToDelete = gameSystems[indexPath.row]
realm.write {
realm.delete(objectToDelete.games)
realm.delete(objectToDelete)
}
// …
Note: In the future, Realm will bring Cascading Deletes as feature. Once that is released, you won't even need to take care of the manual deletion of associated games, but you will rather be able to declare the strong relationship in your model, so that the Games are automatically deleted.
Alternative Link Declaration
You can also declare your link vice-versa, but that would make it likely harder to use a feature like Cascading Deletes in the future. However the code to delete them for now would look the same as above.
class GameSystem : Object {
dynamic var name = ""
// Use a backlink
// (https://realm.io/docs/swift/latest/#inverse-relationships)
dynamic var games: [Game] {
return linkingObjects(Game.self, forProperty: "gameSystem")
}
}
class Game : Object {
dynamic var title = ""
dynamic var genre = ""
let gameSystem: GameSystem? = nil
}

It is very easy to delete the Parent as well as childrens in REALM SWIFT... You just need to write a small piece of code.
Here I am trying to delete my main category called "Swift" and whenever my main category gets deleted all the Sub Categories are also deleted...
do{
try realm.write({
realm.delete(Category.items)
realm.delete(Category)
})
}catch{
print("ERROR WHILE DELETING CELL ::: \(error)")
}
In short to delete subcategories all you need is to place "items" after . followed by parent name.

Related

Realm Migration: Migrating objects to another

I've added a new Model object to my realm objects and I am trying to move data from my old realm object properties to this new object.
In the old Schema, I have the following:
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
}
In the new schema, I've added a new property called media
so now it's looking this this
class Item: Object {
#objc dynamic var image = ""
#objc dynamic var coverImage = ""
#objc dynamic var video = ""
#objc dynamic var media: Media?
}
I've also added this new Model object:
class Media: Object {
#objc dynamic var fullImage = ""
#objc dynamic var thumbnailImage = ""
#objc dynamic var video = ""
var item = LinkingObjects(fromType: Item.self, property: "media")
}
My goal is to move the data from the old Item objects to the new Media objects.
I was trying to do something like this, but I don't know how to migrate that linking object, any help in the right direction would be appreciated.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
// enumerate first object
migration.enumerateObjects(ofType: Item.className()) { oldItem, newItem in
let image = oldItem?["image"] as! String
let coverImage = oldItem?["coverImage"] as! String
let video = oldItem?["video"] as! String
//migrate second object
migration.enumerateObjects(ofType: Media.className(), { (oldMedia, newMedia) in
})
}
}
})
You don't need to do anything with LinkingObjects, realm calculates those automatically when you query them.
All you'll need to do in your migration is set media to be a new Media object with the values you already have.
Other notes:
The second enumerateObjects isn't needed.
You can remove image, coverImage, and video from Item since you're moving those value to Media
Edit: This is what you would need to have in your migration.
let media = Media()
media.fullImage = oldItem?["image"] as! String
media.thumbnailImage = oldItem?["coverImage"] as! String
media.video = oldItem?["video"] as! String
newItem?["media"] = media
When adding objects to a project, the migration is super simple. In this case you're not changing or adding data to existing or new properties so it's even easier.
All that needs to be done is to increment the schemaVersion and implement your migration block. Suppose the prior version was 1, increment that to 2.
let config = Realm.Configuration (
schemaVersion: 2,
migrationBlock: { migration, oldSchemaVersion in
//nothing to do here since we not altering any data
})
Realm already knows your Object has data and that data will persist as it's not being altered. All of the Item objects will have the new media property added and linked to the new Media object.
See Local Migrations for more examples. The Updating values section is when you want to actually manipulate the existing data
Edit:
We now have a bit more information and what the OP is trying to do is to copy data from an existing object to a new object and then create a relationship between the objects. Here's the code that would do that.
The initial object is Item and the new object is Media. For this example I am copying the data in a Item property image to the Media property fullImage
let config = Realm.Configuration (
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
migration.enumerateObjects(ofType: Item.className() ) { oldObject, newObject in
let image = oldObject!["image"] as! String
let media = Media()
media.fullImage = image
newObject!["media"] = media
}
})

Trying to add objects to Class and save info to Firebase database

I want to save new objects via a view controller in my app. However, I want these new objects to load when the app is logged into. I am using firebase to save data into a database, but how can I save an object and have it return when the app is logged into again? I am new-ish to programming, sorry for any potential confusion.
Here is where the goal information is read when the app has been logged into.
for i in 0 ... clientList.count - 1 {
screenHandle = ref?.child(organizationCode).child(clientList[i].name).observe(.value, with: { (snapshot) in
let clientStuffLoad = snapshot.value as! [String:Any]
if clientStuffLoad["Goal 1 Description"] != nil {
clientList[i].goal1 = clientStuffLoad["Goal 1"] as! String
} else {
clientList[i].goal1 = ""
}
This is essentially what I have regarding adding a new member to the class Client:
#IBAction func addingClientSaveButton(_ sender: Any) {
var client7 = Client(name: addingClientName.text!,
goal1: addingClientGoal1.text!, goal2:
addingClientGoal2.text!,
goal3: addingClientGoal3.text!,
isSelected: false, s: 1,
ind: 1, targetBehavior1 : addingClientTB1.text!,
targetBehavior2 : addingClientTB2.text!,
targetBehavior3 : addingClientTB3.text!,
targetBehavior1Info : addingClientTB1Info.text!,
targetBehavior2Info : addingClientTB2Info.text!,
targetBehavior3Info : addingClientTB3Info.text!)
but I would like the object name to read the client name input as opposed to client7
The second part to this is that I want a way to write this to the database, and be able to read it at log in so that I can use the properties of the class and add to it when adding a new client.
This is a super broad question because it covers a lot of different aspects of working with Firebase; writing, reading, handling DataSnapshots etc. Also, I don't know what your data represents so I picked something for me to cover some of the aspects of working with Firebase.
There's no error checking but it works as is. I've commented along the way.
Firebase has no objects; just parent and child nodes. Everything can be thought of as key: value pairs like a dictionary. You cannot write an object or read an object. Only NSString, NSNumber, NSDictionary and the dreaded NSArray (or their Swift counterparts)
Let's start with a class - there's 100 ways to do this but I like classes to be responsible for their properties as well as accepting them and presenting them
class WineClass {
var wine_key = ""
var name = ""
var varietal = ""
//this is used when creating a new wine object before storing in firebase
init(withName: String, andVarietal: String) {
self.name = withName
self.varietal = andVarietal
}
//this is used when we are loading data from firebase to create the wineclass object
init(withSnapshot: DataSnapshot) {
let wineName = withSnapshot.childSnapshot(forPath: "wine_name").value as? String ?? "No Wine Name"
let wineDict = withSnapshot.value as! [String: Any]
let wineVarietal = wineDict["wine_varietal"] as? String ?? "No Wine Varietal"
self.wine_key = withSnapshot.key //when we read a wine, this will be it's reference in case we want to update or delete it
self.name = wineName
self.varietal = wineVarietal
}
//this is use to create a dictionary of key:value pairs to be written to firebase
func getWineDictForFirebase() -> [String: Any] {
let d = [
"wine_name": self.name,
"wine_varietal": self.varietal
]
return d
}
}
Then, we need a class var to store the WineClass's. This would be for example a dataSource for a tableView
var wineArray = [WineClass]() //a class var array to store my wines
Then I will give you two buttons, one that populates and writes some wine to Firebase and then a second that read them in and prints to console
func button0() {
self.writeWine(withName: "Scarecrow", andVarietal: "Red Blend")
self.writeWine(withName: "Ghost Horse", andVarietal: "Cabernet Sauvignon")
self.writeWine(withName: "Screaming Eagle", andVarietal: "Cabernet Sauvignon, Merlot, Cabernet Franc")
}
func button1() {
self.readWines()
}
And then the function that accepts some strings as properites for each wine and writes them to Firebase
func writeWine(withName: String, andVarietal: String) {
let newWine = WineClass(withName: withName, andVarietal: andVarietal) //create a new wine object
let wineListRef = self.ref.child("wine_list") //get a reference to my firebase wine_list
let thisWineRef = wineListRef.childByAutoId() //a new node for this wine
let d = newWine.getWineDictForFirebase() //get the wine properties as a dictionary
thisWineRef.setValue(d) //save it in firebase
}
and finally a function that reads in those wines, and prints their properties in console
func readWines() {
let wineRef = self.ref.child("wine_list")
wineRef.observeSingleEvent(of: .value, with: { snapshot in //we are reading in the entire wine node which will contain many child nodes
let allWines = snapshot.children.allObjects as! [DataSnapshot] //cast each child node as a DataSnapshot & store in array
for wineSnap in allWines { //iterate over each child node in the array
let wine = WineClass(withSnapshot: wineSnap) //create a new wine, ensuring we also keep track of it's key
self.wineArray.append(wine) //add to the array
}
for wine in self.wineArray {
print(wine.wine_key, wine.name, wine.varietal)
}
})
}
lastly, when button0 is clicked, our Firebase looks like this
wine_list
-LhbjhkEC8o9TUISCjdw
wine_name: "Scarecrow"
wine_varietal: "Red Blend"
-LhbjhkEC8o9TUISCjdx
wine_name: "Ghost Horse"
wine_varietal: "Cabernet Sauvignon"
-LhbjhkEC8o9TUISCjdy
wine_name: "Screaming Eagle"
wine_varietal: "Cabernet Sauvignon, Merlot, Cabernet Franc"
and then the output when button1 is clicked
-LhbjhkEC8o9TUISCjdw Scarecrow Red Blend
-LhbjhkEC8o9TUISCjdx Ghost Horse Cabernet Sauvignon
-LhbjhkEC8o9TUISCjdy Screaming Eagle Cabernet Sauvignon, Merlot, Cabernet Franc
Note that self.ref is a reference to the root node of my firebase yours will need to reference your firebase.

How to use NSSet created from Core Data

I have the following core data model:
where Person to Codes is a one-to-many relationship.
I have a function which returns a Person record and if the code person.codes returns an NSSet of all the codes associated with that Person. The issue that I am having is how to use the NSSet.
person.codes.allObjects.first returns this data:
<Codes: 0x60000213cb40> (entity: Codes; id: 0xb978dbf34ddb849 <x-coredata://A2B634E4-E136-48E1-B2C5-82B6B68FBE44/Codes/p1> ; data: {
code = 4LQ;
number = 1;
whosAccount = "0xb978dbf34ddb869 <x-coredata://A2B634E4-E136-48E1-B2C5-82B6B68FBE44/Person/p1>";
})
I thought if I made person.codes.allObjects.first of type Codes, I would be able to access the code and number elements but I get an error: error: value of type 'Any?' has no member 'number'
Also, how can I search this data set for a particular code or number.
I appreciate that this is proabably a simple question but have searched and read the documentation to no avail. I suspect that may base knowledge is not sufficient.
Update
I have a CoreDataHandler class which contains the following code:
class CoreDataHandler: NSObject {
//static let sharedInstance = CoreDataHandler()
private static func getContext() -> NSManagedObjectContext {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static func fetchPerson() -> [Person]? {
let context = getContext()
do {
let persons: [Person] = try context.fetch(Person.fetchRequest())
return persons
} catch {
return nil
}
}
I can fetch a person using:
let row = personTableView.selectedRow
let person = CoreDataHandler.fetchPerson()?[row]
Core Data supports widely native Swift types.
Declare codes as Set<Codes> in the Person class.
It's much more convenient than typeless NSSet.
You get a strong type and you can apply all native functions like filter, sort, etc. without type cast.
let codes = person.codes as! Set<Code>
Once that is done you can access the properties. Searching can be done by filtering for instance
let filteredCodes = codes.filter({ $0.code == "XYZ" })
will return all objects that has the code "XYZ". Or to get only one you can use
let code = codes.first(where: {$0.id == 1})
which will return the first object that has id = 1
A simple example getting all Person objects that has a given code
func findWithCode(_ code: String) -> [Person] {
guard let persons = CoreDataHandler.fetchPerson() else {
return []
}
var result = [Person]()
for person in persons {
let codes = person.codes as! Set<Code>
if codes.contains(where: { $0.code == code }) {
result.append(person)
}
}
return persons
}

Generic Type array with UITableView and Realm

I am using realm for database. I have Favourite Object and History Object.
I want to show in TableViewController. However, I don't want to do duplicated code. For now, in FavouriteViewController , it has var favs: Results<OGFav>? and HistoryViewController, it has var history: Results<OGHistory>?
Most of the code are the same and different is data type.
Example: it only different like following
if let object:OGFav = self.favs?[indexPath.row] {
In some place , I use like
let fav:OGFav = favs[indexPath.row]
For History
if let object:OGHistory = self.history?[indexPath.row] {
History also use like below
let history:OGHistory = self.history[indexPath.row]
How can I clean the code ? I am using two viewcontroller and code are the same excepted OGFav and OGHistory.
Update:
OGFav and OGHistory have the same data.
class OGFav: Object {
dynamic var word = ""
dynamic var def = ""
}
class OGHistory: Object {
dynamic var word = ""
dynamic var def = ""
dynamic var updatedAt = NSDate()
}

Filtering Realm objects with Swift

I always get the following error when trying to filter my Realm database using NSPredicate:
Property 'text' is not a link in object of type 'getType'
I want to filter my Realm database to show only the items that have some specific text in them. This is what I've tried:
let realm = try! Realm()
let predicate = NSPredicate(format: "typez.text.filter = 'special'")
let filterThis = realm.objects(Publication).filter(predicate)
print(filterThis)
The relevant portion of my model classes is:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
You mentioned that the relevant portions of you model classes look like so:
class Publication: Object, Mappable {
dynamic var id: Int = 0
var typez = List<getType>()
dynamic var url: String?
}
class getType: Object, Mappable {
dynamic var text: String = ""
}
If I understand you correctly, you want to find Publication instances that have an entry in their typez list with text equal to special. You can express that as:
let realm = try! Realm()
let result = realm.objects(Publication).filter("ANY typez.text = 'special'")
print(result)
I was not liking the accepted answer here because it doesn't actually answer the question... but then it helped me more than I realized. I will now be using closures instead of NSPredicates whenever possible. The actual answer to this question should be a slightly modified version of #NSGangster's answer:
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.typez.filter({ $0.text == "special" } != [] ) })
print(filterThis)
.. or something close to that.
But what I was looking for was a bit different. I needed a way to filter on exact words of a multi-word string, and using an NSPredicate with "CONTAINS" would match any containing substring, e.g. a search for "red" would match "fred". Realm doesn't support "LIKE" or regex yet, so using a closure was the only thing I could get to work:
//I was going for a "related terms" result for a dictionary app
let theResults = terms.filter(
{
//Looking for other terms in my collection that contained the
//title of the current term in their definition or more_info strings
$0.definition.components(separatedBy: " ").contains(term.title) ||
$0.more_info.components(separatedBy: " ").contains(term.title)
}
)
With as much of the day as I spent searching, hopefully this helps someone else with a similar issue.
I don't usually use NSPredicate's directly, instead I do an inline predicate closure within the filter paramter.
let realm = try! Realm()
//Array of publications
let realmObjects = realm.objects(Publication)
//any publication where .text property == special will be filtered. and filter out empty array
let filterThis = realmObjects.filter({ $0.getType.filter({ $0.text == "special" } != [] ) })
print(filterThis)