Last active
September 6, 2019 14:09
-
-
Save calebkleveter/c4f7018eaa9565ef5957fbf6bd9dda59 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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