Skip to content

Commit cf039fb

Browse files
committed
Add generated to-many relationship count accessors
1 parent ca8b058 commit cf039fb

44 files changed

Lines changed: 428 additions & 30 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Docs/Development/DesignNotes.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,12 @@ func removeFromTags(_ tags: Set<Tag>) {
209209

210210
```swift
211211
@PersistentModel(
212+
generateToManyCount: true,
212213
)
213214
```
214215

215216
- 对多 getter(`Set<T>` / `[T]`)当前固定生成,不提供独立策略开关。
216-
- 不生成任何 `*Count` 访问器,建议使用 `NSManagedObjectContext.count(for:)` + `NSPredicate`
217+
- 默认生成 `relationshipNameCount` 访问器;如需关闭,可显式写 `generateToManyCount: false`
217218
- 所有对多关系都不生成 setter,统一通过便利方法进行关系修改。
218219
- `Set<T>` 生成单个和批量 `add/remove` 便利方法。
219220
- `[T]` 生成单个和批量 `add/remove`,以及 `insertInto*(_:at:)` 便利方法。
@@ -380,7 +381,7 @@ func insertIntoTags(_ tag: Tag, at index: Int) {
380381
- ordered/unordered 与属性类型的一致性
381382

382383
无论源码如何声明,工具都会强制检查模型层 inverse 是否配置;缺失 inverse 一律为 `[ERROR]`
383-
v1 不自动生成 `*Count`若需要数量,推荐通过 `NSManagedObjectContext.count(for:)` + `NSPredicate` 计算
384+
v1 默认生成 `*Count`若不希望暴露这类成员,可显式关闭 `generateToManyCount`
384385

385386
---
386387

@@ -418,7 +419,7 @@ public final class Item: NSManagedObject {
418419

419420
// 宏生成的关系便利方法
420421
public func addToTags(_ tag: Tag) { ... }
421-
// v1 不自动生成 tagsCount,需使用 context.count(for:)
422+
public var tagsCount: Int { ... }
422423
}
423424
```
424425

Docs/Development/ImplementationPlan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@
181181
- [ ] validate 工具完成并接入 CI
182182
- [ ] generate 工具完成并接入命令入口
183183
- [ ] 规范文档与实现行为一致
184-
- [x] `tagsCount` 决策收敛(v1 不自动生成 `*Count`通过文档与诊断引导 `context.count(for:)`
184+
- [x] `tagsCount` 决策收敛(v1 默认生成 `*Count`并提供 `generateToManyCount: false` 开关
185185

186186
## 7. Current Sprint Plan (PersistentModel)
187187

Docs/Development/Specification.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Runtime schema / runtime model builder 的 v1 边界:
6767
- `Paths`(可组合子路径,含 relationship/composition)
6868
- `__cdFieldTable`(路径映射元信息)
6969
- `convenience init`(默认不生成;可通过 `generateInit: true` 开启)
70+
- 对多关系 `*Count` 访问器(默认生成;可通过 `generateToManyCount: false` 关闭)
7071

7172
`@objc` 注入规则:
7273

@@ -81,13 +82,14 @@ Runtime schema / runtime model builder 的 v1 边界:
8182
```swift
8283
@PersistentModel(
8384
generateInit: false,
85+
generateToManyCount: true,
8486
)
8587
```
8688

8789
当前策略语义:
8890

8991
- 对多 getter(`Set<T>` / `[T]`)当前固定生成,不提供单独策略开关。
90-
- 不生成任何 `*Count` 访问器,推荐改用 `NSManagedObjectContext.count(for:)` + `NSPredicate`
92+
- 默认生成 `relationshipNameCount` 访问器,基于底层 `NSSet` / `NSOrderedSet``count` 暴露轻量计数读取
9193
- `Set<T>` 生成单个和批量 `add/remove` 便利方法。
9294
- `[T]` 生成单个和批量 `add/remove`,以及 `insertInto*(_:at:)` 便利方法。
9395

@@ -237,7 +239,7 @@ relationship declaration 规则:
237239

238240
### Count
239241

240-
- 当前不自动生成 `*Count`
242+
- 当前默认自动生成 `*Count`;如需关闭,使用 `generateToManyCount: false`
241243

242244
## 5. Constructor Contract
243245

Docs/Development/ToolingCLI.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ v1 的版本与发行策略分为两层:
211211
"format": "none",
212212
"headerTemplate": null,
213213
"generateInit": false,
214+
"generateToManyCount": true,
214215
"defaultDecodeFailurePolicy": "fallbackToDefaultValue"
215216
},
216217
"validate": {
@@ -260,6 +261,8 @@ v1 的版本与发行策略分为两层:
260261
}
261262
}
262263
},
264+
"generateInit": false,
265+
"generateToManyCount": true,
263266
"include": [],
264267
"exclude": [],
265268
"level": "conformance",
@@ -535,8 +538,7 @@ v1 的版本与发行策略分为两层:
535538
### 4.4 宏生成策略参数(映射到当前宏能力)
536539

537540
- `--generate-init <true|false>`
538-
- `--relationship-setter-policy <none|warning|plain>`
539-
- `--relationship-count-policy <none|warning|plain>`
541+
- `--generate-to-many-count <true|false>`
540542
- `--default-decode-failure-policy <fallbackToDefaultValue|debugAssertNil>`
541543

542544
说明:这些参数作为“生成代码默认策略”,具体属性仍允许在代码层用 `@Attribute(...)` 覆盖。
@@ -568,6 +570,7 @@ v1 的版本与发行策略分为两层:
568570
- CLI 显式传入的相对路径相对当前工作目录解析。
569571
- `emitExtensionStubs`: optional, bool,默认 `false`
570572
- `generateInit`: optional, bool,默认 `false`
573+
- `generateToManyCount`: optional, bool,默认 `true`
571574
- `defaultDecodeFailurePolicy`: optional, enum(`fallbackToDefaultValue`,`debugAssertNil`),默认 `fallbackToDefaultValue`
572575

573576
## 5. `validate` 参数设计(v1)
@@ -583,8 +586,7 @@ v1 的版本与发行策略分为两层:
583586
- `--split-by-entity <bool>`
584587
- `--header-template <path>`
585588
- `--generate-init <bool>`
586-
- `--relationship-setter-policy <none|warning|plain>`
587-
- `--relationship-count-policy <none|warning|plain>`
589+
- `--generate-to-many-count <bool>`
588590
- `--default-decode-failure-policy <fallbackToDefaultValue|debugAssertNil>`
589591
- `--type-mappings`
590592
- v1 不单独提供 CLI 参数,推荐在 JSON 配置中声明。
@@ -629,6 +631,7 @@ v1 的版本与发行策略分为两层:
629631
- `headerTemplate`: optional, string/null,默认 `null`
630632
- `emitExtensionStubs`: optional, bool,默认 `false`
631633
- `generateInit`: optional, bool,默认 `false`
634+
- `generateToManyCount`: optional, bool,默认 `true`
632635
- `defaultDecodeFailurePolicy`: optional, enum(`fallbackToDefaultValue`,`debugAssertNil`),默认 `fallbackToDefaultValue`
633636
- `include`: optional, array<string>,默认 `[]`
634637
- `exclude`: optional, array<string>,默认 `[]`

Docs/PersistentModelGuide.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ For a valid model type, the macro generates:
9898
- conditional `fetchRequest()` when the type does not already declare one
9999
- optional convenience `init(...)` when `generateInit: true`
100100
- to-many relationship getters
101+
- to-many relationship count accessors such as `tagsCount` (enabled by default; disable with
102+
`generateToManyCount: false`)
101103
- unordered to-many helpers: single-value and batch `addTo*` / `removeFrom*`
102104
- ordered to-many helpers: single-value and batch `addTo*` / `removeFrom*`, plus
103105
`insertInto*(_:at:)`
@@ -807,6 +809,31 @@ convenience init(
807809
)
808810
```
809811

812+
## To-Many Count Accessors
813+
814+
`@PersistentModel` generates `relationshipNameCount` accessors for to-many relationships by
815+
default.
816+
817+
```swift
818+
@PersistentModel
819+
final class Tag: NSManagedObject {
820+
@Relationship(inverse: "tag", deleteRule: .nullify)
821+
var items: Set<Item>
822+
}
823+
```
824+
825+
Generated surface:
826+
827+
```swift
828+
var itemsCount: Int
829+
```
830+
831+
Disable this when you do not want the extra generated members:
832+
833+
```swift
834+
@PersistentModel(generateToManyCount: false)
835+
```
836+
810837
## Runtime Schema for Tests and Debugging
811838

812839
`@PersistentModel` also emits runtime schema metadata.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ It gives you:
196196

197197
- explicit attribute/relationship metadata
198198
- generated Core Data accessors
199+
- lightweight `relationshipNameCount` accessors for to-many relationships
199200
- generated to-many relationship helper APIs
200201
- typed key/path metadata for sort and predicate construction
201202
- runtime schema metadata for tests and debug workflows

Sources/CDETool/Commands/BootstrapConfigCommand.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ private func relocateBootstrapTemplate(
181181
),
182182
emitExtensionStubs: generate.emitExtensionStubs,
183183
generateInit: generate.generateInit,
184+
generateToManyCount: generate.generateToManyCount,
184185
defaultDecodeFailurePolicy: generate.defaultDecodeFailurePolicy
185186
)
186187
},
@@ -216,6 +217,7 @@ private func relocateBootstrapTemplate(
216217
currentDirectoryURL: currentDirectoryURL
217218
),
218219
generateInit: validate.generateInit,
220+
generateToManyCount: validate.generateToManyCount,
219221
defaultDecodeFailurePolicy: validate.defaultDecodeFailurePolicy,
220222
include: validate.include,
221223
exclude: validate.exclude,

