Two same parents in one entity - swift

I making chat backend and I need messages history table which will contain two users.
Is it some way to do something like that right way?
static func prepare(_ database: Database) throws {
try database.create("historys") { history in
history.id()
history.parent(User.self, optional: false)
history.parent(User.self, optional: false)
}
}
Now I'm getting an error of multiple user_id fields.

It should really be possible to set the field name in your preparation; this would be a useful enhancement.
In the meantime, though, you can get the same effect by creating an int field.
static func prepare(_ database: Database) throws {
try database.create("historys") { history in
history.id()
history.int("sender_user_id", optional: false)
history.int("recipient_user_id", optional: false)
}
}
In your model, you'll have properties senderUserId: Node and recipientUserId: Node, and you'll initialise them as e.g. senderUserId = try Node.extract("sender_user_id").
You can then fetch each relation using the following convenience methods on the model:
func sender() throws -> Parent<User> {
return try parent(senderUserId)
}
func recipient() throws -> Parent<User> {
return try parent(recipientUserId)
}

Related

How do I call a new database connection from within a read-only database connection in GRDB?

The function below returns a Ledgers record. Most of the time, it will find it in the optional _currentReceipt variable, or by searching the database, No writing needed there. I'd like to use a read-only GRDB database connection. Read-only database connections can run in parallel on different threads.
In the rare case, the first two steps fail, I can create a default Ledger. Calling try FoodyDataStack.thisDataStack.dbPool.write { writeDB in ... will throw a fatal error, Database connections are not reentrant. I'm looking for a way to save that default Ledger without having to wrap the whole function in a read-write connection.
Can I call an NSOperation on a separate queue from within a GRDB .read block?
class func getCurrentReceipt(db: Database) throws -> Ledgers {
if let cr = FoodyDataStack.thisDataStack._currentReceipt {
return cr
}
// Fall through
do {
if let cr = try Ledgers.filter(Ledgers.Columns.receiptClosed == ReceiptStatus.receiptOpen.rawValue).order(Ledgers.Columns.dateModified.desc).fetchOne(db) {
FoodyDataStack.thisDataStack._currentReceipt = cr
return cr
} else {
throw FoodyDataStack.myGRDBerrors.couldNotFindCurrentReceipt
}
} catch FoodyDataStack.myGRDBerrors.couldNotFindCurrentReceipt {
// Create new receipt with default store
let newReceipt = Ledgers()
newReceipt.dateCreated = Date()
newReceipt.dateModified = Date()
newReceipt.receiptStatus = .receiptOpen
newReceipt.receiptUrgency = .immediate
newReceipt.dateLedger = Date()
newReceipt.uuidStore = Stores.defaultStore(db).uuidKey
FoodyDataStack.thisDataStack._currentReceipt = newReceipt
return newReceipt
} catch {
NSLog("WARNING: Unhandled error in Ledgers.getCurrentReceipt() \(error.localizedDescription)")
}
}
Edit: I'm leaving this question here, but I think I may be going for premature optimization. I'm going to try dbQueue instead of dbPool and see what the performance is. I'll be back to dbPool if speed requires it.
GRDB database access methods are not reentrant (The read and write methods of DatabaseQueue and DatabasePool).
To help you solve your issue, try to split your database access methods in two levels.
The first level is not exposed to the rest of the application. Its methods all take a db: Database argument.
class MyStack {
private var dbQueue: DatabaseQueue
private func fetchFoo(_ db: Database, id: Int64) throws -> Foo? {
return try Foo.fetchOne(db, key: id)
}
private func setBar(_ db: Database, foo: Foo) throws {
try foo.updateChanges(db) {
$0.bar = true
}
}
}
Methods from the second level are exposed to the rest of the application. They wrap first-level methods in read and write database access methods:
class MyStack {
func fetchFoo(id: Int64) throws -> Foo? {
return try dbQueue.read { db in
try fetchFoo(db, id: id)
}
}
func setBar(id: Int64) throws {
try dbQueue.write { db in
guard let foo = try fetchFoo(db) else {
throw fooNotFound
}
try setBar(foo: foo)
}
}
}
Methods of the first level can be as low-level as needed, and can be composed.
Methods of the second level are high level, and are not composable: they can't call each other because of the "Database connections are not reentrant" fatal error. They provide the thread-safe database methods that guarantee database consistency.
See the Concurrency Guide for more information.

Understanding Migrations in Vapor-Fluent (Server side Swift)

I'm writing a web service in Swift using Vapor framework.
I have model named Item. Intially it has only name and id properties.
typealias VaporModel = Content & PostgreSQLModel & Parameter
final class Item: VaporModel {
var id: Int?
var name: String
}
After I configure a controller for the model and add the routes, when I hit the post Item request, I get the error as Model.defaultDatabase is required to use as DatabaseConnectable. I think the error is because I have not added Item to Migrations in configure.swift and I do the same after conforming Item to PostgreSQLMigration.
var migrations = MigrationConfig()
migrations.add(model: Item.self, database: .psql)
services.register(migrations)
Now, I am able to hit the post request and create items in the database.
So I understand that Migration protocol creates the default schema for a model and adds a new table to the database with the model's properties as columns.
Now I want to add a property such as price to my Item class. Now when I hit the post request, I get the error as column "price" of relation "Item" does not exist.
I assume the Migration protocol will be able to identify the schema changes and the column to my table (that's what I was used to in while using Realm for my iOS apps). But I am wrong and I read through the Migration docs and implement the prepare and revert methods in migration like below.
extension Item: PostgreSQLMigration {
static func prepare(on conn: PostgreSQLConnection) -> Future<Void> {
return Database.create(self, on: conn) { creator in
creator.field(for: \.price)
}
}
static func revert(on connection: PostgreSQLConnection) -> EventLoopFuture<Void> {
return Future.map(on: connection) { }
}
}
I'm still struck with the same error column "price" of relation "Item" does not exist. What am I missing here? Is my migration code correct?
Also, I understand that if am not making any changes to the Model, I can comment out the migration config, because they need not run every time I run the service. Is that correct?
With your code you haven't added a new migration. You have implemented a manual initial migration, but the initial migration has run already as requested (migrations.add(model: Item.self, database: .psql). To create a new migration you would need sth like:
struct ItemAddPriceMigration: Migration {
typealias Database = PostgreSQLDatabase
static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return Database.update(Item.self, on: conn) { builder in
builder.field(for: \.price)
}
}
static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return conn.future()
}
}
And then you need to add it in configure:
migrations.add(migration: ItemAddPriceMigration.self, database: .psql)

