Realm models' relationships - Swift - ios

I have 2 Realm models that should be connected to each other but I have difficulty understanding how to do this when writing to a model.
The 1st model will always be populated before the 2nd. This model contains a course name and some data related to the course:
class CourseModel: Object {
dynamic var coursename = ""
dynamic var par3field = 0
dynamic var par4field = 0
dynamic var par5field = 0
}
The second model has all the scoring data related to the course:
class ScoresModel: Object {
dynamic var dateplayed = ""
}
My question is this - how do i write the ScoresModel data and link this data to the appropriate CourseModel's course name and it's associated data?
ADDITIONAL AFTER ANSWER FROM bcamur:
bcamur, thank you for the feedback. This looks like what I need. However, when writing the ScoresModel to Realm, what do i need to do to let the Realm know to which course name to link the ScoresModel data I just wrote?
I'm currently writing the 2 models like this:
let mycourse = CourseModel()
mycourse.coursename = courseName
mycourse.par3field = par3s
mycourse.par4field = par4s
mycourse.par5field = par5s
CoursesData.addNewCourse(my course)
let dataToSave = ScoresModel()
dataToSave.dateplayed = dateTextField.text!
ScoresData.saveNewScores(dataToSave)
Thank you in advance.

You can save the ScoresModel objects as a list in your CourseModel:
class CourseModel: Object {
dynamic var coursename = ""
dynamic var par3field = 0
dynamic var par4field = 0
dynamic var par5field = 0
let scoreModels: List<ScoresModel> = List<ScoresModel>()
}
class ScoresModel: Object {
dynamic var dateplayed = ""
//include this if you want to be able to see which course has this ScoresModel in its scoresModels list
var courseModel: CourseModel? {
return linkingObjects(CourseModel.self, forProperty: "scoreModels").first
}
}

Related

App crashes when I am trying to saved data in my model

I am developing an App using Realm. At some point in my app when I try to manipulate my model, my app crashed in an unexpected way. Here is what the stack trace said
Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first
What I am trying to do :
lets break down my problem in parts.following is my model of app
#objcMembers public class ClassGroup : Object , Codable {
dynamic var Id : Int? = ""
dynamic var ClassName : String? = ""
dynamic var TeacherId : Int = 0
dynamic var Teachers : [TeacherMdoel]? = []
}
#objcMembers public class TeacherModel : Object , Codable {
dynamic var Id : String? = ""
dynamic var Name : String? = ""
dynamic var ClassId : Int = 0
dynamic var Students : [StudentClass]? = []
}
#objcMembers public class StudentModel : Object , Codable {
dynamic var Id : String? = ""
dynamic var Name : String? = ""
dynamic var ClassId : Int = 0
dynamic var TeacherId : Int = 0
}
now I am trying to get the list of all classes like this from realm (after saving them to realm )
let mClassLists = mDbHelper.realmObj.objects(ClassGroup.self)
Now here I get exception/error. What I am doing is, I am trying to populate my UITableView with some data that consist of all of the above models. I am fetching data and saving them in my model and trying to supply that list to UITableView but my app crash with the error I mentioned above
let mClassLists = mDbHelper.realmObj.objects(ClassGroup.self)
let classLists = Array (mClassLists)
for classModel in classLists {
let resultPredicateTeachers = NSPredicate(format: "ClassId == %#", classModel.Id)
let mTeachersList = mDbHelper.realmObj.objects(TeacherModel.self).filter(resultPredicateTeachers)
if(mTeachersList.count > 0){
var listTeachers : [TeacherModel] = []
for teacherModel in mTeachersList {
let resultPredicateStudent = NSPredicate(format: "TeacherId == 29")
let mStudentList = mDbHelper.realmObj.objects(StudentModel.self).filter(resultPredicateStudent)
if(mStudentList.count > 0){
let studentsList = Array(mStudentList)
teacherModel.Students = studentsList[0]
}
listTeachers.append(savedDetailItem)
}
classModel.Teachers? = (listTeachers)
listClassModel.append(classModel)
}
}
**In the Above code you can see that I am gathering data on behalf of Ids and saving the resultant arrays in the model. So I am getting error in the following line
**
teacherModel.Students = studentsList[0]
now I really do not understand why it is happening? I am not saving data in realm, I am saving in my model, still I am getting error.
In Realm database, if you want to modify any model (save new data or update), the operation should be performed in a write transaction:
try! realm.write {
realm.add(<your_model_objects>)
}