Sources/CDETool/Commands/GenerateCommand.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ struct GenerateCommand: ParsableCommand {
7272
@Option(name: .long, help: "Whether to generate a convenience init (true/false).")
7373
var generateInit: Bool?
7474

75+
@Option(
76+
name: .long, help: "Whether to generate to-many relationship count accessors (true/false).")
77+
var generateToManyCount: Bool?
78+
7579
@Option(name: .long, help: "Decode failure policy: fallbackToDefaultValue/debugAssertNil.")
7680
var defaultDecodeFailurePolicy: ToolingDecodeFailurePolicy?
7781

Sources/CDETool/Commands/ValidateCommand.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ struct ValidateCommand: ParsableCommand {
5858
help: "Whether generated source is expected to include a convenience init (true/false).")
5959
var generateInit: Bool?
6060

61+
@Option(
62+
name: .long,
63+
help:
64+
"Whether generated source is expected to include to-many relationship count accessors (true/false)."
65+
)
66+
var generateToManyCount: Bool?
67+
6168
@Option(
6269
name: .long, help: "Expected decode failure policy: fallbackToDefaultValue/debugAssertNil.")
6370
var defaultDecodeFailurePolicy: ToolingDecodeFailurePolicy?

Sources/CDETool/Support/GenerateCommandSupport.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ enum GenerateCommandSupport {
4141
overrides.format = command.format
4242
overrides.emitExtensionStubs = command.emitExtensionStubs
4343
overrides.generateInit = command.generateInit
44+
overrides.generateToManyCount = command.generateToManyCount
4445
overrides.defaultDecodeFailurePolicy = command.defaultDecodeFailurePolicy
4546
overrides.headerTemplate = command.headerTemplate.map {
4647
makeAbsoluteURL(fromCLIPath: $0).path
@@ -84,6 +85,7 @@ enum GenerateCommandSupport {
8485
headerTemplate: command.headerTemplate.map { makeAbsoluteURL(fromCLIPath: $0).path },
8586
emitExtensionStubs: command.emitExtensionStubs,
8687
generateInit: command.generateInit,
88+
generateToManyCount: command.generateToManyCount,
8789
defaultDecodeFailurePolicy: command.defaultDecodeFailurePolicy
8890
)
8991

0 commit comments

Comments
 (0)