CoreData - Only fetch specific child managed Objects using a generic method

I have a strange problem:
I have a NSManagedObject called ItemTemplate. It has many child managed objects, e.g. CustomItemTemplate or SpecialItemTemplate.
Now I have a list viewController that is supposed to show these child managed objects. For example only "CustomItemTemplaze" or only "SpecialItemTemplate". I wrote this generic method to fetch all ItemTemplates and filter out the desired child-objects (I haven't found a better way yet).
private func loadTemplates<T: ItemTemplate>(ofType type: T.Type) -> [ModelObject] {
// ModelObject is just a model for my managed objects
var templates = [ModelObject]()
do {
let request: NSFetchRequest<ItemTemplate> = ItemTemplate.fetchRequest()
let result = try mainViewContext.fetch(request)
for item in result {
if item is T { // this is somehow always true
templates.append(item.modelObject) // add the converted item to the array
}
}
} catch let error as NSError {
print("Error: ", error.debugDescription)
}
return templates
}
I call it like this:
enum Category {
case custom
case special
public var templateClass: ItemTemplate.Type {
switch self {
case .custom:
return CustomItemTemplate.self
case .special:
return SpecialItemTemplate.self
}
}
}
loadTemplates(ofType: currentCategory.templateClass)
However, it's not filtering. if item is T seems to be true for every item. It thus returns every ItemTemplate, instead of only certain child objects.
Why is that? I can't explain it.
Thanks for any help!

Swift - Get the parent of a Realm object; Always empty

I've got a relationship where:
A Parent has many Children
ie:
class Factory: Object {
public let engines = List<Engine>()
}
class Engine:Object {
private let parents:LinkingObjects<Factory> = LinkingObjects(fromType: Factory.self, property: "engines")
var parent:Factory? {
return self.parents.first
}
}
I read the factories via JSON and create the children (Engine) manually in a for-loop, similar to this:
var engines:[Engine] = [Engine]()
for _ in stride(from:0, to: 3, by: 1) {
let engine: Engine = Engine.init()
engines.append(engine)
}
return engines
In my test I want to query the parent of a given engine to ensure that the parent is correct; or perhaps get a parent attribute.
However, whenever I try to grab an attribute via the parent its always empty;
for (_, element) in (factories.enumerated()) {
for (_, eng) in element.engines.enumerated() {
print (eng.parent ?? "N/A" as Any) // Always prints out N/A
}
}
Ideally I want to be able to access the parent's data; like the name of the parent, perhaps costs, etc.
I've tried resetting simulator and also deleting derived data; but regardless of what I do the results are always N/A or empty.
How can I query the given element and ensure that I can grab the parent data?
Many thanks
Turns out there were a number of issues that I had to do to resolve this.
I was using XCTest and Realm was causing issues where there were multiple targets.
Make all my model classes' public
Remove the models from the test target, this included a file where the JSON data was being loaded into memory
I had to write my data into Realm, which I had not done;
let realm = try! Realm()
try! realm.write {
for parent:EYLocomotive in objects {
for _ in stride(from:0, to: parent.qty, by: 1) {
let engine : EYEngine = EYEngine.init()
parent.engines.append(engine)
}
realm.add(parent)
}
}

How to fake Realm Results for tests

I have written a test to validate if a function is called :
func test_getTaskLists_doNotCreateOrUpdateTaskListToStorageWhenSynchedLocally() {
...
let (datasource, restAPI, fakeTaskListStorage) = ...
datasource.getTaskLists() { (taskLists, error) -> Void in
...
XCTAssertEqual(1, fakeTaskListStorage.readAllInvocationCount)
...
}
...
}
The function is mocked to bypass super implementation and the issue is that the function returns a Results which I can't figure out to build/mock in order to return a valid object so the compiler stops complaining...I know I could just call super.readAll() but here I actually want to convert my test data (fakeTaskLists) to a fake Result object so everyone is happy...not sure if thats possible
class FakeTaskListsStorageRealm : TaskListStorageRealm {
var fakeTaskLists:[TaskList]?
override func readAll() -> RealmSwift.Results<TaskList> {
readAllInvocationCount += 1
//Here I want to return fakeTaskLists somehow...
}
}
There is no way to instantiate Results directly. Subclassing Results doesn't allow too. I think the best way is hiding Results by protocol like ResultsWrapper rather than using Results directly.
But an easy workaround is using in-memory Realm when testing.
The FakeTaskListsStorageRealm's readAll() can be written using in-memory Realm as follows:
class FakeTaskListsStorageRealm : TaskListStorageRealm {
var fakeTaskLists:[TaskList]?
override func readAll() -> RealmSwift.Results<TaskList> {
readAllInvocationCount += 1
return try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "test")).objects(TaskList.self)
}
}