Vapor Swift - Obtaining data from two Models - swift

I have the next function on Vapor:
func getPartidosHandler(_ req: Request) throws -> Future<[PartidoWSData]> {
return Partido.query(on: req).filter(\.estado == nil).all().map(to: [PartidoWSData].self) { partidos in
var partidosWS: [PartidoWSData] = []
for partido in partidos {
// Something here
}
return partidosWS
}
}
And the next struct PartidoWSData:
struct PartidoWSData: Content {
let idPartido: String
let fecha: String
let sede1: Future<Sede>
let sede2: Future<Sede>
}
My model Partido has two references to Sede, "sede1" and "sede2".
What I want is that the function gives an array of PartidoWSData struct, where I can see two properties of "Partido", "idPartido" and "fecha", and the two Sede related to the model.
How can I do that?
Thanks!

I'm not sure exactly what type of relation exists between Partido and Sedebecause the model wasn't included here, but assuming it's a Parent/Child relation, you should be able to do something like:
func getPartidosHandler(_ req: Request) throws -> Future<[PartidoWSData]> {
return Partido.query(on: req).filter(\.estado == nil).all().flatMap { partidos -> Future<[PartidoWSData]> in
let partidoIDs = try partidos.map { try $0.requireID() }
return Sede.query(on: req).filter(\.partidoID ~~ partidoIDs).map { sedes -> [PartidoWSData] in
return partidos.map { partido -> PartidoWSData in
return PartidoWSData(
id: partido.id
sedes: sedes.filter { $0.partidoID == partido.id }
)
}
}
}
}
The key is using the ~~ operator to do an x IN (...) predicate, following by using Array.filter to get the appropriate results.

Related

How to recursively iterate over Swift Syntax with SwiftSyntax library?

I would like to iterate in my code over the Swift AST like this, finding the struct keyword.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let tokenKind = (child as? TokenSyntax)?.tokenKind, tokenKind == .structKeyword {
// if type(of: child) == StructDeclSyntax.self {
print ("yeah")
}
recursion(node: child)
}
}
let input = """
public struct cmd_deleteEdge<E: VEdge> : EdgeCommand {
public var keyEquivalent = KeyEquivalent.none
public let title = "Delete Edge"
public let id = "deleteEdge"
public let toolTip = "Delete selected Edge"
public let icon = Icon.delete
//receivers
public let edge: E
public init(edge: E) {
self.edge = edge
}
public func execute() throws -> ActionResult {
edge.deleteYourself()
return .success("deleted edge")
}
}
"""
public func convert(structText: String) throws -> String {
let sourceFile = try SyntaxParser.parse(source: structText)
let result = recursion(node: Syntax(sourceFile))
return result
}
try convert(structText: input)
It just simply doesn't work, I never reach the "Yeah" (which means I cannot do anything useful during the recursion).
I find this library very confusing. Would anyone have a good UML diagram explaining how it really works?
Before you tell me, yes I know I could use a Visitor, but I want to understand how it works by myself.
You can use SyntaxProtocol for iterating all items in AST and then use its _syntaxNode public property to make a target syntax, e.g.:
import SwiftSyntax
import SwiftSyntaxParser
func recursion(node: SyntaxProtocol) {
if let decl = StructDeclSyntax(node._syntaxNode) {
print(decl.identifier)
}
node.children.forEach { recursion(node: $0) }
}
let code = """
struct A {}
class Some {
struct B {}
}
func foo() {
struct C {}
}
"""
let sourceFile = try SyntaxParser.parse(source: code)
recursion(node: sourceFile)
Outputs:
A
B
C
NOTE: It is not recommended to retrieve _syntaxNode property directly and you can use Syntax(fromProtocol: node) instead.
SyntaxVisitor
But the best approach is using Visitor pattern with SyntaxVisitor class to avoid recursion issues for large and complex files:
class Visitor: SyntaxVisitor {
var structs = [StructDeclSyntax]()
init(source: String) throws {
super.init()
let sourceFile = try SyntaxParser.parse(source: source)
walk(sourceFile)
}
// MARK: - SyntaxVisitor
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
structs.append(node)
return .skipChildren
}
}
let visitor = try Visitor(source: code)
visitor.structs.forEach {
print($0.identifier)
}
I found it after trial & error and reviewing of the API.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let token = TokenSyntax(child), token.tokenKind == .structKeyword {
print ("yeah")
}
recursion(node: child)
}
return node.description
}
This approach to identify the kind of the token works, and the print statement will be reached. Again, I do wonder how the class diagram for SwiftSyntax would look like.

How to always return an array in Vapor 3 and Fluent (even for single entity requests)

I'd like to have an index controller function that returns an array of entities if no request parameter is set or a single entity if the id parameter is set. However, I'd like to always receive an array, in the latter case it just contains only one element.
Here's my function:
final class AddressController {
func index(_ req: Request) throws -> Future<[Address]> {
if let id = try? req.query.get(UUID.self, at: "id") {
// THIS IS NOT WORKING...
return Address.find(id, on: req)
} else {
return Address.query(on: req).all()
}
}
}
final class AddressController {
func index(_ req: Request) throws -> Future<[Address]> {
if let id = try? req.query.get(UUID.self, at: "id") {
return Address.find(id, on: req).map {
guard let address = $0 else { return [] }
return [address]
}
} else {
return Address.query(on: req).all()
}
}
}

Convert [Future<Object,Error>] to Future<[Object],Error>

Is there any way I can parse through the code below and return the value of it as an array Future<[Object],Error> . I'm using the BrightFutures future implementation
return apiService.getArrayObject()
.flatMap(NetworkQueue.context) { (arrayObjects: [ArrayObject]) -> Future<[Object], Error> in
let objects = arrayObjects.map {
apiService.getObject(of: $0.objectId)
}
return objects // ERROR since objects is [Future<Object,Error>] rather than needed output
}
Use sequence
return apiService.getArrayObject()
.flatMap(NetworkQueue.context) { (arrayObjects: [ArrayObject]) -> Future<[Object], Error> in
let objects: [Future<Object, Error>] = arrayObjects.map {
apiService.getObject(of: $0.objectId)
}
let sequenceFuture: Future<[Object], NoError> = objects
.sequence()
return sequenceFuture
}

Subscribing to fetch a nested array

I have an object and its properties as following:
class Section {
var cards: [MemberCard]
init(card: [MemberCard]) {
}
}
class MemberCard {
var name: String
var address: String?
init(name: String) {
self.name = name
}
}
I'm subscribing to a RxStream of type Observable<[Section]>. Before I subscribe I would to want flat map this function.
where the flat map would perform the following actions:
let sectionsStream : Observable<[Section]> = Observable.just([sections])
sectionsStream
.flatMap { [weak self] (sections) -> Observable<[Section]> in
for section in sections {
for card in section.cards {
}
}
}.subscribe(onNext: { [weak self] (sections) in
self?.updateUI(memberSections: sections)
}).disposed(by: disposeBag)
func getAddressFromCache(card: MemberCard) -> Observable<MemberCard> {
return Cache(id: card.name).flatMap ({ (card) -> Observable<MemberCard> in
asyncCall{
return Observable.just(card)
}
}
}
How would the flatmap look like when it comes to converting Observable<[Section]> to array of [Observable<MemberCard>] and back to Observable<[Section]>?
Technically, like that -
let o1: Observable<MemberCard> = ...
let o2: Observable<Section> = omc.toList().map { Section($0) }
let o2: Observable<[Section]> = Observable.concat(o2 /* and all others */).toList()
But I do not think it is an optimal solution, at least because there is no error handling for the case when one or more cards cannot be retrieved. I would rather build something around aggregation with .scan() operator as in https://github.com/maxvol/RaspSwift
Here you go:
extension ObservableType where E == [Section] {
func addressedCards() -> Observable<[Section]> {
return flatMap {
Observable.combineLatest($0.map { getAddresses($0.cards) })
}
.map {
$0.map { Section(cards: $0) }
}
}
}
func getAddresses(_ cards: [MemberCard]) -> Observable<[MemberCard]> {
return Observable.combineLatest(cards
.map {
getAddressFromCache(card: $0)
.catchErrorJustReturn($0)
})
}
If one of the caches emits an error, the above will return the MemberCard unchanged.
I have a couple of other tips as well.
In keeping with the functional nature of Rx, your Section and MemberCard types should either be structs or (classes with lets instead of vars).
Don't use String? unless you have a compelling reason why an empty string ("") is different than a missing string (nil). There's no reason why you should have to check existence and isEmpty every time you want to see if the address has been filled in. (The same goes for arrays and Dictionaries.)
For this code, proper use of combineLatest is the key. It can turn an [Observable<T>] into an Observable<[T]>. Learn other interesting ways of combining Observables here: https://medium.com/#danielt1263/recipes-for-combining-observables-in-rxswift-ec4f8157265f

RxSwift - Concat only on condition

I have 2 stream which are being concatenated. If the first stream executes onError instead of onComplete, I shouldn't be concatenating the second stream.
example:
func updateEntity(entities: [Member]) -> Observable<[Result<Member>]> {
let remoteUpdate = remoteStore.update(entities: entities)
return remoteUpdate.concat(localStore.update(entities: entities))
}
I shouldn't be updating the localStore if remoteUpdate throws an error, onError is called in remoteUpdate
Update:
public override func update(entities: [PlaceAlert]) -> Observable<[Result<PlaceAlert>]> {
let remoteUpdate = remoteStore.update(entities: entities)
var entityPlaceHolder: [PlaceAlert] = entities
return remoteUpdate.catchError { _ in
entityPlaceHolder = []
return localStore.update(entities: entityPlaceHolder)
}.concat(localStore.update(entities: entityPlaceHolder))
}
Just tried improvising. would this make any difference? LocalUpdate with emptyArray does nothing if there is an error
concat is not an if/then statement. Both updates are getting called at the same scope, it's just that the result won't reflect the localStore.update response if the remoteStore errors.
catchError is what you want in this context:
func updateEntity(entities: [Member]) -> Observable<[Result<Member>]> {
let remoteUpdate = remoteStore.update(entities: entities)
return remoteUpdate.catchError { _ in
localStore.update(entities: entities)
}
}