# 親子・量産・参照のパターン

[SBlueprint で単一レコードを宣言する](/ja/apex-stem/docs/apex-blueprint-sblueprint-guide) では、1 レコードを単独で宣言する基本 5 メソッドを扱いました。このページでは、そこから一歩進めて **複数レコードを構造的に組み立てる** ためのパターンを扱います。

具体的には以下のトピックです:

- `withChildren`: 親 blueprint の中に子をネストする
- `times(n)` + `{#}` プレースホルダ: 連番つきの量産
- `use` の offset 指定: 量産した親レコードの一部だけを子に紐付ける
- `{P0}` / `{P1}`: ネスト内で「直近の親」「親の親」を参照する
- `parentIdField`: 親が複数 lookup を持つときの曖昧解消
- `sharedWith`: 手動共有の宣言と、量産への追随

## withChildren: 親の下に子をネストする

`withChildren(child)` は、1 つの blueprint の中に「この blueprint の子レコードたち」を入れ子で書ける API です。コードのインデント階層がそのままデータ階層になるので、親子関係が一目で読み取れます。

### 1:1 のシンプルな親子

```apex
SBlueprint.of(Account.class)
    .template(Blueprints.accBasic())
    .alias('parentAccount')
    .withChildren(
        SBlueprint.of(Contact.class)
            .set('LastName', 'TestContact')
            .alias('childContact')
    );
```

ポイント:

- 子の `AccountId` への親 Id 転記は **自動**。`.use(...)` を書く必要は無い
- `withChildren` の引数の中で子の blueprint を **インライン定義** することで、「この親の下にぶら下がる子」という構造が視覚化される
- 子側で `.alias(...)` を付けておけば、`getByAlias('childContact')` で取り出せる

### 1:N の親子 (`times` との組み合わせ)

`withChildren` の中で `.times(n)` を使うと、同じ親に複数の子をぶら下げられます。

```apex
SBlueprint.of(Account.class)
    .alias('parentAccount')
    .template(Blueprints.accBasic())
    .withChildren(
        SBlueprint.of(Contact.class)
            .set('LastName', 'Contact-{#}')
            .alias('con_{#}')
            .times(3)
    );
```

`con_1` / `con_2` / `con_3` の 3 件の `Contact` が、すべて同じ `Account` に紐付いた状態で生成されます。alias と `LastName` の両方で `{#}` を使っていることで、後から `getByAlias('con_2')` で個別に取り出せます。

### ネストしてさらに孫まで

`withChildren` は **ネスト可能** です。「Account の下に Contact、Contact の下に Case」のような 3 階層も自然に書けます。

```apex
SBlueprint.of(Account.class)
    .alias('acc')
    .template(Blueprints.accBasic())
    .withChildren(
        SBlueprint.of(Contact.class)
            .set('LastName', 'TestContact')
            .alias('con')
            .withChildren(
                SBlueprint.of(Case.class)
                    .set('Subject', 'TestCase')
                    .alias('case')
            )
    );
```

### 同じ親の下に複数種類の子を並べる

1 つの親に **異なる SObject 型の子** を並べたい場合は、`.withChildren(...)` を続けて呼び出します。

```apex
SBlueprint.of(Account.class)
    .template(Blueprints.accBasic())
    .alias('acc')
    .withChildren(
        SBlueprint.of(Contact.class)
            .set('LastName', 'TestContact')
            .alias('childContact')
    )
    .withChildren(
        SBlueprint.of(Opportunity.class)
            .set('Name', 'TestOpportunity')
            .set('StageName', 'Prospecting')
            .set('CloseDate', Date.today().addDays(30))
            .alias('childOpp')
    );
```

`Contact` と `Opportunity` が、それぞれ自分の `AccountId` に親 Id を持って同じ `Account` の下にぶら下がる形で生成されます。子側で `.times(...)` も使えるので、「Account の下に Contact 3 件 + Opportunity 2 件」のような不揃いな構造も自然に表現できます。

