Converting app from SQLite.swift to GRDB.swift Second Question - swift

I am continuing the conversion of a number of my apps from SQLite.sift to GRDB.swift.
I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB. This was suggested in a reply to my first post on this subject.
struct FigureList: Codable, FetchableRecord
{
let figID: Int64
var notes: String?
var theDescription: String
var obrienNum: String
var manufNum: String?
var catagory: String?
}
This is the piece of code I'm looking for help to redo so it will work with GRDB. My apps use this type of code to build arrays from the database table. Pretty standard process.
static func getFigureList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = Table(gTheCollection)
let figID = Expression<Int64>("FigureID")
let notes = Expression<String>("Notes")
let theDescription = Expression<String>("theDescription")
let obrienNum = Expression<String>("ObrienNum")
let manufNum = Expression<String>("ManufNum")
let theCatagory = Expression<String>("Category")
do {
for figures in try Database.shared.databaseConnection!.prepare(theTable.order(obrienNum)) {
theArray.append(FigureList(figID: figures[figID],
notes: figures[notes],
theDescription: figures[theDescription],
obrienNum: figures[obrienNum],
manufNum: figures[manufNum],
catagory: figures[theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}
This is what I have come up with so far. It doesn't produce any warnings or errors but then again I'm pretty sure it's not total correct. Again, thanks in advance for any help.
static func getFigList() -> [FigureList]
{
var theArray = [FigureList]()
let theTable = gTheCollection
let figID = Column("FigureID")
let notes = Column("Notes")
let theDescription = Column("theDescription")
let obrienNum = Column("ObrienNum")
let manufNum = Column("ManufNum")
let theCatagory = Column("Category")
do {
try Database.shared.databaseConnection!.read { db in
let figures = try Row.fetchOne(db, sql: "SELECT * FROM = ? ORDER BY ObrienNum", arguments: [theTable])
theArray.append(FigureList.init(figID: figures![figID],
notes: figures![notes],
theDescription: figures![theDescription],
obrienNum: figures![obrienNum],
manufNum: figures![manufNum],
catagory: figures![theCatagory]))
}
} catch {
print("Fetching figure list failed: \(error)")
}
return theArray
}

I converted my structs to add Codable, FetchableRecord so as to have them work better with GRDB.
Good. Now it's very simple:
static func getFigList() throws -> [FigureList] {
return try Database.shared.databaseConnection!.read { db in
// SELECT * FROM FigureList ORDER BY ObrienNum
return FigureList.order(Column("ObrienNum")).fetchAll(db)
}
}

Related

How to Fetch Multiple Types From CloudKit using CKRecord.ID?

When fetching multiple types from CloudKit using CKRecord.ID I get the following error.
Error
Cannot invoke 'map' with an argument list of type '(#escaping (CKRecord.ID, String, CKAsset, Int) -> Lib)'
CloudKit Fetch Function
static func fetch(_ recordID: [CKRecord.ID], completion: #escaping (Result<[Lib], Error>) -> ()) {
let recordID: [CKRecord.ID] = recordID
let operation = CKFetchRecordsOperation(recordIDs: recordID)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { (record, err) in
guard let record = record?.values.map(Lib.init) ?? [] //returns error here
else {
if let err = err {
completion(.failure(err))
}
return
}
DispatchQueue.main.async {
completion(.success(record))
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
Lib
struct Lib {
var recordID: CKRecord.ID
var name: String
var asset: CKAsset
var rating: Int
}
How can I retrieve multiple types from CloudKit using the CKRecord.ID?
You haven't defined an initializer that accepts a CKRecord.
This will make it compile:
extension Lib {
init(_: CKRecord) { fatalError() }
}
Get rid of your ?? [] and go from there!
It may help you if you use accurate pluralization:
operation.fetchRecordsCompletionBlock = { records, error in
guard let libs = records?.values.map(Lib.init)
The answer from #Jessey was very helpful but kept returning a fatalError(). What ended up working was adding to the init.
Lib
struct Lib {
var recordID: CKRecord.ID
var name: String
var asset: CKAsset
var rating: Int
init(record: CKRecord){
recordID = record.recordID
name = (record["name"] as? String)!
asset = (record["asset"] as? CKAsset)!
rating = (record["rating"] as? Int)!
}
}
The "name", "asset", and "rating" are the custom field names in CloudKit dashboard records. I also got rid of the ?? [] in the fetch function per the instruction.
CloudKit Tutorial: Getting Started was a good reference.

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

Swift Vapor add additional info to custom response

I've two Models, Trip and Location. I would return a custom response with some field of trip and the number of Location that has the tripID equal to id of Trip. There is my code(not working). The field locationCount is always empty.
func getList(_ request: Request)throws -> Future<Response> {
let deviceIdReq = request.parameters.values[0].value
let queryTrips = Trip.query(on: request).filter(\.deviceId == deviceIdReq).all()
var tripsR = [TripCustomContent]()
var trips = [Trip]()
return queryTrips.flatMap { (result) -> (Future<Response>) in
trips = result
var count = 0
for t in trips {
let tripIdString = String(t.id!)
let v = Location.query(on: request).filter(\.tripID == tripIdString).count().map({ (res) -> Int in
return res
})/*.map{ (result) -> (Int) in
count = result
return result
}*/
let tripCustomContent = TripCustomContent.init(startTimestamp: t.startTimestamp, endTimestamp: t.endTimestamp, deviceId: t.deviceId, locationCount: v)
tripsR.append(tripCustomContent)
}
let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(tripsR)
let response = HTTPResponse.init(status: .ok, version: HTTPVersion.init(major: x, minor: y), headers: HTTPHeaders.init(), body: data)
let finalResponse = Response.init(http: response, using: request)
return try g.encode(for: request)
}
}
and this is my custom content struct:
struct TripCustomContent: Encodable {
var startTimestamp: String?
var endTimestamp: String?
var deviceId: String
var locationCount: Future<Int>
}
any suggestions?
You're trying to use a value which isn't available yet. When you're returning a Future, you aren't returning the value inside it.
So you want your TripCustomContent to be like this (use in vapor Content instead of Codable:
struct TripCustomContent: Content {
var startTimestamp: String?
var endTimestamp: String?
var deviceId: String
var locationCount: Int
}
You queried the Trip correctly, but not the Location. You could maybe try something like this:
return queryTrips.flatMap { trips -> Future<[TripCustomContent]> in
let tripIds = trips.map({ String($0.id!) })
return Location.query(on: request).filter(\.tripID ~~ tripIds).all().map { locations in
return trips.map { trip in
let locationCount = locations.filter({ $0.tripId == String(trip.id!) }).count
return TripCustomContent(... locationCount: locationCount)
}
}
}
What did I do here?
Map the trips to their tripIds to get an array of tripIds
Get all locations with a tripId of one of the tripIds in the above array
Map each of the trips to an instance of TripCustomContent, using the locations of the database filtered by tripId
Finally, you don't need to encode the JSON yourself, just return objects conforming Content:
func getList(_ request: Request) throws -> Future<[TripCustomContent]>
The above could be a solution to your strategy. But maybe you take a look at relations if they can be a more efficient, easier and faster way.

Error While Get Sum of Entity Property from Core Data

I've made a class to manage my CoreData entities [named : DBHelper] (All CRUD operations, which works fine. However, I need to get the sum of a property called subtotal but I'm receiving the following error:
thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)
What is wrong with my code ?
Entity class
import Foundation
import CoreData
#objc(Odetails)
public class Odetails: NSManagedObject {
public let entityName = "Odetails"
}
Entity Extension :
extension Odetails {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Odetails> {
return NSFetchRequest<Odetails>(entityName: "Odetails")
}
#NSManaged public var pname: String?
#NSManaged public var price: Int16?
#NSManaged public var qty: Int16?
#NSManaged public var subtotal: Int16?
}
Core Data Helper Class :
import CoreData
class DBHelper {
var context: NSManagedObjectContext
init(context: NSManagedObjectContext){
self.context = context
}
// ......
func getsum() -> Int16{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Odetails")
do {
let response = try context.fetch(fetchRequest)
var sum : Int16 = 0
for res in response as! [Odetails]{
sum += res.subtotal
}
return sum
} catch let error as NSError {
// failure
print(error.localizedDescription)
return 0
}
}
}
The Use :
override func viewDidLoad() {
super.viewDidLoad()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let dbhelper = DBHelper(context: context)
print("count : \(dbhelper.getAll().count)")
print("Sum : \(dbhelper.getsum())") // ERROR HERE
var sum : Int16 = 0
for r in dbhelper.getAll() { // Method Return [Odetails]
sum += r.subtotal // OR ERROR HERE
}
print(" Sum : \(sum)")
}
Any help will be much appreciated
A bit offtopic, this is a more efficient way to get the sum of a particular property in Core Data
func getSum() -> Int16
{
let description = NSExpressionDescription()
description.name = "sumSubtotal"
description.expression = NSExpression(forKeyPath: "#sum.subtotal")
description.expressionResultType = .integer16AttributeType
let request : NSFetchRequest<NSDictionary> = NSFetchRequest(entityName: "Odetails")
request.propertiesToFetch = [description]
request.resultType = .dictionaryResultType
do {
let results = try context.fetch(request)
return results.isEmpty ? 0 : results[0]["sumSubtotal"] as! Int16
} catch {
print(error)
return 0
}
}
Rather than forcing unwrapping. Can you try optional binding to make sure that the data was being fetched correctly.
guard let response: [Odetails] = context.fetch(fetchRequest) else { return 0}
var sum = 0
for res in response {
sum = sum + Int(res.subtotal)
}
return sum
The problem is you got overflow on Int16. According to Apple docs
In most cases, you don’t need to pick a specific size of integer to
use in your code. Swift provides an additional integer type, Int,
which has the same size as the current platform’s native word size:
On a 32-bit platform, Int is the same size as Int32. On a 64-bit
platform, Int is the same size as Int64.
Unless you need to work with a specific size of integer, always use
Int for integer values in your code. This aids code consistency and
interoperability. Even on 32-bit platforms, Int can store any value
between -2,147,483,648 and 2,147,483,647, and is large enough for many
integer ranges.

Swift error when trying to access Dictionary: `Could not find member 'subscript'`

This won't compile:
I've tried a couple different things; different ways of declaring the Dictionary, changing its type to match the nested-ness of the data. I also tried explicitly saying my Any was a collection so it could be subscripted. No dice.
import UIKit
import Foundation
class CurrencyManager {
var response = Dictionary<String,Any>()
var symbols = []
struct Static {
static var token : dispatch_once_t = 0
static var instance : CurrencyManager?
}
class var shared: CurrencyManager {
dispatch_once(&Static.token) { Static.instance = CurrencyManager() }
return Static.instance!
}
init(){
assert(Static.instance == nil, "Singleton already initialized!")
getRates()
}
func defaultCurrency() -> String {
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
let codesToCountries :Dictionary = [ "US":"USD" ]
if let localCurrency = codesToCountries[countryCode]{
return localCurrency
}
return "USD"
}
func updateBadgeCurrency() {
let chanCurr = defaultCurrency()
var currVal :Float = valueForCurrency(chanCurr, exchange: "Coinbase")!
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(currVal)
}
func getRates() {
//Network code here
valueForCurrency("", exchange: "")
}
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"][exchange][currency] as Float
}
}
Let's take a look at
response["current_rates"][exchange][currency]
response is declared as Dictionary<String,Any>(), so after the first subscript you try to call another two subscripts on an object of type Any.
Solution 1. Change the type of response to be a nested dictionary. Note that I added the question marks because anytime you access a dictionary item you get back an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String, Float>>>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"]?[exchange]?[currency]
}
Solution 2. Cast each level to a Dictionary as you parse. Make sure to still check if optional values exist.
var response = Dictionary<String,Any>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
let exchanges = response["current_rates"] as? Dictionary<String,Any>
let currencies = exchanges?[exchange] as? Dictionary<String,Any>
return currencies?[currency] as? Float
}
You can get nested dictionary data by following these steps:
let imageData: NSDictionary = userInfo["picture"]?["data"]? as NSDictionary
let profilePic = imageData["url"] as? String
func valueForCurrency(currency :String, exchange :String) -> Float? {
if let exchanges = response["current_rates"] as? Dictionary<String,Any> {
if let currencies = exchanges[exchange] as? Dictionary<String,Any> {
return currencies[currency] as? Float
}
}
return nil
}
response is declared as such:
var response = Dictionary<String,Any>()
So the compiler thinks response["current_rates"] will return an Any. Which may or may not be something that is subscript indexable.
You should be able to define you type with nested Dictionaries, 3 levels and eventually you get to a float. You also need to drill in with optional chaining since the dictionary may or may not have a value for that key, so it's subscript accessor returns an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String,Float>>>()
// ... populate dictionaries
println(response["current_rates"]?["a"]?["b"]) // The float