Skip to content

Commit be824ea

Browse files
committed
[Package/Target]Updated documentation
1 parent 08865e4 commit be824ea

9 files changed

Lines changed: 316 additions & 99 deletions

File tree

README.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,62 @@
11
# SafeFetching
22

3-
This library offers a DSL (Domain Specific Language) to safely build predicates and requests to fetch a CoreData store. Also a wrapper around `NSFetchedResultsController` is offered to publish arrays of `NSManagedObject` to be used with a `NSDiffableDataSource`.
3+
This library offers a DSL (Domain Specific Language) to safely build predicates and requests to fetch a CoreData store.
44

55
The documentation is built with docC. You can [read it online](https://abridoux.github.io/SafeFetching/documentation/safefetching/) or locally by running *Product**Build Documentation* or hitting **⇧⌃⌘D**.
66

7-
## Convenient and safe fetching
7+
## Convenient and Safe Fetching
88

9-
For any CoreData entity generated by Xcode, the only required step is to make it implement `Fetchable`.
9+
The library requires to manually define the entity class. Then the macro `FetchableManagedObject` can be used.
1010

1111
```swift
12-
final class RandomEntity: NSManagedObject {
12+
@FetchableManagedObject
13+
final class User: NSManagedObject {
1314

1415
@NSManaged var score = 0.0
1516
@NSManaged var name: String? = ""
1617
}
1718
```
19+
This makes `User` conform to `Fetchable` and ready to be used with the SafeFetching API.
1820

19-
```swift
20-
extension RandomEntity: Fetchable {}
21-
```
22-
23-
Then it's possible to use the DSL to build a request. The last step can either get the built request as `NSFetchRequest<RandomEntity>` or execute the request in the provided context.
21+
Then it's possible to use the DSL to build a request. The last step can either get the built request as `NSFetchRequest<User>` or execute the request in the provided context.
2422

2523
```swift
26-
RandomEntity.request()
24+
User.request()
2725
.all(after: 10)
28-
.where(\.score >= 15 || \.name != "Joe")
26+
.where { $0.score >= 15 || $0.name != "Joe" }
2927
.sorted(by: .ascending(\.score), .descending(\.name))
3028
.setting(\.returnsDistinctResults, to: true)
3129
.nsValue
3230
```
3331

3432
```swift
35-
RandomEntity.request()
33+
User.request()
3634
.all(after: 10)
37-
.where(\.score >= 15 || \.name != "Joe")
35+
.where { $0.score >= 15 || $0.name != "Joe" }
3836
.sorted(by: .ascending(\.score), .descending(\.name))
3937
.setting(\.returnsDistinctResults, to: true)
40-
.fetch(in: context) // returns [RandomEntity]
38+
.fetch(in: context) // returns [User]
4139
```
4240

43-
Advanced `NSPredicate` operators are also available like `BEGINSWITH` (`hasPrefix`). To use one, specified a key path followed by `*`:
41+
Advanced `NSPredicate` operators are also available like `BEGINSWITH` (`hasPrefix`).
4442

4543
```swift
46-
RandomEntity.request()
44+
User.request()
4745
.all()
48-
.where(\.name * .hasPrefix("Do"))
46+
.where { $0.name.hasPrefix("Do", options: .caseInsensitive) }
4947
.nsValue
5048
```
5149

52-
More about that in the documentation.
50+
More about that in the [documentation]((https://abridoux.github.io/SafeFetching/documentation/safefetching/).
51+
52+
## `NSPredicate`, `Predicate`
53+
Why not using `Predicate` directly? Two reasons:
54+
- It works only with SwiftData as of today (so iOS 17+, macOS 14+ ...). It doesn't work with CoreData.
55+
- It doesn't support everything that `NSPredicate` does when fetching a CoreData store.
56+
57+
Meanwhile, `NSPredicate` requires to write everything in a `String`, which is very error-prone.
58+
59+
Whereas this library tries to reach three objectives:
60+
1. Use compiler-checking to evaluate predicates and avoid runtime errors.
61+
2. Writing a request and especially a predicate should feel as natural as possible in Swift.
62+
3. No feature of NSPredicate to fetch a CoreData store should be left behind.

Sources/SafeFetching/Request/FetchableMember+Operators/BooleanKeyPathPredicate+Comparison.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ public func == <E: Fetchable, V: Equatable & DatabaseValue & DatabaseTestValue>(
1414
Builders.Predicate<E>(identifier: lhs.identifier, operatorString: "==", value: rhs)
1515
}
1616

17-
public func test<E: Fetchable, V: Equatable & DatabaseValue & DatabaseTestValue>(
18-
lhs: FetchableMember<E, V>,
19-
rhs: V
20-
) -> Builders.Predicate<E> {
21-
Builders.Predicate<E>(identifier: lhs.identifier, operatorString: "==", value: rhs)
22-
}
23-
2417
public func != <E: NSManagedObject, V: Equatable & DatabaseValue & DatabaseTestValue>(
2518
lhs: FetchableMember<E, V>,
2619
rhs: V

Sources/SafeFetching/SafeFetching.docc/Articles/build-predicates.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ Learn how to specify safe predicates safely when building a request.
55
Examples in the article refer to this entity.
66

77
```swift
8+
@FetchableManagedObject
89
final class StubEntity: NSManagedObject {
9-
1010
@NSManaged var score = 0.0
11-
@NSManaged var name: String? = ""
11+
@NSManaged var name: String? = ""
1212
}
1313
```
1414

15-
When building a request, the ``Builders/Request/where(_:)-((Entity.FetchableMembers)->Builders.Predicate<Entity>)`` operation allows to specify a predicate. For a demonstration purpose in this article, predicates are specified after their implicit declaration.
15+
When building a request, the ``Builders/Request/where(_:)-5uzqj`` operation allows to specify a predicate. For a demonstration purpose in this article, predicates are specified after their implicit declaration.
1616

1717
```swift
1818
let predicate: Builders.Predicate<StubEntity>
@@ -55,18 +55,20 @@ $0.score <= 20
5555

5656
##### Boolean
5757

58+
```swift
59+
$0.isAdmin
60+
```
61+
5862
```swift
5963
$0.isAdmin == true
6064
```
6165

66+
Inversion is supported.
67+
6268
```swift
6369
!$0.isAdmin
6470
```
6571

66-
> Tip: The `where(_:)` function has convenient variations to take a single boolean like ``Builders/Request/where(_:)-3pukm``:
67-
>
68-
> `.where(\.isAdmin)`.
69-
7072

7173
## Advanced Operations
7274
It's possible to use the advanced operators offered by `NSPredicate` safely by specifying calling the dedicated function from the ``FetchableMember``.
@@ -234,3 +236,42 @@ is only the same as
234236
$0.color == .blue
235237
```
236238
*when the stored `color` is a single option*.
239+
240+
## Relationships
241+
Predicates in SafeFetching support relationships. Given the two entities:
242+
243+
```swift
244+
@FetchableManagedObject
245+
final class StubEntity: NSManagedObject {
246+
@NSManaged var score = 0.0
247+
@NSManaged var pet: Pet?
248+
}
249+
250+
@FetchableManagedObject
251+
final class Pet: NSManagedObject {
252+
@NSManaged var name: String
253+
}
254+
```
255+
The following predicate can be expressed for the `User` entity.
256+
257+
```swift
258+
$0.pet.name == "Minouche"
259+
```
260+
> Note: Even if `pet` is an optional `Pet` relationship, SafeFetching has no concerns about it when specifying comparison. Optionals are not relevant when writing a `NSPredicate` string format to fetch a CoreData store (unless of course when checking nullity).
261+
262+
## Standalone predicate
263+
Using a `where(_:)` function is not the only way to make predicate.
264+
265+
### NSPredicate convenience
266+
If needed, a predicate can be specified to make a `NSPredicate`.
267+
268+
```swift
269+
let predicate: NSPredicate = .safe(on: User.self) { $0.score > 10 }
270+
```
271+
272+
### Static
273+
Also, a predicate can be provided with ``Builders/Predicate/predicate(_:)``.
274+
275+
```swift
276+
let predicate: Builders.Predicate<User> = .predicate { $0.score > 10 }
277+
```

Sources/SafeFetching/SafeFetching.docc/Articles/build-requests.md

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,20 @@
22

33
Learn how to build requests with the SafeFetching DSL.
44

5-
Examples in the article refer to this entity.
5+
Examples in the article refer to this `User`class.
66

77
```swift
8-
final class StubEntity: NSManagedObject {
9-
8+
@FetchableManagedObject
9+
final class User: NSManagedObject {
1010
@NSManaged var score = 0.0
11-
@NSManaged var name: String? = ""
11+
@NSManaged var name: String? = ""
12+
@NSManaged var isAdmin: Bool
1213
}
1314
```
1415

1516
## Setup
1617

17-
To be able to use the DSL for an entity, the only required step is to make it conform to `Fetchable`.
18-
19-
```swift
20-
extension StubEntity: Fetchable {}
21-
```
22-
23-
Then the request creation starts with `StubEntity.request()`.
18+
To be able to use the DSL for an entity it is needed to make it conform to `Fetchable` which is done here using the ``FetchableManagedObject()`` macro. Then the request creation starts with `User.request()`.
2419

2520
## Steps
2621
There are four steps (some are optional) to build a request:
@@ -29,100 +24,123 @@ There are four steps (some are optional) to build a request:
2924
- specify sorts (optional)
3025
- get either the build request `NSFetchRequest` with ``Builders/Request/nsValue`` or execute it directly with ``Builders/Request/fetch(in:)``
3126

32-
Additionally, the "setting" can be performed.
27+
Additionally, the "setting" step can be performed to set a property of `NSFetchRequest` before using it for fetching the store.
3328

3429
### Target
3530

3631
##### All
3732
To target all entities (which is the default behavior of CoreData's fetch requests), use the ``Builders/PreRequest/all(after:)`` function.
3833

3934
```swift
40-
StubEntity.request()
35+
User.request()
4136
.all()
4237
```
4338

44-
To ignore the first nth results:
39+
To ignore the first nth results.
4540

4641
```swift
47-
StubEntity.request()
42+
User.request()
4843
.all(after: 10)
4944
```
5045

5146
##### First
52-
To target the first entity meeting the criteria, use ``Builders/PreRequest/first()`` and ``Builders/PreRequest/first(nth:after:)``
47+
To target the first User meeting the criteria, use ``Builders/PreRequest/first()`` and ``Builders/PreRequest/first(nth:after:)``
5348

5449
```swift
55-
StubEntity.request()
50+
User.request()
5651
.first()
5752
```
5853

59-
Ignore the first nth results
54+
Ignore the first nth results.
6055

6156
```swift
62-
StubEntity.request()
57+
User.request()
6358
.first(after: 10)
6459
```
6560

66-
Limit the results
61+
Limit the results.
6762

6863
```swift
69-
StubEntity.request()
64+
User.request()
7065
.first(20)
7166
```
7267

73-
Limit the results and ignore the nth first
68+
Limit the results and ignore the nth first.
7469

7570
```swift
76-
StubEntity.request()
71+
User.request()
7772
.first(20, after: 10)
7873
```
7974

8075
### Predicate
8176

82-
Specify a predicate with the ``Builders/Request/where(_:)-5ar9o`` function after the target.
77+
Specify a predicate with one of the `where(_:)` functions after the target.
8378

8479
```swift
85-
StubEntity.request()
80+
User.request()
8681
.all()
87-
.where(\.score > 20)
82+
.where { $0.score > 20 }
8883
```
8984

85+
Compound predicates are also supported.
86+
87+
```swift
88+
User.request()
89+
.all()
90+
.where { $0.score > 20 && $0.name.contains("dore") }
91+
```
92+
93+
Single boolean values can be used.
94+
95+
```swift
96+
User.request()
97+
.all()
98+
.where { $0.isAdmin }
99+
```
100+
101+
> Tip: The `where(_:)` function has convenient variations to take a single boolean like ``Builders/Request/where(_:)-3pukm``:
102+
>
103+
> `.where(\.isAdmin)`.
104+
105+
Naming the parameter can sometimes be preferable for longer predicates.
106+
90107
```swift
91-
StubEntity.request()
108+
User.request()
92109
.all()
93-
.where(\.score > 20
94-
&& \.name * .contains("dore")
95-
)
110+
.where { members in
111+
members.score > 20 && members.name.contains("dore")
112+
|| !members.isAdmin
113+
}
96114
```
97115

98116
To learn more about building predicates, you can read <doc:build-predicates>.
99117

100118
### Sort
101-
After the target has been specified, one sort or more can be set to the request with ``Builders/Request/sorted(by:_:)``
119+
After the target has been specified, one sort or more can be set to the request with ``Builders/Request/sorted(by:_:)``.
102120

103121
```swift
104-
StubEntity.request()
122+
User.request()
105123
.all()
106124
.sorted(by: .ascending(\.name))
107125
```
108126

109127
```swift
110-
StubEntity.request()
128+
User.request()
111129
.all()
112130
.sorted(by: .ascending(\.name), .descending(\.score))
113131
```
114132

115133
### Additional settings
116-
If needed, it's possible to assign a value to the request being built with ``Builders/Request/setting(_:to:)`` which allows to keep the functional appearance.
134+
If needed, it's possible to assign a value to a property of the request being built with ``Builders/Request/setting(_:to:)``.
117135

118136
```swift
119-
StubEntity.request()
137+
User.request()
120138
.all()
121139
.setting(\.returnsDistinctResults, to: true)
122140
```
123141

124142
## Steps order
125-
Whereas some steps cannot be performed in a random order, others like the "setting" can be used any time after the target is specified. That said, it's advised to keep the order used to expose the steps:
143+
Whereas some steps cannot be performed in a random order, others like the "setting" can be used any time after the target is specified. That said, it's advised to keep the following order:
126144

127145
- target
128146
- predicate
@@ -132,9 +150,9 @@ Whereas some steps cannot be performed in a random order, others like the "setti
132150
For instance, here is a request with all the possible/required steps.
133151

134152
```swift
135-
StubEntity.request()
153+
User.request()
136154
.all(after: 10)
137-
.where(\.score >= 15 || \.name != "Winner")
155+
.where { $0.score >= 15 || $0.name != "Winner" }
138156
.sorted(by: .ascending(\.score), .descending(\.name))
139157
.setting(\.returnsDistinctResults, to: true)
140158
.nsValue

0 commit comments

Comments
 (0)