How do I add attribute validation to Realm models? - swift

In a swift IOS app I have a Realm Model like this:
import Foundation
import RealmSwift
class Item: Object {
dynamic var name = ""
let prices = List<Price>()
}
This lets me save an instance of Item with an empty String as a name:
let newItem = Item()
newItem.name = "" //or not set the attribute at all
let realm = Realm()
realm.write {
realm.add(newItem)
}
How can I setup simple (and more complex) attribute validation?
The name String is supplied from an UITextField. Do I do validate the user input in the ViewController responsible here, or is there a way to do this on the Realm Model, like the rails developer in me wants to do?
Thanks in advance.
Cheers,
nc

Realm currently does not support validation beyond uniqueness for primary keys, but you can follow https://github.com/realm/realm-cocoa/issues/1769 for further updates!

Related

Realm multiple database problems

There are two databases locally, 'user.realm' and 'person.realm'. 'User' model need to be saved in 'user.realm'. 'Person' model need to saved in 'person.realm' , When the following code is used, the 'User' and 'Person' tables is created in 'user.realm'. I need 'User' model only saved to 'user.realm'.
class User:Object {
#objc dynamic var id:Int = 0
#objc dynamic var name:String = ""
}
class Person:Object {
#objc dynamic var id:Int = 0
objc dynamic var name:String = ""
}
//create "user.realm"
var config = Realm.Configuration()
config.fileURL = dataPath.appendingPathComponent("user.realm")
Realm.Configuration.defaultConfiguration = config
var realm = try! Realm()
//create "person.realm"
var config = Realm.Configuration()
config.fileURL = dataPath.appendingPathComponent("person.realm")
Realm.Configuration.defaultConfiguration = config
var realm = try! Realm()
When I execute a user database, I only need to create the "User" table in user.realm, instead of "User" and "Person" two tables are created together in "user.realm", I can't remove the Person model, because then you cannot create the "Person" table in "Person.realm". Personally, it seems that Realm is more troublesome to implement.
One of the key points to know about Realm is that it is not a relational database. While it has that 'feel' which is intentional, there are no tables as such and Realm structures are often split amongst different realms. Check out Realm vs Other Databases.
In some cases it's more efficient to store like data within the same Realm - a User and a Person look very similar so it may be better to store them within the same Realm - I just don't know the use case here.
To answer the question though, you can specifically tell Realm what object or objects to store within a realm. It's done through the config like this
let config = Realm.Configuration(objectTypes: [MyClass.self, MyOtherClass.self])
let realm = try! Realm(configuration: config)
That realm will only have MyClass and MyOtherClass in it. Applying that to your question
let config = Realm.Configuration(objectTypes: [UserClass.self])
let myUserRealm = try! Realm(configuration: config)
Will only have one object type in it, a UserClass.
That's covered in the Class Subsets section of the Realm Documentation Provide a subset of classes to a Realm

Prepopulate Realm database with many to many relationships?

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.

Swift Core Data - storing an array of custom types

I am trying to create a data model which mirrors a view model that I use to handle an API call, the idea being that I will be able to store all the necessary data in core data and then access it when the user is offline, effectively giving the app offline functionality.
However, there is one entity which I need to store which is an array of a custom class that I have in the app:
[OrderSheet]
This is a struct defined as follows:
struct OrderSheet {
let order: SheetClass // codable class
let sheet: Sheet // codable struct
init(fuelOrder: SheetClass, sheet: Sheet) {
self.order = order
self.sheet = sheet
}
}
How can I create an entity that would be capable of storing the above?
One simple way would be to have an entity that holds only one Data field (Binary Data in xcdatamodel settings), which would be the orderSheet itself.
Before going with this solution, I'd like to mention that, one down side of this approach is; if later in the future, any of the models inside OrderSheet changes, you won't be able to retrieve already stored objects as conversion will fail. One way of overcoming this issue would be declaring everything inside OrderSheet and sub models as Optional. But if it is not so crucial, meaning, if not being able to read old models on user's device after an app update is okay, (maybe they will be replaced with new networking call) then you can go with not marking properties as optional either.
Lets imagine you create an entity named OrderSheetManaged with one field as I mentioned like following:
import Foundation
import CoreData
#objc(Entity)
public class OrderSheetManaged: NSManagedObject {
}
extension OrderSheetManaged {
#nonobjc public class func fetchRequest() -> NSFetchRequest<OrderSheetManaged> {
return NSFetchRequest<OrderSheetManaged>(entityName: "OrderSheetManaged")
}
#NSManaged public var orderSheet: Data?
}
I will write some code for NSManagedObjectContext, which is not directly related to your question, you should make research on how to initialise a core data stack and a managed context from it if you are not familiar with that since it is crucial.
I also do some force unwrapping for simplicity, make sure to not force unwrap where not needed in production code.
Now whenever you have an actual OrderSheet object (it is orderSheet in my example below), that was parsed before, you are going to convert it to Data and persist it with new Core Data model as following:
// unrelated to question, it should already be initialised from core data stack, I just init with
// concurrency type to make compiler happy, dont do this before further research.
let yourManagedContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let entityDescription = NSEntityDescription.entity(forEntityName: "OrderSheetManaged",
in: yourManagedContext)
let dataForCoreData = try! JSONEncoder().encode(orderSheet)
let managedOrderSheet = NSManagedObject(entity: entityDescription!, insertInto: yourManagedContext)
managedOrderSheet.setValue(dataForCoreData, forKey: "orderSheet")
Now we have persisted your object as Data inside a wrapper core data model (OrderSheetManaged)
Let's see now how we can fetch these models from our core data and convert it back to OrderSheet model:
// when you fetch it
var orderSheets = [OrderSheet]()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "OrderSheetManaged")
var coreDataObjects: [NSManagedObject]!
do {
coreDataObjects = try yourManagedContext.fetch(request) as? [NSManagedObject]
for coreDataObject in coreDataObjects {
if let orderSheetData = coreDataObject.value(forKey: "orderSheet") as? Data {
let orderSheet = try! JSONDecoder().decode(OrderSheet.self, from: orderSheetData)
orderSheets.append(orderSheet)
}
}
} catch {
error
}
Now you will have all your stored order sheets inside orderSheets array.
You can also write some utility methods to easily modify core data models by converting orderSheet data inside of them to OrderSheet first and then again converting it back to Data after modifying and then persisting again with setValue.

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
}
}

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!