Skip to content

Instantly share code, notes, and snippets.

@calebkleveter
Last active September 6, 2019 14:09
Show Gist options
  • Save calebkleveter/c4f7018eaa9565ef5957fbf6bd9dda59 to your computer and use it in GitHub Desktop.
Save calebkleveter/c4f7018eaa9565ef5957fbf6bd9dda59 to your computer and use it in GitHub Desktop.
@propertyWrapper
public final class Parent<To>: AnyField, AnyEagerLoadable where To: ModelIdentifiable {
@Field public var id: To.IDValue
private var implementation: AnyField & AnyEagerLoadable
var key: String { return self.implementation.key }
var inputValue: DatabaseQuery.Value? {
get { self.implementation.inputValue }
set { self.implementation.inputValue = newValue }
}
public var wrappedValue: To {
get {
guard let value = self.eagerLoadedValue else { // <= Value of type 'Parent<To>' has no member 'eagerLoadedValue'
fatalError("Parent relation not eager loaded, use $ prefix to access")
}
return value
}
set { fatalError("Use $ prefix to access") }
}
public var projectedValue: Parent<To> { self }
private init(id: Field<To.IDValue>, implementation: AnyField & AnyEagerLoadable) {
self._id = id
self.implementation = implementation
}
// MARK: - Override
func output(from output: DatabaseOutput) throws {
try self.implementation.output(from: output)
}
func encode(to encoder: Encoder) throws {
try self.implementation.encode(to: encoder)
}
func decode(from decoder: Decoder) throws {
try self.implementation.decode(from: decoder)
}
func eagerLoad(from eagerLoads: EagerLoads, label: String) throws {
try self.implementation.eagerLoad(from: eagerLoads, label: label)
}
func eagerLoad(to eagerLoads: EagerLoads, method: EagerLoadMethod, label: String) {
self.implementation.eagerLoad(to: eagerLoads, method: method, label: label)
}
}
// MARK: - Required
extension Parent where To: Model {
public convenience init(key: String) {
let field = Field<To.IDValue>(key: key)
self.init(id: field, implementation: Required(_id: field))
}
public func eagerLoaded() throws -> To {
guard let eagerLoaded = (self.implementation as? Required)?.eagerLoadedValue else {
throw FluentError.missingEagerLoad(name: To.schema)
}
return eagerLoaded
}
public func query(on database: Database) -> QueryBuilder<To> {
return To.query(on: database).filter(self.key, .equal, self.id)
}
public func get(on database: Database) -> EventLoopFuture<To> {
return self.query(on: database).first().flatMapThrowing { parent in
guard let parent = parent else { throw FluentError.missingParent }
return parent
}
}
private final class Required: AnyField & AnyEagerLoadable {
@Field var id: To.IDValue
internal var eagerLoadedValue: To?
var key: String { self._id.key }
var schema: String { To.schema }
var inputValue: DatabaseQuery.Value? {
get { self.$id.inputValue }
set { self.$id.inputValue = newValue }
}
init(_id: Field<To.IDValue>) {
self._id = _id
self.eagerLoadedValue = nil
}
func eagerLoad(from eagerLoads: EagerLoads, label: String) throws {
guard let request = eagerLoads.requests[label] else { return }
if let join = request as? JoinEagerLoad {
self.eagerLoadedValue = try join.get(id: self.id)
} else if let subquery = request as? SubqueryEagerLoad {
self.eagerLoadedValue = try subquery.get(id: self.id)
} else {
fatalError("unsupported eagerload request: \(request)")
}
}
func eagerLoad(to eagerLoads: EagerLoads, method: EagerLoadMethod, label: String) {
switch method {
case .subquery:
eagerLoads.requests[label] = SubqueryEagerLoad(key: self.key)
case .join:
eagerLoads.requests[label] = JoinEagerLoad(key: self.key)
}
}
func output(from output: DatabaseOutput) throws {
try self.$id.output(from: output)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let parent = self.eagerLoadedValue {
try container.encode(parent)
} else {
try container.encode([ To.key(for: \._$id): self.id ])
}
}
func decode(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: _ModelCodingKey.self)
try self.$id.decode(from: container.superDecoder(forKey: .string(To.key(for: \._$id))))
// TODO: allow for nested decoding
}
}
}
// MARK: - Optional
extension Parent where To: OptionalType & Encodable, To.Wrapped: Model {
public convenience init(key: String) {
let field = Field<To.IDValue>(key: key)
self.init(id: field, implementation: Optional(_id: field))
}
public func eagerLoaded() throws -> To {
guard let eagerLoaded = (self.implementation as? Optional)?.eagerLoadedValue else {
throw FluentError.missingEagerLoad(name: To.Wrapped.schema)
}
return eagerLoaded
}
public func query(on database: Database) -> QueryBuilder<To.Wrapped> {
return To.Wrapped.query(on: database).filter(self.key, .equal, self.id)
}
public func get(on database: Database) -> EventLoopFuture<To> {
return self.query(on: database).first().map { $0 as! To }
}
private final class Optional: AnyField, AnyEagerLoadable {
@Field var id: To.IDValue
internal var eagerLoadedValue: To?
var key: String { self._id.key }
var schema: String { To.Wrapped.schema }
var anyEagerLoadedValue: Any? {
get { self.eagerLoadedValue }
set { self.eagerLoadedValue = newValue as! To? }
}
var inputValue: DatabaseQuery.Value? {
get { self.$id.inputValue }
set { self.$id.inputValue = newValue }
}
init(_id: Field<To.IDValue>) {
self._id = _id
self.eagerLoadedValue = nil
}
func eagerLoad(from eagerLoads: EagerLoads, label: String) throws {
guard let request = eagerLoads.requests[label] else { return }
if let join = request as? JoinEagerLoad {
self.eagerLoadedValue = try join.get(id: self.id)
} else if let subquery = request as? SubqueryEagerLoad {
self.eagerLoadedValue = try subquery.get(id: self.id)
} else {
fatalError("unsupported eagerload request: \(request)")
}
}
func eagerLoad(to eagerLoads: EagerLoads, method: EagerLoadMethod, label: String) {
switch method {
case .subquery:
eagerLoads.requests[label] = SubqueryEagerLoad(key: self.key)
case .join:
eagerLoads.requests[label] = JoinEagerLoad(key: self.key)
}
}
func output(from output: DatabaseOutput) throws {
try self.$id.output(from: output)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let parent = self.eagerLoadedValue {
try container.encode(parent)
} else {
try container.encode([ To.Wrapped.key(for: \._$id): self.id ])
}
}
func decode(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: _ModelCodingKey.self)
try self.$id.decode(from: container.superDecoder(forKey: .string(To.Wrapped.key(for: \._$id))))
// TODO: allow for nested decoding
}
}
}
// MARK: - Optional Model Identifiable
extension Optional: ModelIdentifiable where Wrapped: ModelIdentifiable {
public typealias IDValue = Wrapped.IDValue?
public var id: Wrapped.IDValue?? {
get { self?.id }
set { self?.id = newValue ?? nil }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment