Prepopulate Realm database with many to many relationships? - swift

I'm brand new to this, so forgive me if I'm missing something obvious or not asking the right question. I plan to make an app that have a few sets of data that require many-to-many relationships. For example, if I have a model for Food items and a model for CookingMethods to cook that food item. So, each Food can have multiple CookingMethods, and each CookingMethod applies to multiple types of Food.
I think this is the right way to set up the realm data:
class Food: Object {
#objc dynamic var title: String = ""
#objc dynamic var id: Int = 0
var cookingMethods = List<CookingMethod>()
}
class CookingMethod: Object {
#objc dynamic var id: Int = 0
#objc dynamic var title: String = ""
let foods = LinkingObjects(fromType: Food.self, property: "cookingMethods")
}
Is it now possible to import a set of data (probably a csv file?) using either Realm Studio or programmatically that allows me to link this relationship? The Food would have a list of CookingMethods, and the CookingMethods would link back to multiple different Foods?
If I'm going about this all wrong please let me know, there is a spreadsheet of data that I'd like to add to my app's database as a one time thing.

Related

RealmSwift Observing a collection of child objects (I think)

So I've got a collection of objects - Category
Each Category object has a field called total which contains another object, a Total
Each Total object has a field called amount which is a simple double
I want to observe any changes to the amount field of all the Total objects in my collection of Category... Basically, if I could add an observer onto each Category object's total. Does that makes sense? It's much harder to describe that I thought. haha
I've tried all kinds of different way but all either end up in memory leaks or horribly inefficient and breaking the typical Realm way of handling data.
Something to note - there are thousands of TartuGecko objects... Anything in the app that relates to an amount is stored as a TartuGecko. Therefore, observing them all is impractical.
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
dynamic var total: TartuGecko?
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
}
There are some inconsistencies in how you are describing things (as noted by Jay) but assuming you just need the concepts, this is how you could structure your Realm models:
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
dynamic var categoryTotal: CategoryTotal?
}
Then you can access all the TartuGecko objects on each CategoryTotal object with categoryTotal.tartuGeckos like this:
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.tartuGeckos) //<-- All your TartuGeckos that point to that category are available here
}
You will have to make sure that each TartuGecko object that gets created has a reference to a CategoryTotal.
If you just want a total amount for each CategoryTotal, then a more direct solution would be to use a computed property like this:
final class CategoryTotal: QueryModelObject{
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
//Computed property
var totalAmount: Double{
var total = 0.0
//There are fancier ways of calculating this, but I'm trying to be clear
for tartuGecko in tartuGecko{
total += tartuGecko.amount
}
return total
}
}
Then on each CategoryTotal object you always have the total amount available to you.
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.totalAmount) //<-- Total amount
}

Storing weather forecast data in Swift

I want to make an application which requires a lot of weather forecast data, three cities, seven days, 24 hours and six values in every hour of forecast.
I'm using the Dark Sky API and pod called ForecastIO.
What should I use for storage? CoreData or Realm? I was told that Realm is a lot of easier to work with and it's also more efficient. I looked into code and for a beginner it is much easier and not dealing with any graphs is also plus, but if it needs to be done, I'll study it. And how should I structure it?
I tried this but Realm accepts only basic data types, so it won't work.
HourlyWeather.swift
import Foundation
import RealmSwift
class HourlyWeather: Object {
#objc dynamic var temperature: Double = 0
#objc dynamic var wind: Double = 0
#objc dynamic var precip: Double = 0
#objc dynamic var humidity: Double = 0
#objc dynamic var uvIndex: Int = 0
#objc dynamic var icon: String = ""
}
DailyWeather.swift
import Foundation
import RealmSwift
class DailyWeather: Object {
#objc dynamic var day = [HourlyWeather()] // I understand that this is a no-no for Realm
}
CityWeather.swift
import Foundation
import RealmSwift
class CityWeather: Object {
#objc dynamic var city = [DailyWeather()] // The same
}
I wanted this code to be accessible as
City.day[index].hour[index].temperature
for example
You need to use List instead of Array when you want to store a collection of Realm objects as a property of another object. For more information, read the Many-to-many relationships part of the official docs (but I'd suggest going through the whole documentation, since it gives a really good starting point for using Realm).
class DailyWeather: Object {
let day = List<HourlyWeather>()
}
class CityWeather: Object {
let city = List<DailyWeather>()
}
You need to declare a property of type List. See the Many to Many docs
class DailyWeather: Object {
let city = List<DailyWeather>()
}

Where's the best place to call methods that interact with my database?

I'm creating an app that interacts with a Firestore database. As of now I have a singleton class, DatabaseManager that has all the methods relating to the Firestore database (i.e. get/post methods).
I have a User model called User that has properties such as name, email, photoURL, and some app-specific properties. Any user can edit their profile to update information from a view controller called EditProfileViewController.
Now my question is: is it best to call the DatabaseManager.shared.updateInfo(forUser: user) (where user is a User instance) from EditProfileViewController, User, or some other place?
Sorry if this is an obvious question, but there's going to be a lot of points in the app where I'll need similar logic so I wanted to know what's the best design. Also I'm sure this question has more to with MVC than it does Firebase/Swift.
A couple of thoughts:
Rather than accessing the singleton directly with, DatabaseManager.shared.update(for:), I might instead have a property for the database manager, initialize/inject it with the DatabaseManager.shared, and have whatever needs to interact with the database use that reference, e.g., dataManager.update(for:). The goal would be to allow your unit tests to mock a database manager if and when necessary.
I would not be inclined to have a view controller interact directly with the DatabaseManager. Many of us consider the view controller, which interacts directly with UIKit/AppKit objects, as part of the broader “V” of MVC/MVP/MVVM/whatever. We’d often extricate business logic (including interaction with the database manager) out of the view controller.
I personally wouldn’t bury it under the User object, either. I’d put it in an extension of the database manager, and called from the view model, the presenter, or whatever you personally want to call that object with the business logic.
Is there a reason you're using a singleton to contain all the Firestore logic? User model should contain the method updateInfo.
Here's an example i've used with Firestore:
class Group {
// can read the var anywhere, but an only set value in this class
private(set) var groupName: String!
private(set) var guestsInGroup: Int!
private(set) var joinedGroup: Bool!
private(set) var timeStampGroupCreated: Date!
private(set) var documentId: String!
init(groupName: String, guestsInGroup: Int, joinedGroup: Bool, timeStampGroupCreated: Date, documentId: String) {
self.groupName = groupName
self.guestsInGroup = guestsInGroup
self.joinedGroup = joinedGroup
self.timeStampGroupCreated = timeStampGroupCreated
self.documentId = documentId
}
// method to parse Firestore data to array, that table view will display
class func parseData(snapshot: QuerySnapshot?) -> [Group]{
var groups = [Group]()
guard let snap = snapshot else { return groups }
for document in snap.documents {
let data = document.data()
let groupName = data[GROUP_NAME] as? String ?? "No Group Name"
let guestsInGroup = data[GUESTS_IN_GROUP] as? Int ?? 0
let joinedGroup = data[JOINED_GROUP] as? Bool ?? false
let timeStampGroupCreated = data[TIMESTAMP_GROUP_CREATED] as? Date ?? Date()
let documentId = document.documentID
// add objects with fetched data into thoughts array
let newGroup = Group(groupName: groupName, guestsInGroup: guestsInGroup, joinedGroup: joinedGroup, timeStampGroupCreated: timeStampGroupCreated, documentId: documentId)
groups.append(newGroup)
}
return groups
}
}

Realm returning wrong data

I'm new to realm. I have two objects, Restaurant and Address.
class Address: Object {
#objc dynamic var identifier: Int = 0
#objc dynamic var name: String!
#objc dynamic var restaurant: Restaurant!
}
notice that Address has relationship with restaurant.
class Restaurant: Object {
#objc dynamic var identifier: Int = 0
#objc dynamic var name: String!
#objc dynamic var location: String!
#objc dynamic var images: String!
}
now on my api side I will request for addresses and it will return list of addresses with restaurant but the restaurant has an identifier value only and others are nil to save loading time. I will save it to my realm.
Now in order to get the other restaurant details like name and location etc. I will request again list of restaurants to the api and save it to realm.
Right now I got 2 list of records save on my database. Address with incomplete restaurant information and Restaurants with complete information.
Now I want to get the Restaurant with complete information using the identifier of the Address.restaurant.identifier. so the code is like this.
let realm = try! Realm()
let predicate = NSPredicate(format: "identifier = %i", address.restaurant.identifier)
let restaurant = realm.objects(Restaurant.self).filter(predicate).first!
My expected output is the restaurant with complete details because I'm filtering the Restaurant class, instead I got the restaurant inside the Address object with incomplete restaurant details.
Could someone help me query the restaurant with complete details? Thank you.
As you said you make 2 requests, one for short addresses and another for restaurant details, in first request you make a relation between address and restaurant, but when you are getting restaurant details you save it like new object.
I think in your case you need to provide primaryKey for each class that you want to save in DB like:
override static func primaryKey() -> String? {
return "identifier"
}
After that data from restaurant details request will update your restaurant with the same id in DB and there will be only one unique object.
Also, you can have 2 tables, one for restaurants and another for addresses. Address will have restaurants identifiers, restaurant will have only one address identifier.
Hope it help you
From Realm Swift documentation for relationships: "For example, rex.owner?.address.country will traverse the object graph and automatically fetch each object from Realm as needed."
So realm would fetch Restaurant object for Address object only when it is accessed. So I would not try to optimize fetches by having 2 Restaurants in the Realm database.
Do you have to do a query?
Instead of doing:
let realm = try! Realm()
let predicate = NSPredicate(format: "identifier = %i", address.restaurant.identifier)
let restaurant = realm.objects(Restaurant.self).filter(predicate).first!
Couldn't you just do (given that you have the Address object):
let restaurant = address.restaurant

Query where list has item/object in Realm

Using Realm in Swift (I am still using the version just before v1.x):
I have this class
class Event: Object {
dynamic var id: String = ""
dynamic var title: String? = nil
dynamic var creator: User?
let members = List<User>()
}
How can I find all events with the member "User A" (I have the id of the User A).
I tried something like this but doesn't really work:
let predicate = NSPredicate(format: "ANY members.id == %#", userA.id)
eventsWithUserA = realm.objects(Event).filter(predicate)
If you're not using the latest version, hopefully you're at least using version 0.100 or higher. If so, you can use Realm's inverse relationships feature to do this:
class User: Object {
let events = LinkingObjects(fromType: Event.self, property: "members")
}
After implementing this, user.events will return a List of every Event object in which the user object is in its members property.
let eventsWithUserA = userA.events
Hopefully this should eliminate the entire need to manually perform a query for what you're trying to achieve here.
Let me know if that doesn't work for you!