## Multiplication: 上位階層の times が下位に伝播する

`withChildren` と `.times(n)` を組み合わせるときに重要な仕様があります。**上位階層で `.times(n)` を入れて親を量産すると、その下にぶら下がる子は親ごとに丸ごと再生成される** ため、件数は掛け算で増えます。

### 2 階層: 親 N × 子 M

```apex
SBlueprint.of(Account.class)
    .set('Name', 'Acc-{#}')
    .alias('acc_{#}')
    .times(2)                      // 親 2 件
    .withChildren(
        SBlueprint.of(Contact.class)
            .parentIdField('AccountId')
            .set('LastName', 'Con-{#}')
            .alias('con_{#}')
            .times(2)              // 各親に 2 件ずつ
    );
```

結果: `Account` が 2 件、`Contact` が **2 × 2 = 4 件** 生成されます。

### 3 階層以上: そのまま指数的に伸びる

階層を増やすと、件数はそのまま掛け算で積み上がっていきます。

```apex
SBlueprint.of(Account.class)
    .times(2)                          // 親 2 件
    .withChildren(
        SBlueprint.of(Contact.class)
            .parentIdField('AccountId')
            .times(2)                  // 子 2 件 / 親 → 合計 4 件
            .withChildren(
                SBlueprint.of(Case.class)
                    .parentIdField('ContactId')
                    .times(2)          // 孫 2 件 / 子 → 合計 8 件
            )
    );
```

総レコード件数: `Account` 2 件 + `Contact` 4 件 + `Case` **2 × 2 × 2 = 8 件**。「階層の `.times(...)` 値の積」が末端のレコード件数になる、と覚えておけば見通しが立てやすくなります。

> 階層が深くなると **レコード数が指数的に増える** ことに注意してください。5 階層で各 `.times(3)` を重ねると `3⁵ = 243` 件のレコードが生成され、ガバナ制限の DML 行数 (10000) を圧迫します。「ちょっと多めに」のつもりが膨大な件数に化けやすいので、各階層の `.times(...)` は意識的に絞り込むのが安全です。

## times + {#}: 連番つきの量産

`.times(n)` は同じ blueprint を `n` 件量産します。「3 件の Contact」「10 件の Account」のようなケースで、`for` ループを書かずに 1 行で済ませられます。

`{#}` プレースホルダは `.set(...)` の値や `.alias(...)` の引数に埋め込んで使います。`times(n)` で展開されるとき、`{#}` が `1`, `2`, `3`, ... と置き換わります。

```apex
SBlueprint.of(Contact.class)
    .set('LastName', 'Contact-{#}')
    .alias('con_{#}')
    .times(3);
```

生成されるのは:

| alias | LastName |
|---|---|
| `con_1` | `Contact-1` |
| `con_2` | `Contact-2` |
| `con_3` | `Contact-3` |

### アルファベット連番: `{A}` / `{a}`

`{#}` の数値連番に加えて、**アルファベット連番** 用の `{A}` / `{a}` プレースホルダもサポートされています。大文字版が `{A}` で `'A'` / `'B'` / `'C'` ...、小文字版が `{a}` で `'a'` / `'b'` / `'c'` ... に展開されます。

```apex
SBlueprint.of(Account.class)
    .set('Name', 'Acc-{A}')
    .alias('acc_{a}')
    .times(3);
```

生成されるのは:

| alias | Name |
|---|---|
| `acc_a` | `Acc-A` |
| `acc_b` | `Acc-B` |
| `acc_c` | `Acc-C` |

「人間が読んで違いを判別したい」ようなテストデータ (テストレポートに出るラベル等) で、数字より文字記号の方が読みやすい場合に使い分けます。

### startAt / interval: 連番の起点と刻みを変える

`.set(...)` / `.alias(...)` には、`{#}` の **起点** と **刻み** を指定する追加引数があります。

```apex
SBlueprint.of(Account.class)
    .set('Name', 'Acc-{#}', 10, 2)   // 10, 12, 14
    .alias('acc_{#}', 10, 2)         // acc_10, acc_12, acc_14
    .times(3);
```

