Skip to content

Commit

Permalink
Make RelationshipManipulation work on top of KVC/StoredKVC
Browse files Browse the repository at this point in the history
The AR was directly peeking into its own value storage,
doesn't work if a subclass overrides KVC.
Also implement regular-KVC based manipulations in the
default implementation.
  • Loading branch information
helje5 committed Nov 10, 2024
1 parent 7542c02 commit cf2a6ea
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 15 deletions.
26 changes: 15 additions & 11 deletions Sources/ZeeQL/Access/ActiveRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ZeeQL
//
// Created by Helge Hess on 26/02/2017.
// Copyright © 2017-2019 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
Expand Down Expand Up @@ -123,11 +123,16 @@ open class ActiveRecordBase : ActiveRecordType, SmartDescription {


// MARK: - KVC
// TBD: the KVC and StoredKVC are very similar, but not the same? They should
// be the same as they perform no extra work here?

open func takeValue(_ value: Any?, forKey k: String) throws {
// Note: `values` itself is a [String:Any?], so all that may be superfluous.
willChange()

// Note: There is no default setter! This is why this always pushes into
// the dictionary!
// TODO: Since the stable v5 ABI we *can* do KVC setters, implement them!
if let value = value {
values[k] = value // values is wrapped again in an Optional<Any>
}
Expand Down Expand Up @@ -168,43 +173,42 @@ open class ActiveRecordBase : ActiveRecordType, SmartDescription {

public func addObject(_ object: AnyObject, toPropertyWithKey key: String) {
// TBD: this is, sigh.
// also, the KVC access is still a little open, this should do
// takeValueForKey in case the subclass overrides it

willChange()

// If it is a to-one, we push the object itself into the relship.
if let relship = entity[relationship: key], !relship.isToMany {
values[key] = object
takeStoredValue(object, forKey: key)
return
}

if var list = values[key] as? [ AnyObject ] {
// TBD: Really AnyObject? Rather `DatabaseObject`?
// Because the input is like that!
if var list = storedValue(forKey: key) as? [ AnyObject ] {
list.append(object)
values[key] = list
takeStoredValue(list, forKey: key)
}
else {
values[key] = [ object ]
takeStoredValue([ object ], forKey: key)
}
}
public func removeObject(_ o: AnyObject, fromPropertyWithKey key: String) {
willChange()

// If it is a to-one, we clear the object itself into the relship.
if let relship = entity[relationship: key], !relship.isToMany {
values.removeValue(forKey: key)
takeStoredValue(nil, forKey: key)
return
}

guard var list = values[key] as? [ AnyObject ] else { return }
guard var list = storedValue(forKey: key) as? [ AnyObject ] else { return }
#if swift(>=5)
guard let idx = list.firstIndex(where: { $0 === o }) else { return }
#else
guard let idx = list.index(where: { $0 === o }) else { return }
#endif

list.remove(at: idx)
values[key] = [ list ]
takeStoredValue([ list ], forKey: key)
}


Expand Down
38 changes: 34 additions & 4 deletions Sources/ZeeQL/Access/DatabaseObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// ZeeQL
//
// Created by Helge Hess on 26/02/2017.
// Copyright © 2017-2021 ZeeZide GmbH. All rights reserved.
// Copyright © 2017-2024 ZeeZide GmbH. All rights reserved.
//

/**
Expand Down Expand Up @@ -171,7 +171,9 @@ public enum DatabaseObjectError : Swift.Error {
*
* Special KVC functions for toMany keys in ORM objects.
*/
public protocol RelationshipManipulation : AnyObject, KeyValueCodingType {
public protocol RelationshipManipulation
: AnyObject, KeyValueCodingType, MutableKeyValueCodingType
{

/**
* Add an object to the array stored under '_key'.
Expand All @@ -194,8 +196,36 @@ public protocol RelationshipManipulation : AnyObject, KeyValueCodingType {
public extension RelationshipManipulation { // default imp

func addObject(_ object: AnyObject, toPropertyWithKey key: String) {
// TODO
fatalError("not implemented: \(#function)")
// TBD: this is, sigh.
// also, the KVC access is still a little open, this should do
// takeValueForKey in case the subclass overrides it
let log = globalZeeQLLogger

// If it is a to-one, we push the object itself into the relship.
if let object = object as? DatabaseObject {
do {
try takeValue(object, forKey: key)
}
catch {
log.error("Could not take toOne relationship for key:", key)
}
return
}

// TBD: Really AnyObject? Rather `DatabaseObject`?
// Because the input is like that!
do {
if var list = value(forKey: key) as? [ AnyObject ] {
list.append(object)
try takeValue(list, forKey: key)
}
else {
try takeValue([ object ], forKey: key)
}
}
catch {
log.error("Could not take toMany relationship for key:", key)
}
}
func removeObject(_ object: AnyObject, fromPropertyWithKey key: String) {
// TODO
Expand Down

0 comments on commit cf2a6ea

Please sign in to comment.