Add only objects that don't currently exist to Realm database - ios

I have a subjects Realm database:
import Foundation
import RealmSwift
class Subjects: Object {
dynamic var subject = ""
override static func primaryKey() -> String? {
return "subject"
}
}
In the database I have four subjects, say, Computing 101, English, History and Geography. Now, I have a struct that can hold an array of subjects, like so:
struct mySubjects {
static var subjects:Array<String> = [String]()
}
The user can add subjects to the struct, say, Computing 101, English, History, Geography and Accountancy for Engineers.
My problem is that I only want to add the one subject that does not already exist in the database, i.e. "Accountancy for Engineers". I thought I could simply code a NSPredicate like so:
for subject in mySubjects.subjects {
let myPredicate:NSPredicate = NSPredicate(format: "subject != '\(subject)'")
results = realm.objects(Subjects).filter(myPredicate)
No luck :(. Any suggestions?

Because you've set up a primary key, you could use the objectForPrimaryKey(_:key:) function.
If the result of something like realm.objectForPrimaryKey(Subject.self, "Subject Name") is nil, then that subject doesn't exist in the database yet.

Related

Database structure in Firebase Swift

I am trying to think of the best solution for storing user's data in firebase, considering scalability, queries etc...
For example: Each user can create a list of categories. I was thinking of storing it like so:
Categories (Collection)
- User ID (Document)
- Category ID
- title
- imageURL
I would like to access the categories collection, find a list of all user ID's and when accessing a user ID I find their list of all the categories they have created...
I am quite new to firebase...
Or should I create Categories(collections) - User ID (document) - Category Id (collection) - title etc (document) ? Just does not seem right....
I appreciate any assistance or tips!
Edit: This is how I am currently saving the data to Firebase from my IOS app:
Model:
struct Category: Codable, Identifiable {
#DocumentID var docId: String?
var id: String? = UUID().uuidString
var authorId: String
var title: String
var categoryImageUrl: String?
Save data function:
guard let userId = Auth.auth().currentUser?.uid else { return } // REMOVE
let db = Firestore.firestore() // REMOVE
let collectionRef = db.collection("Categories").document(userId)
do {
try collectionRef.setData(from: category)
}
catch {
print(error)
}
It's hard to give a comprehensive answer, but one thing to keep in mind is that the client-side SDKs for Firestore don't have an API to get a list of (sub)collection. So it's important that your application can know the collection names without that, either because they are hard-coded (most common) or because they are stored elsewhere (such as in the parent document).

How Query and Count entries in a Realm Database

I'd like to build a search on which the user can filter down the results step-by-step. So with no choice set, there is a button which says e.g. "1,234,567 Results" and if you choose a color for example the results set shrinks... we all know this kind of search. I did build it many times, but this is the first time in Realm (and swift).
Lets Say I have 5 Persons in my Person Table, then there are about 145,224 Dog entries per Person and about 2,507,327 Cat entries per Dog. How do I query and Count nested Objects in Realm?
class Person: Object {
#objc dynamic var name = ""
let dogs = List<Dog>()
// ...other Properties
}
extension Person {
static func all(in realm: Realm = try! Realm()) -> Results<Person> {
return realm.objects(Person.self)
}
}
// counts -> 145,224 db entries per person
class Dog: Object {
#objc dynamic var name = ""
dynamic var Person: Person?
let cats = List<Cats>()
// ...other Properties as well
}
extension Dog {
static func all(in realm: Realm = try! Realm()) -> Results<Dog> {
return realm.objects(Dog.self)
}
}
// counts -> 2,507,327 db entries per dogs
class Cat: Object {
#objc dynamic var name = ""
dynamic var Cat: Cat?
}
extension Cat {
static func all(in realm: Realm = try! Realm()) -> Results<Cat> {
return realm.objects(Cat.self)
}
}
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs
let dogs = Person.all(in: realm).flatMap { $0.dogs }
dogs.count // => takes ~20 seconds to count
In other words, what is the fastest way to get (count) all Dog entries of all Persons (let the cats by side for now).
I tried to workaround the problem by limit the results to 1000. If the results are >1000, then label the button like so "> 1000 Results". But even than it takes very long (I guess the get all count anyway).
So what did I do wrong?
They way you were computing the count required all Dog objects to be loaded into memory which is really inefficient. This is why you were seeing such poor performance. You want to take advantage of Realm's lazy loading features. You may want to read up on that.
I would update your Dog object by getting rid of your managed Person property and replace it with LinkingObjects. If you store a Dog in a Person.dogs List, then realm will create a back link to the Person for you. You will likely want to do the same thing with Cat. That way you can set up some really powerful nested queries.
For convenience you can add a computed Person property to index into the LinkingObjects. Just know that you won't be able to use that property in any of your queries.
class Dog: Object {
#objc dynamic var name = ""
let persons = LinkingObjects(fromType: Person.self, property: "dogs")
var person: Person? { persons.first }
}
One way to compute the count is to query of all Person objects and sum the count of dogs for each Person.
let count = realm.objects(Person.self).reduce(0) { result, person in
return result + person.dogs.count
}
Another options is to query for Dog objects that have a corresponding Person. If a Dog is not in a Person.dogs List, then it won't show up the query.
let realm = try! Realm()
let count = realm.objects(Dog.self).filter("persons.#count > 0").count
Either option is going to be much, much more efficient than what you had.
Hope this helps.

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.

Populate List in Swift 2.0 and display results

I've been learning iOS development for the past three weeks, I'm currently following a course on Udemy so far so good.
However I'm following one of the lectures whereby we build an Instagram Clone.
The instructor is using three arrays which are as follows:
var usernames = [""] // Stores all usernames
var userIds = [""] // Stores all Id's of the given usernames
var isFollowing = [false] // Stores where or not you're following that user
To me trying to keep track of what userId goes with what username using two arrays is basically an accident waiting to happen so I decided to set off and find a more feasible approach. I reverted back to my .Net days and decided to create a list so I went and created a class as follows:
class Users{
var Username : NSString = ""
var UserId : NSString = ""
var Following : Bool = false
}
Now inside my ViewController I make a call to Parse which returns me a list of users and I'm basically trying to loop through the response, and add them to the list class as shown here:
var t = [Users]() // After googling the web, this seems to be the syntax for a list declaration ?
let u = Users()
for object in users{
if let o = object as? PFUser {
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Now when I print this to the console I see the following:
ParseStarterProject_Swift.Users
As I have one user at present, however when I try to loop through T and display the username in the console it doesn't display anything.
for x in t {
print(x.Username)
}
Your basic intuition is correct, it's better to have an array of custom objects, not multiple arrays.
Regarding making it more Swifty, consider your Users type. You might want something like:
struct User {
let username: String
let userId: String
let following: Bool
}
Note,
property names should start with lowercase letter;
Users should probably be called User, as it represents a single user;
we don't generally initialize values to default values like that, but rather specify them in the initializer;
we probably use String not NSString;
if a property cannot change, you'd use let, not var;
properties begin with lower case letters;
Then you can do something like:
var t = [User]()
for object in users {
if let o = object as? PFUser {
t.append(User(username: o.username!, userId: o.objectId!, following: o.IsFollowing!)
}
}
print(t)
Clearly, with all of those ! forced unwrapping operators, you'd want to be confident that those fields were populated for all of those properties.
Using struct is nice because (a) it's a value type; (b) you get the initializer for free; and (c) you can just print them. If you really wanted User to be a reference type (a class), you'd do something like:
class User {
let username: String
let userId: String
let following: Bool
init(username: String, userId: String, following: Bool) {
self.username = username
self.userId = userId
self.following = following
}
}
And if you wanted to be able to just print them, you'd define it to conform to CustomStringConvertible:
extension User: CustomStringConvertible {
var description: String { return "<User; username = \(username); userId = \(userId); following = \(following)>" }
}
With the class, you can feel free to change that description computed property to show it in whatever format you want, but it illustrates the idea.
You are correct in considering that keeping track of what userId goes with what username using two arrays is dangerous, you in the correct direction with your approach.
First, I would just like to suggest that you use correct naming convention:
Classes should be singular (except in very specific cases).
Variable/property names should begin with lowercase.
This would mean that your user class should look like this:
class User {
var username : NSString = ""
var userId : NSString = ""
var following : Bool = false
}
I will keep your existing naming use for the next part. The main problem with your code is that the variable "u" is a object which you create only once and then modify it. You should be creating a new "Users" object for each user instead of modifying the original. If you don't do this you will just have an array with the same user multiple times. This is how your code would look now:
var t = [Users]()
for object in users {
if let o = object as? PFUser {
let u = Users()
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Next you mention that when you print to console you see the text: ParseStarterProject_Swift.Users, that is because Swift does not automatically print a pretty text with the content of your object. In order for it to print something more detailed, your "Users" object would need to implement the CustomStringConvertible. You can see a more detailed answer about that here: how-can-i-change-the-textual-representation-displayed-for-a-type-in-swif.
Lastly, you mention that when you loop trough "t" and display the username in the console it does not display anything. This is caused by one of two things:
Because there are no users being returned from parse, so the "t" array is actually empty. Try print(t.count) to see how many objects are in the array.
Because your "Users" object declares an empty string "" as the default username and the username is not being set correctly when getting the data from the parse. Which means that it IS actually printing something, just that it is an empty string. Try defining a different default value like var username : NSString = "Undefined" to see if it prints something.
Good luck learning swift!

Realm query Object property field by its property

I'm developing an application for iOS using Swift and chose Realm as a database solution for it. I asked one question about Realm and now I have another.
Suppose we have a schema like this:
class Person: Object {
dynamic var id: String = NSUUID().UUIDString
dynamic var name: String?
dynamic var cars: Car?
class Car: Object {
dynamic var name: String?
I have one class (Person) that contains any number of objects of another class (Car). Car that are "linked" with the Person has some properties in context of that Person (and they can be different for same Car for different Persons or for two similar Cars for one Person). Using List<...>() we can not store such properties for each Item, am I right?
If we use Car only for one Person and only once we can create another class that includes only additional properties for Cars and links them with ID of Person plus ID of Car. But it does't work if we have two similar Cars with different additional properties.
So, how I see the solution. Have one table (class) stores ID of Person, ID of one Car and additional properties for this Car. For another Car for the same Person it has the same Person ID, Car ID (same or not) and another additional properties for this instance of a Car.
There is a problem (and a question that I mean). Having that structure I want to query all Cars from that table with their additional properties that have Person ID equals to some_id. How should I do this? Or maybe what another structure (maybe using List<...>) I should have to achieve such kind of behavior?
What is FastList exactly ?
If you want Items to have a property of Lists collection.
You have to redefine your Realm model. something like this.
class Car:Object{
dynamic var createDate: NSDate = NSDate()
}
class Person:Object{
let cars = List<Car>()
}
and query by predicate like this
let realm = Realm()
var ownedCarsFilterByDate = realm.objects(Person).filter("ANY cars.createDate = '\(date)'")
Edited to updated question
Your solution is to create table class, which has 'Person' , 'Car' and 'Context Attribute'.
Your model would be like this
class PersonAndCarRelation:Object{
dynamic var person: Person?
dynamic var car: Car?
dynamic var contextAttribute = ""
}
and you can query all cars associated with person
let personID = "123456789"
let personAndCarArray = realm.objects(PersonAndCarRelation).filter("person.id == \(personID)")
for personAndCar in personAndCarArray{
let personName = personAndCar.person.name
let carName = personAndCar.car.name
let context = personAndCar.contextAttribute
println("I am \(personName). I have a \(carName) with \(context)")
}

Resources