Realm Relation how to implement - ios

I am working on iOS and that is saving product. and this product has some more things inside its model
let suppose the following model
#objcMembers public class ProductModel : Object, Codable {
dynamic var Id : Int = 0
dynamic var Name : String = 0
dynamic var Price : Double = 0.0
}
and the other model (CustomerModel) that contains the ProductModel is as follow
#objcMembers public class CustomerModel : Object, Codable {
dynamic var Id : Int = 0
dynamic var Name : String = 0
var Product : ProductModel? = nil
}
Now when I save customer with the product inside it, I can see that in Realm it gets saved successfully. But if and only if that object is not in Realm already,
Let suppose this
let customer1 = CustomerModel()
customer1.Id = ...
customer1.Name = .....
customer1.Product = product1
Now this customer data is saved. But I am getting exception if I try to save following data
let customer2 = CustomerModel()
customer2.Id = ...
customer2.Name = .....
customer2.Product = product1
Just notice that customer2 also want to save product info that is already saved in Realm namely "product1".
So how to handle this sitution.
I am trying to save the data with the following generic function for realm objects
func save <T: Object> (_ obj : T){
do {
try realmObj.write{
realm.add(obj)
}
}catch{}
}
Question 2:
Also I want to get All Customer, I know how to do it, but problem is It never retrieves the Product inside the Customer. I can see in Realm DB Browser that the customer that get saved with the product, that customer table contains the reference of Product also. But when I try to get all customer then that customer have only customer details not Product detail. Whereas that must be there.

Just put dynamic keyword before your property
dynamic var Product : ProductModel? = nil

Related

How to update object in both array if I use separate array for search result?

I want add search functionality in my listing screen. Every item in listing is not view-only but, say for example an object whose few properties are changing based on user action.
To add search functionality, the general way is to keep separate array for search result. Now, in my case user can either alter few values of object from either by searching or without searching. In that case, I will have to update object in both the array based on id of that object but this solution doesn't seems to be proper.
What is the proper way to update the model in this case?
Here is my object
class Course : NSObject
{
var courseId : Int = 0
var name : String = ""
var chapters : [Chapter] = []
var estimatedTime : Int = 0
// Values which will be altered
var userSpentTime : Int = 0
var status : CourseStatus = .pending // Enum
init(_ json: Any?)
{
guard let data = json as? Dictionary else { return }
/* Assignment of values from JSON */
...
...
}
}

Realm how to save item if not exist else it should update

I have a data coming from webservice. I have same models made in my project. To demonstrate the model let me show a little idea of my model
Model1:
class Standard {
var Id = 0
var standardName = ""
var students : [StudentModel] = nil
}
Model2:
class StudentModel {
var Id = 0
var stdName = ""
var Teacher: [TeacherModel] = nil
}
Model3:
class TeacherModel {
var Id = 0
var Name = ""
}
Now what I am facing is as follow :
I have TeacherModel in DB already, but I dont have student model and standard model instance int he Realm, so it is supposed to save coming student and standard data in Realm. and skip or update TeacherModel in Realm. But right now it is crashing on TeachModel data as one Teacher with same things are already saved in Realm.SO i am looking forward to some sort of method in which it update or just skip saving Item if already exist in the Realm.
Note: These models are just to demonstrate my case, where as I know there are many typos and other thing. Also I did not showed any implementation of Realm over my Models. Its just to show you the things to make you understand.
Well you need to have class func primaryKey() -> String? overriden.
override class func primaryKey() -> String? {
return "Id"
}
And then use realm.write(...) or realm.create(...) functions with update parameter set to true.

Realm list property not saving data

I have two models, lets call them Schools, and Teachers. Models are as under
#objcMembers public class Schools : Object {
dynamic var Id : String = ""
dynamic var UserId : Int64 = 0
dynamic var Name : String? = ""
dynamic var listTeachers : List<Teachers>? = nil
dynamic var teachersList : [Teachers]? = []
}
#objcMembers public class Teachers : Object {
dynamic var Id : String = ""
dynamic var UserId : Int64 = 0
dynamic var Name : String? = ""
}
now before saving data I m putting Teachers objects (list) in School object then I save that School object in realm write closure.
after that I just get the School realm object and when I get the Teachers list, it always gets Nil. What is the case?
Am i missing something or missing something to understand the real LIST property??
please help
Update: This is how I am getting object
let mSavedItems = mDbHelper.realmObj.objects(Schools.self)
if let teachers = mSavedItems[0].teachersList{// here teacher list is nil
}
Your Schools declaration is flawed. You shouldn't declare a List as dynamic or mutable, nor should you make it Optional. As the docs clearly state, let listTeachers = List<Teachers>() is the correct way to declare a many-to-many relationship.
Storing a property of type Array is also not supported by Realm, so you should delete the teachersList : [Teachers]? property.
#objcMembers public class Schools : Object {
dynamic var Id : String = ""
dynamic var UserId : Int64 = 0
dynamic var Name : String? = ""
let listTeachers = List<Teachers>()
}

Struct Gobal is being updated from another VC when it shouldn't be