「N 件目から始まる連番が欲しい」「偶数番号だけ作りたい」のような細かい要件にも対応できます。

## use の offset 指定: 一部の親だけを子に紐付ける

`.times(n)` で **量産した親レコード** の中から、一部だけを子に紐付けたいケースがあります。例えば「10 件の Account のうち、後半 5 件 (6 〜 10) だけに Contact を紐付ける」のような要件です。

`use` には 4-5 引数のオーバーロードがあり、参照する alias の **開始番号** と **刻み** を指定できます。

**シグネチャ:** `.use(String alias, String fromField, String toField, Integer startAt [, Integer interval])`

```apex
SOrchestrator.start()
    .add(
        SBlueprint.of(Account.class)
            .alias('acc_{#}')
            .template(Blueprints.accBasic())
            .times(10)                              // acc_1 〜 acc_10
    )
    .add(
        SBlueprint.of(Contact.class)
            .set('LastName', 'Con-{#}')
            .alias('con_{#}', 6)                    // con_6 〜 con_10
            .use('acc_{#}', 'Id', 'AccountId', 6)   // acc_6 〜 acc_10 を参照
            .times(5)
    );
```

生成される子は `con_6` 〜 `con_10` の 5 件で、それぞれ `acc_6` 〜 `acc_10` を親に持つ形になります。「全件揃って同じ親を持つ」形ではなく、「不揃いな対応」を表現するのにこのオーバーロードが効きます。

## {P0} / {P1} / ...: 上位階層の値を参照する

### なぜこのプレースホルダが必要か

各階層で `.times(...)` を使ったネストでは、末端のレコードから見た「自分の真の親」は **生成のたびに変わるインスタンス** になります。たとえば:

- 親 (Account) `.times(2)`
- 子 (Contact) `.times(2)`
- 孫 (Case) `.times(2)`

このとき孫は計 8 件、子は 4 件 (各親に 2 件ずつ)、親は 2 件です。各孫から見た「自分の真の親 (Contact)」は 4 件の Contact の中の特定の 1 件です。

ところが Contact 側で `.alias('child_{#}')` のように単純な `{#}` プレースホルダで alias を付けると、「Acme-1 の下の `child_1`」と「Acme-2 の下の `child_1`」が **同じ alias を二重宣言したことになり、alias 重複エラーで実行が止まります**。

抜け道はあります。`.alias('{P0}_child_{#}')` のように **親の alias を埋め込んだ複合 alias** を作れば、`__Account_0_1___child_1` / `__Account_0_1___child_2` / `__Account_0_2___child_1` / `__Account_0_2___child_2` のような 4 通りの一意な alias が払い出され、孫から `.use('__Account_0_1___child_1', ...)` のように指せます。

ただ、この方針には欠点があります:

- 孫を `.use(...)` する側で **「自分の真の親の alias 文字列」を頭の中で組み立てる** 必要が出る
- 階層構造を後で変更すると、alias 文字列の組み立てロジックも全部書き直し
- 結果として、テストコードが「データ構造」ではなく「alias 文字列パズル」のように読めてしまう

これを構造的に避けるのが `{P0}` / `{P1}` / `{P2}` ... プレースホルダです。「上から N 番目 (0 始まり) の階層にある、**自分にとっての真の親**」を SOrchestrator が内部で階層的に自動解決してくれるので、alias を打つ必要も、alias 文字列を頭で組み立てる必要もありません。

### 数え方: ルートからの絶対深度

数え方は **「自分から上方向への距離」ではなく、ルートから数えた絶対深度** です:

- `{P0}`: ルート (一番外側の親、階層 0)
- `{P1}`: ルートの 1 つ下 (階層 1)
- `{P2}`: さらにその下 (階層 2)
- 以下、階層が深くなるごとに番号が増える

つまり「4 階層目から階層 1 の値を参照したい」ときは `{P1}` を指定します (「現在地から上に 3 つ」ではないので注意)。

