Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Keypath Based Joins in Swift
//: Playground - noun: a place where people can play
import Cocoa
struct IssueAssignee {
let issueUid: String
let assigneeUid: String
}
struct IssueFollower {
let issueUid: String
let followerUid: String
}
struct IssueModel {
let uid: String
let title: String
var assignees: [IssueAssignee] = []
var followers: [IssueFollower] = []
static let joins: [AnyRelationship<IssueModel>] = [
OneToMany(\IssueModel.assignees, \IssueAssignee.issueUid, \IssueModel.uid),
OneToMany(\IssueModel.followers, \IssueFollower.issueUid, \IssueModel.uid)
]
}
class AnyRelationship<Base> {
func query(withModel model: inout Base) {
fatalError("Needs to be overriden!")
}
}
final class OneToMany<One, Many, SharedKey: Equatable>: AnyRelationship<One> {
let oneKeyPath: WritableKeyPath<One, [Many]>
let manyKeyPath: KeyPath<Many, SharedKey>
let joinKeyPath: KeyPath<One, SharedKey>
init(_ oneKeyPath: WritableKeyPath<One, [Many]>, _ manyKeyPath: KeyPath<Many, SharedKey>, _ joinKeyPath: KeyPath<One, SharedKey>) {
self.oneKeyPath = oneKeyPath
self.manyKeyPath = manyKeyPath
self.joinKeyPath = joinKeyPath
}
override func query(withModel model: inout One) {
// Force casts here simulate generic storage that allows us to get values by metatype.
// For this playground we only support hardcoded `IssueAssignee` type, but that doesn't change
// anything about overall API.
if Many.self == IssueAssignee.self {
model[keyPath: self.oneKeyPath] = assignees.filter { ($0 as! Many)[keyPath: self.manyKeyPath] == model[keyPath: self.joinKeyPath] } as! [Many]
} else if Many.self == IssueFollower.self {
model[keyPath: self.oneKeyPath] = followers.filter { ($0 as! Many)[keyPath: self.manyKeyPath] == model[keyPath: self.joinKeyPath] } as! [Many]
} else {
fatalError()
}
}
}
var issue = IssueModel(uid: "1", title: "ABC", assignees: [], followers: [])
let assignees = [
IssueAssignee(issueUid: "1", assigneeUid: "Assigned to Issue 1"),
IssueAssignee(issueUid: "2", assigneeUid: "Assigned to Issue 2"),
IssueAssignee(issueUid: "1", assigneeUid: "Also assigned to issue 1!!!!")
]
let followers = [
IssueFollower(issueUid: "1", followerUid: "Following Issue 1"),
IssueFollower(issueUid: "2", followerUid: "Following Issue 2"),
IssueFollower(issueUid: "1", followerUid: "Also following issue 1!!!!")
]
for join in IssueModel.joins {
join.query(withModel: &issue)
}
print(issue)
// IssueModel(uid: "1", title: "ABC", assignees: [__lldb_expr_1.IssueAssignee(issueUid: "1", assigneeUid: "Assigned to Issue 1"), __lldb_expr_1.IssueAssignee(issueUid: "1", assigneeUid: "Also assigned to issue 1!!!!")], followers: [__lldb_expr_1.IssueFollower(issueUid: "1", followerUid: "Following Issue 1"), __lldb_expr_1.IssueFollower(issueUid: "1", followerUid: "Also following issue 1!!!!")])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment