Complex query using Fluent in Vapor 4 - vapor

I want to query chats, that contain certain user in siblings field.
Simplified models:
final class Chat: Model, Content {
#Siblings(through: ChatUser.self, from: \.$chat, to: \.$user)
var users: [User]
}
final class User: Model, Content {
#Siblings(through: ChatUser.self, from: \.$user, to: \.$chat)
var chats: [Chat]
}
My fetch route:
func fetch(req: Request) throws -> EventLoopFuture<[Chat]> {
let user = try req.auth.require(User.self)
return Chat
.query(on: req.db)
.filter("users", .contains(inverse: false, .anywhere), user)
.all()
}
I get [ ERROR ] server: column chats.users does not exist (errorMissingColumn)
How can I do such filters in a right way?

If I have understood your requirement correctly, you should be starting with the sibling relationship from the User end, not Chat. Your query then simplifies to:
func fetch(req: Request) throws -> EventLoopFuture<[Chat]> {
let user = try req.auth.require(User.self)
return User
.query(on: req.db)
.filter(\.$id == user.id!)
.with(\.$chats)
.first()
.unwrap(or:Abort(.internalServerError))
.map
{
chattyUser in
return chattyUser.chats
}
}

Related

Writing Tests for Core Data Exist Feature

I am trying to write a test for my Budget Core Data class to make sure that two budgets with the same name cannot be added. Here is my Budget class.
#objc(Budget)
class Budget: Model {
override func save() throws {
if try exists(name ?? "") {
throw BudgetError.duplicateName
} else {
try save()
}
}
private func exists(_ name: String) throws -> Bool {
let request = Budget.fetchRequest()
request.predicate = NSPredicate(format: "name = %#", name)
request.sortDescriptors = []
let budgets = try viewContext.fetch(request)
return !budgets.isEmpty
}
}
The main issue I am facing is that the test is always failing on the first call to the save function.
func test_Throws_Duplicate_Name_Exception() throws {
var thrownError: Error?
let budget = Budget(context: CoreDataProvider.shared.viewContext)
budget.name = "Car Rental"
CoreDataProvider.shared.viewContext.refreshAllObjects()
try budget.save() <-- TEST FAILS HERE AND THROWS duplicateName EXCEPTION
// save it again
XCTAssertThrowsError(try budget.save()) {
thrownError = $0
}
XCTAssertEqual(thrownError as? BudgetError, .duplicateName)
}
I think the main reason is that when a budget object is created as shown below:
let budget = Budget(context: CoreDataProvider.shared.viewContext)
The budget is also added to the ViewContext and the exists function returns true and the BudgetError is thrown.
What can I do about this situation? The error should happen when I call save the second time, not the first time.
Rather than create first, try retrieving it first, then if it exists, you can modify, otherwise create the new one.

How to have columns in table that don't have to be specified in post request

I have a Vapor app where I want some values to be specified by the user in a POST request, and other values to be computed based on the user-specified values.
For example, suppose the user patches in some new values, and each time that happens the table should automatically update a column with the current time.
I was looking at trying to store the computed properties in the database, but when I modified the model to know about the computed properties, all my POST requests began expecting those to be specified.
What's the most idiomatic way to have columns in a table that don't have to be specified by the post requests?
If you are only looking to update a modified or created timestamp then there are two other ways. In your model, put:
static let createdAtKey: TimestampKey? = \.createdAt
static let updatedAtKey: TimestampKey? = \.updatedAt
var createdAt:Date?
var updatedAt:Date?
And let vapor do it for you, see here. Alternatively, you can make use of the methods willCreate, willUpdate, etc. as described in the docs here if you are updating fields that do not need the user's input.
extension User
{
func willUpdate(on connection: Database.Connection) throws -> Future<User>
{
modifiedCount += 1
return Future.map(on: connection) { self }
}
}
Finally, if you need a bit more flexibility than your own solution, consider using this in your controller:
struct EditUserForm:Content
{
let id:Int
let surname:String
let initials:String
}
func save(_ request:Request) throws -> Future<View>
{
return try request.content.decode(EditUserForm.self).flatMap
{
newUserData in
return try request.parameters.next(User.self).flatMap
{
originalUser in
// update fields as required, EditUserForm only has a subset
return originalUser.save(on:request).transform(to:try self.index(request))
}
}
}
You will need the usual route:
router.post(User.parameter, "save", use:userController.save)
I found that I need to make the computed fields be optional in the model, and then compute them in the route function before saving.
For example:
Making modified_date be optional in the model:
final class MyContentType: PostgreSQLModel {
var id: Int?
var name: String
var modified_date: Date?
}
Setting modified_date to the computed value:
func create(_ request: Request, content: MyContentType) throws -> Future< MyContentType > {
content.modified_date = Date()
return content.save(on: request)
}

rxswift viewmodel with input output

I am trying to achieve something similar in rxswift example project from RxSwift repo. But in my case there are dependent observables. I couldn't find any solution without using binding in viewmodel
Here is the structure of my viewmodel:
First the definitions of input, output and viewmodel
typealias UserListViewModelInput = (
viewAppearAction: Observable<Void>,
deleteAction: Observable<Int>
)
typealias UserListViewModelOutput = Driver<[User]>
typealias UserListViewModel = (UserListViewModelInput, #escaping UserApi) -> UserListViewModelOutput
Then there is actual implementation which doesn't compile.
let userListViewModel: UserListViewModel = { input, loadUsers in
let loadedUserList = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.elements()
.asDriver(onErrorDriveWith: .never())
let userListAfterDelete = input.deleteAction
.withLatestFrom(userList) { index, users in
users.enumerated().compactMap { $0.offset != index ? $0.element : nil }
}
.asDriver(onErrorJustReturn: [])
let userList = Driver.merge([loadedUserList, userListAfterDelete])
return userList
}
Viewmodel has two job. First load the user list. Second is delete a user at index. The final output is the user list which is downloaded with UserApi minus deleted users.
The problem in here in order the define userList I need to define userListAfterDelete. And in order to define userListAfterDelete I need to define userList.
So is there a way to break this cycle without using binding inside view model? Like a placeholder observable or operator that keeps state?
This is a job for a state machine. What you will see in the code below is that there are two actions that can affect the User array. When the view appears, a new array is downloaded, when delete comes in, a particular user is removed.
This is likely the most common pattern seen in reactive code dealing with state. So common that there are whole libraries that implement some variation of it.
let userListViewModel: UserListViewModel = { input, loadUsers in
enum Action {
case reset([User])
case delete(at: Int)
}
let resetUsers = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.compactMap { $0.element }
.map { Action.reset($0) }
let delete = input.deleteAction.map { Action.delete(at: $0) }
return Observable.merge(resetUsers, delete)
.scan(into: [User](), accumulator: { users, action in
switch action {
case let .reset(newUsers):
users = newUsers
case let .delete(index):
users.remove(at: index)
}
})
.asDriver(onErrorJustReturn: [])
}

Getting null when trying to return only single object and not array in graphql for neo4j db

I want to return a single object(not array).The graphql server is returning null when i try to return single object.But seems to work fine while trying to return an array.
Here is my schema example:
type Author{
authorID: String
label: String
posts: [Post]
}
type Post {
postID: String
label: String
author: Author
}
type Query {
getAuthorById (authorID: String!): Author #-> this does not work
getAuthorById (authorID: String!): [Author] #-> this works
}
But when I try to run this query "getAuthorById (authorID: String!)",I am getting the following result:
{
"data": {
"getAuthorById": {
"label": null,
"authorID": null
}
}
However,it seems to work only if i try to return an array (when I try to change the schema for type query like this):
type Query {
getAuthorById (authorID: String!): [Author]
}
Here is my resolver.js:
Query: {
getAuthorById(_, params) {
let session = driver.session();
let query = `MATCH (a:Author{ authorID: $authorID}) RETURN a ;`
return session.run(query, params)
.then( result => {
return result.records.map( record => {
return record.get("a").properties
}
)
}
)
},
}
What I need is to return a single object like this:
getAuthorById (authorID: String!): Author
// Instead of array like this-> getAuthorById (authorID: String!): [Author]
so,could someone let me know what am i doing wrong here ? All I need is to return single object and not array .... Thanks in Advance
The problem is in your resolver, specifically you are returning the result of result.records.map() from the resolver. map() evaluates to an Array (applying the inner function to each element of result in this case.
Instead you can just grab the first Record in the Result stream:
.then( result => {
return result.records[0].get("a").properties
}
)

Proper use of Strategy Pattern for simple networking layer in Swift 3

Does the following code demonstrate proper use of Strategy design pattern for a simple networking layer in swift 3?
Some code smells I'm unsure about:
violates Single responsibiility principle. Each strategy class such as Find, has a method for a different type of implementation. This is because I could want to find an image, or a user, or a chatroom. which are stored at different nodes in Firebase. all these different find methods are clumped together in Find class.
At the call sight of a request, if I need to make multiple async request, I nest the next request call inside the closure of the call back. Is this Ok?
The request object allows access to every type of insert, and find method. so in my signup VC I could i have the option to download a chatroom. Is even having access to that kind of implementation bad?
I have posted the code below, and left out all the actual implementation for brevity.
Any tips or guidance is much appreciated!
// USE CASE: Would go in viewDidLoad of ViewController
func testMyRequest () {
let myRequest = Request(insert: Insert(), find: Find())
myRequest.find?.user(with: "id", handler: { (user) in
myRequest.find?.nearbyUsers(user: user, handler: { (users) in
// update collectionView datasource
})
})
}
// Is this protocol necessary?
protocol RequestProtocol {
// - Family of algorithms, related actions.
var insert: Insert? { get set }
var find: Find? { get set }
}
// ---------------------------
class Request: RequestProtocol {
var insert: Insert?
var find: Find?
init(insert: Insert?, find: Find?) {
self.insert = insert
self.find = find
}
}
// Use a singleton maybe for the classes below? Why wouldn't I?
class Insert {
init() { }
func user(_ user: User) {
// insert user to firebase implementation
}
func message(_ message: Message) -> Void {
// insert message to firebase impelmentation
}
func image(data: Data, user: User) {
// insert image to firebase impelmentation
}
}
class Find {
init() { }
func user(with id: String, handler: #escaping (_ user: User) -> Void ) {
// find user implementation
}
func allChatrooms(handler: #escaping ([Chatroom]) -> Void) {
// find all chatrooms implementation
}
func nearbyUsers(user: User, handler: #escaping ([User]) -> Void ) {
// find nearby Users relative to current User location implementation
}
// Private helper method
private func findChatPartners (currentUser: User, chatrooms: [Chatroom] ) -> Set<String> {
}
}

Resources