We are creating a global struct where we store our products for a shopping cart. Here is the code to create it
class Cart : NSObject {
var allProductsInCart = [Product]()
class var sharedCart: Cart {
struct Static {
static let instance = Cart()
}
return Static.instance
}
}
In a separate view controller (called ProductVC), we are creating an instance of Product. We add that product to the array listed above, allProductsInCart like this and then change the value:
let newProduct = Product()
newProduct.name = "Costco"
Cart.sharedCart.allProductsInCart.append(newProduct)
newProduct.name = "test2"
print ("value is: \(Cart.sharedCart.allProductsInCart[0].name)") //It prints "test2" not "Costco"
When the separate instance of product is modified in ProductVC, it is also changed in the struct. It is definitely a separate instance of product because it has a different variable name as seen above.
It should print Costco still because the instance of Product within the ProductVC was modified after it was added to the struct and we want to print the instance in the struct. Right? Or do I have something wrong?
Is this normal behavior? Is there a reason this is happening? Is there a better way that a global variable is supposed to be created or a better way to create a sharedCart that we can access in any view controller?
This happens because newProduct is a reference type (defined by a class) so when you change the name it just changes the name of the same reference. There is only one product in the cart at this point, not two. For reference, the easiest way to define a singleton in Swift would be
class Cart {
static let shared = Cart()
var products = [Product]()
}
So, just following your example:
let newProduct1 = Product()
newProduct1.name = "Costco"
Cart.sharedCart.products.append(newProduct1)
let newProduct2 = Product() // a new product
newProduct2.name = "test2"
// I assume you will want to add this product as well
Cart.shared.products.append(newProduct2)
//This will print "Costco"
print ("value is: \(Cart.sharedCart.products[0].name)")
The reason why allProductsInCart is returning a different value is because of a concept known as Pass by Value versus Pass by Reference, it is nothing to do with the variable being static, or global.
Product is an object. All objects are known as Pass by Reference. This means that the value of the object points to a location in memory, and whenever that value at that location in memory is changed, then, the value is changed everywhere else pointing to that location in memory.
As allProductsInCart stores an array of Product, then it is storing an array of Objects, which means, whenever you change the value of the object, you are changing the value of wherever it is stored in memory.
Edit:
If you wanted it to be pass by value, you would have to convert your array to a primitive data type. For example:
var productName = [String]() would prevent the value from being changed.
Your code would then look like:
class Cart : NSObject {
var productName = [String]()
static let instance = Cart()
}
Then when you call
let newProduct = Product()
newProduct.name = "Costco"
Cart.instance.productName.append(newProduct.name!)
newProduct.name = "test2"
print("Value is \(Cart.instance.productName[0])")
It will print Costco.
Look at What's the difference between passing by reference vs. passing by value?
For further information about pass by value and pass by reference

How to properly handle create and update for realm.io relationships

I've got two RLMObjects:
class Timeline: RLMObject {
dynamic var entries = RLMArray(objectClassName: Entry.className())
dynamic var id = 0
dynamic var title = ""
dynamic var coverPhoto = ""
dynamic var body = ""
override class func primaryKey() -> String {
return "id"
}
}
class Entry: RLMObject {
dynamic var id :Int = 0
dynamic var timelineID :Int = 0
dynamic var name :String = ""
dynamic var caption :String = ""
dynamic var body :String = ""
dynamic var imageURL :String = ""
override class func primaryKey() -> String {
return "id"
}
}
As you can see there is a To-Many relationship between a Timeline and an Entry. A timeline has many Entries.
the JSON fetch and assignment of my timelines works just fine, they are being fetched and set with:
realm.beginWriteTransaction()
Timeline.createOrUpdateInDefaultRealmWithObject(timelineObject)
realm.commitWriteTransaction()
My problem arises when I'm trying to fetch the entries (which is a separate JSON request/response) for a given timeline and set them.
Fetching and creating them like this works just fine:
realm.beginWriteTransaction()
Entry.createOrUpdateInDefaultRealmWithObject(entryObject)
realm.commitWriteTransaction()
But of course that doesn't associate them with my Timeline object. So then I tried to get my Timeline object and then adding the Entry object to my timeline by doing:
let timelineObject = Timeline(forPrimaryKey: id)
timelineObject.entries.addObject(entryObject)
(inside of a transaction ofc).
This works fine for the initial fetch. But when I try to refetch your data, you get the following RLMException:
'RLMException', reason: 'Can't set primary key property 'id' to existing value 59.'
Am I doing something wrong, or is this a bug? It seems like there should be an instance method on RLMObject that creates or updates a product, like it's class method does?
Solution
As segiddins comment suggests, you add the return from the class method: createOrUpdate... and then add that return to your object (in my case, the timelineObject.entries.addObject(entryReturn).
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
for entry in json["entries"] {
...
//Process and map the JSON
...
let entryReturn = Entry.createOrUpdateInDefaultRealmWithObject(entryObject)
timelineObject.entries.addObject(entryReturn)
}
realm.commitWriteTransaction()
Try adding the Entry object that gets returned from the createOrUpdate call.

Resources