Skip to content

Commit

Permalink
Switch to GRDB (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
GeordieP committed Dec 13, 2019
1 parent 6dba041 commit 695b411
Show file tree
Hide file tree
Showing 37 changed files with 896 additions and 673 deletions.
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -18,7 +18,9 @@ A quick & easy shopping list experience for iOS.
**Technologies**

- [Swift](https://swift.org/), [SwiftUI](https://developer.apple.com/xcode/swiftui/)
- [SwiftDux](https://github.com/StevenLambion/SwiftDux) (Redux-like state management for SwiftUI by [StevenLambion](https://github.com/StevenLambion))
- [GRDB](https://github.com/groue/GRDB.swift) by [groue](https://github.com/groue)
- [GRDBCombine](https://github.com/groue/GRDBCombine) by [groue](https://github.com/groue)
- [SQLite](https://www.sqlite.org/index.html)

**Requirements**

Expand All @@ -30,3 +32,5 @@ A quick & easy shopping list experience for iOS.
I've been writing weekly posts about my experiences using SwiftUI for the first time on my website. Feel free to check them out:

- [Week 1 - Starting out, Filtering Lists](http://gpow.ca/blog/posts/SwiftShop1/)
- [Week 2 - Lists state & Products search bar](http://gpow.ca/blog/posts/SwiftShop2/)
- [Week 3 - GRDB](http://gpow.ca/blog/posts/SwiftShop3/)
199 changes: 103 additions & 96 deletions SwiftShop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
Expand Up @@ -2,12 +2,21 @@
"object": {
"pins": [
{
"package": "SwiftDux",
"repositoryURL": "https://github.com/StevenLambion/SwiftDux",
"package": "GRDB",
"repositoryURL": "https://github.com/groue/GRDB.swift.git",
"state": {
"branch": null,
"revision": "2d479ad90f9c3cfc8b63ce65066db2c9f7c47685",
"version": "0.11.2"
"revision": "a67070dd9a08dad455e9f2895a0c40921d31fa1c",
"version": "4.6.2"
}
},
{
"package": "GRDBCombine",
"repositoryURL": "https://github.com/groue/GRDBCombine.git",
"state": {
"branch": null,
"revision": "d2819f45f7e98244da5049d4ff136f87d423b8ea",
"version": "0.7.0"
}
}
]
Expand Down
14 changes: 14 additions & 0 deletions SwiftShop/AppConstants.swift
@@ -0,0 +1,14 @@
//
// AppConstants.swift
// SwiftShop
//
// Created by Geordie Powers on 2019-12-01.
// Copyright © 2019 Geordie Powers. All rights reserved.
//

import Foundation

struct AppConstants {
static let DEFAULT_LIST_ID = "MAIN"
static let DEFAULT_LIST_NAME = "Main List"
}
128 changes: 128 additions & 0 deletions SwiftShop/AppDatabase.swift
@@ -0,0 +1,128 @@
//
// AppDatabase.swift
// SwiftShop
//
// Created by Geordie Powers on 2019-12-05.
// Copyright © 2019 Geordie Powers. All rights reserved.
//

import GRDB

struct AppDatabase {
static func openDatabase(atPath path: String) throws -> DatabasePool {
let dbPool = try DatabasePool(path: path)

#if DEBUG
try dbPool.erase()
#endif

try setupMigrations.migrate(dbPool)

#if DEBUG
try debugMigrations.migrate(dbPool)
#endif

return dbPool
}
}

extension AppDatabase {
static var setupMigrations: DatabaseMigrator {
var migrator = DatabaseMigrator()

migrator.registerMigration("createList") { db in
try db.create(table: "listEntity") { t in
t.autoIncrementedPrimaryKey("id")

t.column("name", .text)
.notNull()
.collate(.localizedCaseInsensitiveCompare)
}
}

migrator.registerMigration("createProduct") { db in
try db.create(table: "productEntity") { t in
t.autoIncrementedPrimaryKey("id")

t.column("name", .text)
.notNull()
.collate(.localizedCaseInsensitiveCompare)

t.column("price", .double)
}
}

migrator.registerMigration("createProductStatus") { db in
try db.create(table: "productStatusEntity") { t in
t.autoIncrementedPrimaryKey("id")

t.column("listId", .integer)
.references("listEntity", onDelete: .cascade)
t.column("productId", .integer)
.references("productEntity", onDelete: .cascade)

t.column("complete", .boolean)
.notNull()
.defaults(to: false)
}
}

migrator.registerMigration("createTag") { db in
try db.create(table: "tagEntity") { t in
t.autoIncrementedPrimaryKey("id")

t.column("name", .text)
.notNull()
.collate(.localizedCaseInsensitiveCompare)

t.column("color", .text)
}
}

migrator.registerMigration("createProductTag") { db in
try db.create(table: "productTagEntity") { t in
t.column("productId", .integer)
.references("productEntity", onDelete: .cascade)
t.column("tagId", .integer)
.references("tagEntity", onDelete: .cascade)
}
}

return migrator
}

static var debugMigrations: DatabaseMigrator {
var migrator = DatabaseMigrator()

migrator.registerMigration("createDevEntities") { db in
var list = ListEntity(id: nil, name: AppConstants.DEFAULT_LIST_NAME)
try list.insert(db)

var product1 = ProductEntity(id: nil, name: "First product", price: 3.00)
var product2 = ProductEntity(id: nil, name: "Second product", price: 2.00)
var product3 = ProductEntity(id: nil, name: "Third product", price: 1.00)
try product1.insert(db)
try product2.insert(db)
try product3.insert(db)

var tag1 = TagEntity(id: nil, name: "BlueTag", color: "blue")
var tag2 = TagEntity(id: nil, name: "GreenTag", color: "green")
try tag1.insert(db)
try tag2.insert(db)

var productStatus1 = ProductStatusEntity(listId: list.id!, productId: product1.id!, complete: false)
try productStatus1.insert(db)
var productStatus2 = ProductStatusEntity(listId: list.id!, productId: product2.id!, complete: true)
try productStatus2.insert(db)

var productTag1 = ProductTagEntity(productId: product1.id!, tagId: tag1.id!)
var productTag2 = ProductTagEntity(productId: product1.id!, tagId: tag2.id!)
var productTag3 = ProductTagEntity(productId: product3.id!, tagId: tag2.id!)
try productTag1.insert(db)
try productTag2.insert(db)
try productTag3.insert(db)
}

return migrator
}
}
30 changes: 21 additions & 9 deletions SwiftShop/AppDelegate.swift
Expand Up @@ -7,25 +7,37 @@
//

import UIKit
import Combine
import GRDB

// Default app model instance
var App = AppModel(database: { fatalError("Database is uninitialized") })

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let dbPool = try! createDbPool(application)
App = AppModel(database: { dbPool })

return true
}


private func createDbPool(_ application: UIApplication) throws -> DatabasePool {
let databaseURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("db2.sqlite")

let dbPool = try AppDatabase.openDatabase(atPath: databaseURL.path)
dbPool.setupMemoryManagement(in: application)

return dbPool
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { }
}
23 changes: 23 additions & 0 deletions SwiftShop/AppModel.swift
@@ -0,0 +1,23 @@
//
// AppModel.swift
// SwiftShop
//
// Created by Geordie Powers on 2019-12-07.
// Copyright © 2019 Geordie Powers. All rights reserved.
//

import UIKit
import Combine
import GRDB
import GRDBCombine

struct AppModel {
private let database: () -> DatabaseWriter

init(database: @escaping () -> DatabaseWriter) {
self.database = database
}

func products() -> ProductRepository { ProductRepository(database()) }
func lists() -> ProductListRepository { ProductListRepository(database()) }
}
23 changes: 0 additions & 23 deletions SwiftShop/Containers/ListPage.swift

This file was deleted.

44 changes: 0 additions & 44 deletions SwiftShop/Containers/ProductsPage/FilterManager.swift

This file was deleted.

48 changes: 0 additions & 48 deletions SwiftShop/Containers/ProductsPage/ProductsPage.swift

This file was deleted.

0 comments on commit 695b411

Please sign in to comment.