How to add the elements with the same ending number in the same array? (Swift 3)

I have my code below and I am attempting to create new arrays, in which the numbers in the elements increment by 1 whenever the user adds a book. For instance, when the user already has 1 book added and when he adds another one the array should read as ["bookTitle2,"bookAuthor2", "bookPublisher2", "bookNumOfPages2"]
let bookDetails = ["bookTitle", "bookAuthor", "bookPublisher", "bookNumOfPages"]
var bookDetail = ""
var bookNumber = Int()
var bookNumArray = [Int]()
if bookNumArray.contains(bookNumber) {
print("Book already exists")
} else {
while bookNumber < 2 {
bookNumber += 1
bookNumArray.append(bookNumber)
for detail in bookDetails {
bookDetail = "\(detail)" + String(bookNumber)
let newBookArray = [bookDetail]
print(newBookArray)
}
}
}
When I run the code above, this shows up instead:
["bookTitle1"]
["bookAuthor1"]
["bookPublisher1"]
["bookNumOfPages1"]
["bookTitle2"]
["bookAuthor2"]
["bookPublisher2"]
["bookNumOfPages2"]
So I want all the strings that end with 1 in one array and those that end in 2 in another array.
When you do:
for detail in bookDetails {
bookDetail = "\(detail)" + String(bookNumber)
let newBookArray = [bookDetail]
print(newBookArray)
}
For every iteration you are creating a new array with the current detail e.g bookTitle into an array of your string var bookDetail declared outside of this loop's scope.
Also note that newBookArray is a local variable, so it will be destroyed when it gets out of the loop. You would need an array of bookDetail to store the newBookArray.
let bookDetails = ["bookTitle", "bookAuthor", "bookPublisher", "bookNumOfPages"]
var bookDetailArray : [Array<String>] = [] //Your array to store all the bookDetails
var bookDetail : [String] = [] //Your array to store a bookDetail
var bookNumber = Int()
var bookNumArray = [Int]()
Then you can do:
bookDetail.removeAll() //Clear all objects before appending new one
for detail in bookDetails {
bookDetail.append("\(detail) + String(bookNumber)")
}
bookDetailArray.append(bookDetail)
Just a suggestion: As other people said, a dictionary or a class for the bookDetail properties would be a better model in your case. Read up on Object-oriented programming if you ever plan to use a class.
I would do a dictionary or create a class bookDetails with properties like bookTitle, bookAuthor, etc. And then I would create an array of the instances of this class.
If you want to do it your way, Why not create a two-way array, something like:
var arrayFinal = [[""]]
var bookNumber = 0
// Whenever the action is triggered
bookNumber += 1
var bookDetails = ["bookTitle", "bookAuthor", "bookPublisher", "bookNumOfPages"]
for detail in bookDetails
{
detail = "\(detail) +\(bookNumber)"
}
arrayFinal.add(bookDetails)
Or something like that...

How to migrate old properties into a new object with Realm Swift

Previously, I had only one object that had every value that I needed. I "regrouped" them and made separate objects. I added properties with the type of the new objects to the original object.
How can I assign the old property values to the object's properties?
Here's the code for my objects:
class MainObject: Object {
dynamic var id: Int = 0
// Schema 0
dynamic var otherId: Int = 0
dynamic var otherStr: String = ""
dynamic var anotherId: Int = 0
dynamic var anotherD: Double = 0.0
dynamic var anotherText: String = ""
// Schema 1
dynamic var otherObjectVar: OtherObject?
dynamic var anotherObjectVar: AnotherObject?
}
// Schema 1
class OtherObject: Object {
dynamic var id: Int = 0
dynamic var str: String = 0
}
class AnotherObject: Object {
dynamic var id: Int = 0
dynamic var d: Double = 0.0
dynamic var text: String = ""
}
(Changed variable names)
I tried to use convenience init(){} but it didn't work. I also tried to assign an object instance to the newObject, but that didn't work either.
Here's that code for easier understanding:
let other = OtherObject()
other.id = 0
other.str = oldObject["otherStr"] as! string
newObject["otherObjectVar"] = other
How can I migrate the old properties into a new property which is another object?
EDIT: Temporarily, I solved it with
let obj = migration.create(MainObject.className())
migration.delete(obj)
but I don't think this is the right solution. So if anyone has a solution for this, I'd appreciate it.
Assuming you're doing this during schema migration, you need to use migration.create to create new objects, not their init. Then you would set them on the new object, along the lines of:
let other = migration.create(OtherObject.className())
other["id"] = 0
other["str"] = oldObject["otherStr"] as! String
newObject?["otherObjectVar"] = other

Realm queries to extract data

I have 2 Realm Models:
class CourseModel: Object {
dynamic var coursename = ""
dynamic var par3field = 0
dynamic var par4field = 0
dynamic var par5field = 0
let scoreModels: List<ScoresModel> = List<ScoresModel>()
override internal static func primaryKey() -> String? { return "coursename" }
}
class ScoresModel: Object {
dynamic var dateplayed = ""
var courseModel: CourseModel? {
return linkingObjects(CourseModel.self, forProperty: "scoreModels").first
}
}
The app user will first add a new course for which I use CourseModel. As the user plays a course they enter scores for that course, for which I use ScoresModel, hence the primary key 'coursename'.
I query the CourseModel with
let realm = try Realm()
let results = realm.objects(CourseModel)
return results
and it produces the following result
Results<CourseModel> (
[0] CourseModel {
coursename = First Course;
par3field = 4;
par4field = 10;
par5field = 4;
scoreModels = RLMArray <0x797a36d0> (
[0] ScoresModel {
dateplayed = Apr 5, 2016; },
[1] ScoresModel {
dateplayed = Mar 3, 2016; }
);
},
[1] CourseModel {
coursename = Second Course;
par3field = 4;
par4field = 10;
par5field = 4;
scoreModels = RLMArray <0x7a046f40> (
[0] ScoresModel {
dateplayed = Apr 5, 2016; }
);
}
)
The ScoresModel produces a similar result but without the CourseModel data.
The ScoresModel has a lot of data in it, I only showed 'dateplayed' here to keep it short.
My question is this; when I've extracted the data from Realm how can I access a particular field to work with that data, i.e. how do I get the par5field data to do calculations with it, and also the 2nd question how do I get to the scoreModels data, for example 'dateplayed' to list the dates in a table for example?
When you perform a query against Realm, the results are returned in a Results object that behaves exactly like an array. So you need to iterate through each object to access the properties you want for each one.
To answer your first question, to access the par5field property (From just the first object):
let firstObject? = results.first
let par5field = firstObject.par5field
// Do calculations with it
For your second question, scoreModels is just a standard array object, so you can just insert the values it into a table view as you would a standard Array object.
If you wanted to list ALL of the ScoreModel objects, regardless of which CourseModel objects they belong to, you can perform a Realm query to get them directly.
let realm = try! Realm()
let results = realm.objects(ScoreModel)
return results

How do I create a dictionary from an array of objects in swift 2.1?

I have an array of type "drugList", and they are derived from a struct "DrugsLibrary":
struct DrugsLibrary {
var drugName = ""
var drugCategory = ""
var drugSubCategory = ""
}
var drugList = [DrugsLibrary]()
//This is the dictionary i'm trying to build:
var dictionary = ["": [""," "]]
My data model is initialized using this function:
func createDrugsList() {
var drug1 = DrugsLibrary()
drug1.drugName = "drug1"
drug1.drugCategory = "Antibiotics"
drug1.drugSubCategory = "Penicillins"
self.drugList.append(drug1)
var drug2 = DrugsLibrary()
drug2.drugName = "drug2"
drug2.drugCategory = "Antibiotics"
drug2.drugSubCategory = "Penicillins"
self.drugList.append(drug2)
var drug3 = DrugsLibrary()
drug3.drugName = "drug2"
drug3.drugCategory = "Antibiotics"
drug3.drugSubCategory = "Macrolides"
self.drugList.append(drug3)
}
my problem is that i'm trying to create a dictionary from the drugList where the key is the drugSubCategory and the value is the drug name. The value should be an array if there are several drugs in this subcategory
for example, the dictionary should look something like this for this example:
dictionary = [
"Penicillins": ["drug1","drug2"]
"Macrolides": ["drug3"]
]
I tried this method:
for item in drugList {
dictionary["\(item.drugSubCategory)"] = ["\(item.drugName)"]
}
this gave a dictionary like this, and it couldn't append drug2 to "Penicllins":
dictionary = [
"Penicillins": ["drug1"]
"Macrolides": ["drug3"]
]
So I tried to append the items into the dictionary using this method but it didn't append anything because there were no common items with the key "" in the data model:
for item in drugList {
names1[item1.drugSubCategory]?.append(item1.drugName)
}
Anyone knows a way to append drug2 to the dictionary?
I would appreciate any help or suggestion in this matter.
You need to create a new array containing the contents of the previous array plus the new item or a new array plus the new item, and assign this to your dictionary:
for item in drugList {
dictionary[item.drugSubCategory] = dictionary[item.drugSubCategory] ?? [] + [item.drugName]
}
You can use .map and .filter and Set to your advantage here. First you want an array of dictionary keys, but no duplicates (so use a set)
let categories = Set(drugList.map{$0.drugSubCategory})
Then you want to iterate over the unique categories and find every drug in that category and extract its name:
for category in categories {
let filteredByCategory = drugList.filter {$0.drugSubCategory == category}
let extractDrugNames = filteredByCategory.map{$0.drugName}
dictionary[category] = extractDrugNames
}
Removing the for loop, if more Swifty-ness is desired, is left as an exercise to the reader ;).
I have two unrelated observations:
1) Not sure if you meant it as an example or not, but you've initialized dictionary with empty strings. You'll have to remove those in the future unless you want an empty strings entry. You're better off initializing an empty dictionary with the correct types:
var dictionary = [String:[String]]()
2) You don't need to use self. to access an instance variable. Your code is simple enough that it's very obvious what the scope of dictionary is (see this great writeup on self from a Programmers's stack exchange post.
Copy this in your Playground, might help you understand the Dictionaries better:
import UIKit
var str = "Hello, playground"
struct DrugsLibrary {
var drugName = ""
var drugCategory = ""
var drugSubCategory = ""
}
var drugList = [DrugsLibrary]()
//This is the dictionary i'm trying to build:
var dictionary = ["":""]
func createDrugsList() {
var drug1 = DrugsLibrary()
drug1.drugName = "drug1"
drug1.drugCategory = "Antibiotics"
drug1.drugSubCategory = "Penicillins"
drugList.append(drug1)
var drug2 = DrugsLibrary()
drug2.drugName = "drug2"
drug2.drugCategory = "Antibiotics"
drug2.drugSubCategory = "Penicillins"
drugList.append(drug2)
var drug3 = DrugsLibrary()
drug3.drugName = "drug2"
drug3.drugCategory = "Antibiotics"
drug3.drugSubCategory = "Macrolides"
drugList.append(drug3)
}
createDrugsList()
print(drugList)
func addItemsToDict() {
for i in drugList {
dictionary["item \(i.drugSubCategory)"] = "\(i.drugName)"
}
}
addItemsToDict()
print(dictionary)

Resources