### 最小例: 2 階層で `{P0}` を使う

まずは最もシンプルな 2 階層構造で、`{P0}` (= ルート) を参照するパターンから見ます。子 Contact の `Description` に親 Account の `Name` を流し込むだけのケースです。

```apex
SBlueprint.of(Account.class)             // P0 (ルート)
    .set('Name', 'Acme')
    .withChildren(
        SBlueprint.of(Contact.class)
            .set('LastName', 'TestContact')
            .use('{P0}', 'Name', 'Description')  // ルート Account の Name を Description に
    );
```

Contact の Description には `'Acme'` が入ります。`{P0}` が「ルート Account」を指していて、親側で `.alias(...)` を打つ必要がありません。

「親を 1 件作って、その値を子に引き写すだけ」という用途であれば、ここまでの理解で十分です。階層が深くなって `.times(...)` で量産が絡んだ、より高度な使い方は次の Example で見ます。

### 例: 孫が「自分の真の親」の値を引き写す

3 階層 (親 / 子 / 孫) で、孫 Case の `Subject` に **自分の真の親 Contact の LastName** を入れます。

```apex
SBlueprint.of(Account.class)                            // P0 (階層 0 / ルート)
    .set('Name', 'Acme-{#}')
    .times(2)
    .withChildren(
        SBlueprint.of(Contact.class)                    // P1 (階層 1)
            .parentIdField('AccountId')
            .set('LastName', 'Contact-{#}')
            .times(2)
            .withChildren(
                SBlueprint.of(Case.class)               // P2 (階層 2 = 孫 Case)
                    .parentIdField('ContactId')
                    .use('{P1}', 'LastName', 'Subject') // ← 自分の真の親 Contact の LastName
                    .times(2)
            )
    );
```

生成されるレコードと、孫 Case の Subject (= 自分の真の親 Contact の LastName) は次のようになります。

| Account | Contact (真の親) | Case (孫) Subject |
|---|---|---|
| `Acme-1` | `Contact-1` (Acme-1 の下) | `Contact-1` |
| `Acme-1` | `Contact-2` (Acme-1 の下) | `Contact-2` |
| `Acme-2` | `Contact-1` (Acme-2 の下) | `Contact-1` |
| `Acme-2` | `Contact-2` (Acme-2 の下) | `Contact-2` |

それぞれの Contact の下に孫 2 件ずつあるので、孫 Case は計 8 件、上記の組 4 通り × 各 2 件ずつ、という分布になります。同じ `Contact-1` という LastName を持つ Contact が 2 件 (Acme-1 の下 / Acme-2 の下) に存在しますが、各孫は **自分にぶら下がっている本物の親** から値を受け取ります。

ポイント:

- alias を打たなくても、**「自分にとっての真の親」が階層的に自動解決される**
- 数え方はルートからの絶対深度 (現在地から上方向への距離ではない)
- 親 Id の転記は `withChildren` の自動処理に任せ、任意のフィールド値を引き写したいときだけ `{Pn}` を使う

## parentIdField: 親が複数 lookup を持つときに明示する

子オブジェクトが **複数の lookup 候補** を持っている場合 (例: `Contact` が `AccountId` と `CustomAccount__c` の両方を持つ)、`withChildren` だけでは「どちらの lookup に親 Id を入れるか」を SOrchestrator が判断できません。このとき `.parentIdField(...)` で明示します。

**シグネチャ:** `.parentIdField(String fieldName)`

```apex
SBlueprint.of(Account.class)
    .alias('acc')
    .withChildren(
        SBlueprint.of(Contact.class)
            .parentIdField('AccountId')    // どの lookup に親 Id を入れるかを明示
            .template(Blueprints.conBasic())
    );
```

複数 lookup が無いオブジェクトでは `.parentIdField(...)` を書く必要はありません。「曖昧でエラーになったら付ける」という後付けの保険として覚えておけば十分です。

