Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update database prep examples/docs to use dependency context #21

Merged
merged 1 commit into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/Reminders/ReminderForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ struct TagsPopover: View {

#Preview {
let (remindersList, reminder) = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
return try $0.defaultDatabase.write { db in
let remindersList = try RemindersList.fetchOne(db)!
return (
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/ReminderRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ struct ReminderRow: View {
var reminder: Reminder!
var reminderList: RemindersList!
let _ = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
try $0.defaultDatabase.read { db in
reminder = try Reminder.fetchOne(db)
reminderList = try RemindersList.fetchOne(db)!
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/RemindersListDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ struct RemindersListDetailView: View {

#Preview {
let remindersList = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
return try $0.defaultDatabase.read { db in
try RemindersList.fetchOne(db)!
}
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/RemindersListForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ extension Int {

#Preview {
let _ = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
}
NavigationStack {
RemindersListForm()
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/RemindersLists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private struct ReminderGridCell: View {

#Preview {
let _ = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
}
NavigationStack {
RemindersListsView()
Expand Down
9 changes: 5 additions & 4 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct ReminderTag: Codable, FetchableRecord, MutablePersistableRecord {
var tagID: Int64?
}

func appDatabase(inMemory: Bool = false) throws -> any DatabaseWriter {
func appDatabase() throws -> any DatabaseWriter {
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
Expand All @@ -61,12 +61,13 @@ func appDatabase(inMemory: Bool = false) throws -> any DatabaseWriter {
}
#endif
}
if inMemory {
database = try DatabaseQueue(configuration: configuration)
} else {
@Dependency(\.context) var context
if context == .live {
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
print("open", path)
database = try DatabasePool(path: path, configuration: configuration)
} else {
database = try DatabaseQueue(configuration: configuration)
}
var migrator = DatabaseMigrator()
#if DEBUG
Expand Down
2 changes: 1 addition & 1 deletion Examples/Reminders/SearchReminders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private func searchQueryBase(searchText: String) -> QueryInterfaceRequest<Remind
#Preview {
@Previewable @State var searchText = "take"
let _ = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase(inMemory: true)
$0.defaultDatabase = try Reminders.appDatabase()
}

NavigationStack {
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct AppView: View {

#Preview("Happy path") {
let _ = prepareDependencies {
$0.defaultDatabase = SyncUps.appDatabase(inMemory: true)
$0.defaultDatabase = SyncUps.appDatabase()
}
AppView(model: AppModel())
}
9 changes: 5 additions & 4 deletions Examples/SyncUps/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ enum Theme: String, CaseIterable, Codable, Hashable, Identifiable, DatabaseValue
}
}

func appDatabase(inMemory: Bool = false) throws -> any DatabaseWriter {
func appDatabase() throws -> any DatabaseWriter {
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
Expand All @@ -98,12 +98,13 @@ func appDatabase(inMemory: Bool = false) throws -> any DatabaseWriter {
}
#endif
}
if inMemory {
database = try DatabaseQueue(configuration: configuration)
} else {
@Dependency(\.context) var context
if context == .live {
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
print("open", path)
database = try DatabasePool(path: path, configuration: configuration)
} else {
database = try DatabaseQueue(configuration: configuration)
}
var migrator = DatabaseMigrator()
#if DEBUG
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ struct MeetingView: View {

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = SyncUps.appDatabase(inMemory: true)
$0.defaultDatabase = SyncUps.appDatabase()
}
@Dependency(\.defaultDatabase) var database
let syncUp = try! database.read { db in
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/SyncUpsList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ extension LabelStyle where Self == TrailingIconLabelStyle {

#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = SyncUps.appDatabase(inMemory: true)
$0.defaultDatabase = SyncUps.appDatabase()
}
NavigationStack {
SyncUpsList(model: SyncUpsListModel())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ way to do this is to construct the database connection for a path on the file sy
```

However, in tests and Xcode previews we would like to use an in-memory database so that each test
and preview gets their own sandboxed database. To do this we can turn `appDatabase` into a static
function that takes an `inMemory` option (that defaults to `false`) so that it can be configured
when setting up the database:
and preview gets their own sandboxed database. In fact, previews can crash if we attempt to load a
database from the file system.

To fix this we can use `@Dependency(\.context)` to determine if we are in a "live" application
context or if we're in a preview or test.

```diff
func appDatabase() -> any DatabaseWriter {
+func appDatabase(inMemory: Bool = false) -> any DatabaseWriter {
var configuration = Configuration()
configuration.foreignKeysEnabled = true
#if DEBUG
Expand All @@ -122,17 +123,23 @@ when setting up the database:
#endif
- let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
- let database = try DatabasePool(path: path, configuration: configuration)
+ @Dependency(\.context) var context
+ let database: any DatabaseWriter
+ if inMemory {
+ database = try DatabaseQueue(configuration: configuration)
+ } else {
+ if context == .live {
+ let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
+ database = try DatabasePool(path: path, configuration: configuration)
+ } else {
+ database = try DatabaseQueue(configuration: configuration)
+ }
return database
}
```

> Tip: `@Dependency(\.context)` comes from the [Swift Dependencies][swift-dependencies-gh] library,
> which SharingGRDB uses to share its database connection across fetch keys.

[swift-dependencies-gh]: https://github.com/pointfreeco/swift-dependencies

### Step 4: Migrate database

Now that the database connection is created we can migrate the database. GRDB provides all the
Expand All @@ -141,7 +148,7 @@ creating a `DatabaseMigrator`, registering migrations with it, and then using it
database connection:

```diff
func appDatabase(inMemory: Bool = false) throws -> Self {
func appDatabase() throws -> Self {
var configuration = Configuration()
configuration.foreignKeysEnabled = true
#if DEBUG
Expand All @@ -151,12 +158,13 @@ database connection:
}
}
#endif
@Dependency(\.context) var context
let database: any DatabaseWriter
if inMemory {
database = try DatabaseQueue(configuration: configuration)
} else {
if context == .live {
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
database = try DatabasePool(path: path, configuration: configuration)
} else {
database = try DatabaseQueue(configuration: configuration)
}
+ var migrator = DatabaseMigrator()
+ #if DEBUG
Expand All @@ -179,7 +187,7 @@ That is all it takes to create, configure and migrate a database connection. Her
we have just written in one snippet:

```swift
func appDatabase(inMemory: Bool = false) throws -> Self {
func appDatabase() throws -> Self {
var configuration = Configuration()
configuration.foreignKeysEnabled = true
#if DEBUG
Expand All @@ -189,12 +197,13 @@ func appDatabase(inMemory: Bool = false) throws -> Self {
}
}
#endif
@Dependency(\.context) var context
let database: any DatabaseWriter
if inMemory {
database = try DatabaseQueue(configuration: configuration)
} else {
if context == .live {
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
database = try DatabasePool(path: path, configuration: configuration)
} else {
database = try DatabaseQueue(configuration: configuration)
}
var migrator = DatabaseMigrator()
#if DEBUG
Expand Down Expand Up @@ -258,23 +267,21 @@ And if using something besides UIKit or SwiftUI, then simply set the
``Dependencies/DependencyValues/defaultDatabase`` as early as possible in the application's
lifecycle.

It is also important to prepare the database in Xcode previews, but in this situation you will want
to use an in-memory database. This can be done like so:
It is also important to prepare the database in Xcode previews. This can be done like so:

```swift
#Preview {
let _ = prepareDependencies {
$0.defaultDatabase = try! appDatabase(inMemory: true)
$0.defaultDatabase = try! appDatabase()
}
// ...
}
```

And similarly, in tests you will also want to use an in-memory database. This can be done using the
`.dependency` testing trait:
And similarly, in tests, this can be done using the `.dependency` testing trait:

```swift
@Test(.dependency(\.defaultDatabase, try appDatabase(inMemory: true))
@Test(.dependency(\.defaultDatabase, try appDatabase())
func feature() {
// ...
}
Expand Down