|
1 | 1 |  |
2 | 2 |
|
3 | | -[](https://cocoapods.org) |
4 | | -[](https://github.com/Carthage/Carthage) |
5 | | -[](http://swift.org) |
6 | | - |
| 3 | +[](http://swift.org) |
7 | 4 |
|
8 | | -Core Data Query Interface (CDQI) is a type-safe, fluent, intuitive library for working with Core Data in Swift. CDQI tremendously reduces the amount of code needed to do Core Data, and dramatically improves readability by allowing method chaining and by eliminating magic strings. CDQI is a bit like jQuery or LINQ, but for Core Data. |
| 5 | +Core Data Query Interface (CDQI) is a type-safe, fluent, intuitive library for working with Core Data in Swift. CDQI tremendously reduces the amount of code needed to do Core Data, and dramatically improves readability and refactoring by allowing method chaining and by eliminating magic strings. |
9 | 6 |
|
10 | | -NOTE: The `cdqi` tool used to generate attribute proxies is deprecated. Bug fixes and changes in Swift make it very simple to hand-code attribute proxies, so the `cdqi` tool is no longer necessary. |
| 7 | +CDQI uses the [PredicateQI](https://github.com/prosumma/PredicateQI) (PQI) package. PQI provides a type-safe Swift interface on top of Apple's `NSPredicate` language. CDQI adds the machinery needed to make PQI work with Core Data. |
11 | 8 |
|
12 | | -### Features |
| 9 | +## Features |
13 | 10 |
|
14 | 11 | - [x] [Fluent interface](http://en.wikipedia.org/wiki/Fluent_interface), i.e., chainable methods |
15 | 12 | - [x] Large number of useful overloads |
16 | 13 | - [x] Type-safety in filter comparisons. |
17 | 14 | - [x] Filtering, sorting, grouping, aggregate expressions, limits, etc. |
18 | 15 | - [x] Optionally eliminates the use of magic strings so common in Core Data |
19 | 16 | - [x] Query reuse, i.e., no side-effects from chaining |
20 | | -- [x] Support for iOS 9+, macOS 10.11+, tvOS 9+, and watchOS 2+. |
21 | | -- [x] Swift 5 |
| 17 | +- [x] TODO: minimum OS support |
| 18 | +- [x] Swift 5.7 |
22 | 19 |
|
23 | | -### Overview |
| 20 | +## Overview |
24 | 21 |
|
25 | | -In essence, CDQI is a tool that allows the creation (and execution) of fetch requests using a fluent syntax. In most cases, this can reduce many lines of code to a single (but still highly readable) line. |
| 22 | +The best way to understand how to use CDQI is to look at the unit tests, but an example will make things clear. |
26 | 23 |
|
27 | | -```swift |
28 | | -let swiftDevelopers = managedObjectContext.from(Developer.self). |
29 | | - filter(any(Developer.e.languages.name == "Swift")) |
30 | | - orderDesc(by: Developer.e.lastName) |
31 | | - .limit(5) |
32 | | - .all() |
33 | | -``` |
34 | | - |
35 | | -### Integration |
36 | | - |
37 | | -#### Carthage |
38 | | - |
39 | | -In your `Cartfile`, add the following line: |
40 | | - |
41 | | -```ruby |
42 | | -github "prosumma/CoreDataQueryInterface" ~> 7.0 |
43 | | -``` |
44 | | - |
45 | | -#### CocoaPods |
46 | | - |
47 | | -Add the following to your `Podfile`. If it isn't already present, you will have to add `use_frameworks!` as well. |
48 | | - |
49 | | -```ruby |
50 | | -pod 'CoreDataQueryInterface', '~> 7.0' |
51 | | -``` |
52 | | - |
53 | | -### Attribute And Model Proxies |
54 | | - |
55 | | -CDQI works through the use of _attribute and model proxies_. In CDQI, a proxy is a type that stands in for a Core Data model or attribute. There are built-in proxies for all the Core Data attribute types, e.g., `Int32Attribute`, `StringAttribute` and so on. For your own Core Data models, you will need to create your own proxies, which is very simple to do. Imagine we have two Core Data models, `Employee` and `Department`. There is a many-to-one relationship in Core Data between these models. To keep things simple, each has a simple `name` attribute of type `String`: |
| 24 | +First, let's start with the following data model: |
56 | 25 |
|
57 | 26 | ```swift |
58 | | -class Employee: NSManagedObjectModel { |
59 | | - @NSManaged var name: String |
60 | | - @NSManaged var department: Department |
| 27 | +class Language: NSManagedObject { |
| 28 | + @NSManaged var name: String |
| 29 | + @NSManaged var developers: Set<Developer> |
61 | 30 | } |
62 | 31 |
|
63 | | -class Department: NSManagedObjectModel { |
64 | | - @NSManaged var name: String |
65 | | - @NSManaged var employees: Set<Employee> |
| 32 | +class Developer: NSManagedObject { |
| 33 | + @NSManaged var firstName: String |
| 34 | + @NSManaged var lastName: String |
| 35 | + @NSManaged var languages: Set<Language> |
66 | 36 | } |
67 | 37 | ``` |
68 | 38 |
|
69 | | -The proxy classes for these should look like this: |
| 39 | +Given this data model, we can start making some queries: |
70 | 40 |
|
71 | 41 | ```swift |
72 | | -class EmployeeAttribute: EntityAttribute, Subqueryable { |
73 | | - public private(set) lazy var name = StringAttribute(key: "name", parent: self) |
74 | | - public private(set) lazy var department = DepartmentAttribute(key: "department", parent: self) |
75 | | -} |
| 42 | +// Which languages are known by at least two of the developers? |
| 43 | +// developers.@count >= 2 |
| 44 | +Query(Language.self).filter { $0.developers.pqiCount >= 2 } |
76 | 45 |
|
77 | | -extension Employee: Entity { |
78 | | - public typealias CDQIEntityAttribute = EmployeeAttribute |
79 | | -} |
80 | | - |
81 | | -class DepartmentAttribute: EntityAttribute, Subqueryable { |
82 | | - public private(set) lazy var name = StringAttribute(key: "name", parent: self) |
83 | | - public private(set) lazy var employees: EmployeeAttribute(key: "employees", parent: self) |
84 | | -} |
85 | | - |
86 | | -extension Department: Entity { |
87 | | - public typealias CDQIEntityAttribute = DepartmentAttribute |
88 | | -} |
| 46 | +// Which languages are known by developers whose name contains 's'? |
| 47 | +// ANY developers.lastname LIKE[c] '*s*' |
| 48 | +Query(Language.self).filter { any(ci($0.developers.lastName %* "*s*")) } |
89 | 49 | ``` |
90 | 50 |
|
91 | | -Once this is done, CDQI can do its magic. Read on. |
92 | | - |
93 | | -### Starting a Query |
94 | | - |
95 | | -A CDQI query is a chain of methods that build an `NSFetchRequest`. Almost all of the `NSFetchRequest`'s functionality is supported, such as choosing the result type, limiting the number of records fetched, filtering, sorting, etc. |
96 | | - |
97 | | -A query is started by creating an instance of `Query`, which takes two generic type parameters. The first one tells us which `NSManagedObject` subclass is the target of our query. The second tells us what the result of the query should be: Either the same `NSManagedObject` subclass or an `NSDictionary`. |
98 | | - |
99 | | -```swift |
100 | | -let developerQuery = Query<Developer, Developer>() |
101 | | -let developerDictionaryQuery = Query<Developer, NSDictionary>() |
102 | | -``` |
103 | | - |
104 | | -Most `Query` instances are of the form `Query<M, M>` where `M` is an `NSManagedObject` which implements the `Entity` protocol. A perhaps better way to start a query is… |
105 | | - |
106 | | -```swift |
107 | | -let developerQuery = Developer.cdqiQuery |
108 | | -``` |
109 | | - |
110 | | -Queries started with `Query<Developer, Developer>` or `Developer.cdqiQuery` have no implicit `NSManagedObjectContext`, so one must be passed when executing a query. |
| 51 | +We can get the `NSFetchRequest` produced by the query by asking for its `fetchRequest` property. But it's usually easier just to execute the fetch request directly: |
111 | 52 |
|
112 | 53 | ```swift |
113 | | -try Developer.cdqiQuery.order(by: Developer.e.lastName).all(managedObjectContext: moc) |
114 | | -try Developer.cdqiQuery,order(by: Developer.e.lastName).context(moc).all() |
| 54 | +let rustaceans = try Query(Developer.self) |
| 55 | + .filter { |
| 56 | + any($0.languages.name == "Rust") && $0.experience >= 3 |
| 57 | + } |
| 58 | + .fetch(moc) |
115 | 59 | ``` |
116 | | - |
117 | | -This pattern is so common that a convenience method exists on `NSManagedObjectContext`. |
118 | | - |
119 | | -```swift |
120 | | -try moc.from(Developer.self).order(by: Developer.e.lastName).all() |
121 | | -``` |
122 | | - |
123 | | -### Filtering |
124 | | - |
125 | | -Filtering in Core Data requires an `NSPredicate`. CDQI has overloads of many of the built-in operators. These overloads generate Core Data friendly `NSPredicate`s instead of `Bool`s. They are carefully designed so as not to conflict with the ordinary operators. |
126 | | - |
127 | | -| Swift | `NSPredicate` | |
128 | | -| --- | --- | |
129 | | -| `Developer.e.lastName == "Li"` | `"lastName == 'Li'"` | |
130 | | -| `Person.e.age >= 18` | `"age >= 18"` | |
131 | | -| `21...55 ~= Person.e.age` | `"age BETWEEN 21 AND 55"` | |
132 | | -| `Person.e.firstName == "Friedrich" && Person.e.lastName == "Hayek"` | `"firstName == 'Friedrich' AND lastName == 'Hayek'"` | |
0 commit comments