## sharedWith: 共有も量産に追随する (v2.0.0+)

`.sharedWith(user, accessLevel)` は手動共有を**最終状態として宣言**します。`Foo__Share` / `AccountShare` のレコードを自分で組み立てる必要も、親より後に insert する順序を気にする必要もありません。

**シグネチャ:** `.sharedWith(User user, String accessLevel)` (`accessLevel` は `'Read'` または `'Edit'`)

このページの文脈で重要なのは、**共有宣言がここまで説明してきた量産の仕組みにそのまま乗る**ことです。`times` で増えた親には、共有も同じ数だけ増えます。

```apex
SBlueprint.of(Invoice__c.class)
    .set('Name', 'Invoice-{#}')
    .alias('inv_{#}')
    .owner(admin)
    .sharedWith(rep, 'Read')
    .times(5);
// → Invoice__c 5 件と、それぞれに対応する Invoice__Share 5 件
```

ネストした子に付けた共有も同じで、**親の `times` に掛け算されて増えます**。

```apex
SBlueprint.of(Account.class)
    .set('Name', 'Acme-{#}')
    .times(2)
    .withChildren(
        SBlueprint.of(Invoice__c.class)
            .owner(admin)
            .sharedWith(rep, 'Read')
            .times(3)          // 請求書 6 件 → 共有も 6 件
    );
```

これは共有だけの特別扱いではありません。`sharedWith` は内部で **`use()` で親に繋がれた兄弟 blueprint** を組み立てているだけなので、`times` の伝播も `{Pn}` の解決も、通常の子と完全に同じ経路をたどります (詳細は [依存解決の仕組み](/ja/apex-stem/docs/apex-blueprint-dependency-resolution-deep-dive))。

### 宣言が矛盾していれば DML の前に落ちる

共有は「書けば必ず通る」ものではないので、宣言の時点で成立しないケースは `create()` の解析段階で `ApexBlueprintException` になります。

| 状況 | 理由 |
|---|---|
| 組織の共有設定 (OWD) が `Public` のオブジェクト | 手動共有レコード自体が存在しない (`Foo__Share` が見つからない) |
| `owner()` で指定した本人への共有 | オーナーは既に全権を持つため、Salesforce が手動共有を拒否する |
| `accessLevel` が `'Read'` / `'Edit'` 以外 | 指定ミス |

いずれも **DML には到達しません**。「insert してみたら共有が入っていなかった」ではなく、宣言した時点で理由つきで止まります。

> 📌 共有をテストする相手側のユーザー (ペルソナ) の作り方は [SPersona の API リファレンス](/ja/apex-stem/docs/apex-blueprint-api-spersona) を参照してください。

## 例: 親 1 件 + 子 3 件 + 孫 1 件

これまでのパターンを 1 つのテストに統合した例です。

```apex
@isTest
static void testAccountWithContactsAndCase() {
    SOrchestrator orchestrator = SOrchestrator.start()
        .add(
            SBlueprint.of(Account.class)
                .template(Blueprints.accBasic())
                .set('Name', 'ParentAccount')
                .alias('acc')
                .withChildren(
                    SBlueprint.of(Contact.class)
                        .set('LastName', 'Contact-{#}')
                        .use('{P0}', 'Name', 'Description')  // ルート Account (P0) の Name を Description に
                        .alias('con_{#}')
                        .times(3)
                )
        )
        .add(
            SBlueprint.of(Case.class)
                .set('Subject', 'TestCase')
                .use('con_2', 'Id', 'ContactId')             // 2 番目の Contact に紐付け
                .alias('targetCase')
        );
    orchestrator.create();

    Account parent = (Account) orchestrator.getByAlias('acc');
    Contact con2 = (Contact) orchestrator.getByAlias('con_2');
    Case targetCase = (Case) orchestrator.getByAlias('targetCase');

    Assert.areEqual(3, [SELECT COUNT() FROM Contact WHERE AccountId = :parent.Id]);
    Assert.areEqual('ParentAccount', con2.Description);
    Assert.areEqual(con2.Id, targetCase.ContactId);
}
```

ポイント:

- `withChildren` のネストで「Account に Contact が 3 件ぶら下がる」構造をそのまま視覚化
- `use('{P0}', 'Name', 'Description')` で親 Account の Name を子 Contact の Description に流し込み
- `{#}` 付きの alias (`con_{#}`) で、後から `'con_2'` という形で 2 番目の Contact を取り出して Case に紐付け
- テスト本体には複雑な手続きが無く、「最終的に作りたいデータ構造」がそのまま読める

## 例: 木をまたぐ共有参照 (ダイヤ型の依存)

`withChildren` は「木」を表現しますが、実際の結合テストでは **木の外にある共有レコードを、深くネストした子から参照したい** ことがあります。たとえば `Account → Opportunity → Quote → QuoteLineItem` の 4 階層で、最深部の `QuoteLineItem` が共有の `Product2` を参照するようなケースで、これは木ではなく「ダイヤ型」(DAG) の依存になります。

親子の鎖は `withChildren` で、木をまたぐ参照は `use` で宣言し、共有レコードは別の `add` で登録して alias で参照するだけです。`add` の順序は自由で、SOrchestrator が「`Product2` と `Account` を `QuoteLineItem` より先に」とトポロジカルソートで並べてくれます。

```apex
@isTest
static void testQuoteLineItemReferencesSharedProduct() {
    SOrchestrator orchestrator = SOrchestrator.start()
        .add(
            SBlueprint.of(Product2.class)
                .set('Name', 'Widget')
                .alias('product')                        // 木の外に共有レコード
        )
        .add(
            SBlueprint.of(Account.class)
                .template(Blueprints.accBasic())
                .withChildren(
                    SBlueprint.of(Opportunity.class)
                        .template(Blueprints.oppBasic())
                        .withChildren(
                            SBlueprint.of(Quote.class)
                                .set('Name', 'Q-2026')
                                .withChildren(
                                    SBlueprint.of(QuoteLineItem.class)
                                        .template(Blueprints.qliBasic())
                                        .set('Quantity', 3)
                                        .use('product', 'Id', 'Product2Id')  // 木をまたぐ参照
                                        .alias('qli')
                                )
                        )
                )
        );
    orchestrator.create();

    Product2 product = (Product2) orchestrator.getByAlias('product');
    QuoteLineItem qli = (QuoteLineItem) orchestrator.getByAlias('qli');
    Assert.areEqual(product.Id, qli.Product2Id);
}
```

ポイント:

- 共有レコード (`Product2`) は木の枝ではないので、`withChildren` ではなく **別の `add` + `alias`** で登録する
- 木をまたぐ参照は `use('product', 'Id', 'Product2Id')` の 1 行で宣言。親子の鎖 (`withChildren`) と組み合わせると、依存グラフは木ではなく「ダイヤ型」になる
- `add` の順序は読みやすい順でよい。insert 順序は SOrchestrator がトポロジカルソートで解決するので、利用者は一切気にしない
- `QuoteLineItem` の必須項目 (`PricebookEntryId` / `UnitPrice` など、実際には `PricebookEntry` を別途 `add` で用意する必要がある) は `Blueprints.qliBasic()` の `template` に寄せ、テスト本体には検証したい差分 (`Quantity`) と参照 (`use`) だけを残す

## 関連ドキュメント

- [SBlueprint で単一レコードを宣言する](/ja/apex-stem/docs/apex-blueprint-sblueprint-guide): 基本 5 メソッド
- [SOrchestrator で依存解決と実 DML 挿入](/ja/apex-stem/docs/apex-blueprint-sorchestrator-guide): 実行エンジン
- [API リファレンス: SBlueprint](/ja/apex-stem/docs/apex-blueprint-api-sblueprint): 全メソッドのシグネチャ網羅
- [ApexBlueprint ガイドへ戻る](/ja/apex-stem/docs/apex-blueprint-guide)
