# データ取得と DML、IEntry、Mock

このドキュメントは、ApexEloquent のデータアクセス層を **プロダクションコードでどう書くか** に焦点を当てた使い方ガイドです。全 API のシグネチャ一覧は [API リファレンス: IEloquent / Eloquent / MockEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent) と [API リファレンス: IEntry / Entry / MockEntry](/ja/apex-stem/docs/apex-eloquent-api-entry) を参照してください。

クエリの組み立て方は [Scribe でクエリを組み立てる](/ja/apex-stem/docs/apex-eloquent-scribe-guide) を、他の ApexEloquent トピックは [ApexEloquent ガイド](/ja/apex-stem/docs/apex-eloquent-guide) を参照してください。

## IEloquent とは

`IEloquent` は、データアクセス (SOQL / DML) を抽象化したインターフェースです。本番では `Eloquent` (標準 SOQL / DML のラッパー)、テストでは `MockEloquent` (DB を介さない振る舞いモック) を、同じ呼び出し側コードのまま差し替えられます。

```apex
public with sharing class FindActiveOpportunitiesUsecase {
  private final IEloquent fetchEloquent;

  public FindActiveOpportunitiesUsecase() {
    this(null);
  }

  @TestVisible
  private FindActiveOpportunitiesUsecase(IEloquent fetchEloquent) {
    this.fetchEloquent = fetchEloquent ?? new Eloquent();
  }

  public List<IEntry> invoke() {
    Scribe scribe = Scribe.of(Opportunity.class)
      .field('Id')
      .field('Name')
      .whereEqual('IsClosed', false);
    return this.fetchEloquent.get(scribe);
  }
}
```

「2 つのコンストラクタで本番デフォルトとテスト DI を両立する」やり方は [Layered Constructor Pattern](/ja/apex-stem/docs/layered-constructor-pattern) を参照してください。

## データ取得

### 取得系メソッド

ビジネスロジックでは **`get(scribe)` で `List<IEntry>` を受け取る** のが基本です。0 件なら空リストが返ります。1 件だけ取りたい時は `first` (0 件は `null`) / `firstOrFail` (0 件で例外) が便利です。

`getAsSObject` への変換は、外部 API (`Messaging.SingleEmailMessage` など) に `SObject` インスタンスを直接渡したいなど、`SObject` でないと困る具体的な理由がある時だけの最終手段です。同様に `rawSoql(soql)` は `Scribe` で表現できない特殊な SOQL を投げる時の最終手段で、`MockEntry` の SELECT 漏れ検知が無効になります。

シグネチャの一覧は [API リファレンス: IEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent) を参照。

### IEntry のまま扱う利点

`IEntry` は `SObject` のラッパーで、ApexEloquent の 4 つの仕組みの恩恵を受けられます。

1. **未 SELECT フィールドアクセスの偽陽性検知**: `Scribe` で `field()` し忘れたフィールドに本番コードがアクセスすると、テスト実行時 (`MockEntry` 経由) で例外が出ます。`SObject` に早期変換するとこの保護が外れて、「テストは通るが本番で値が空だった」事故が起きます。
2. **モックデータ構築の自由度**: `MockEntry.set()` はリレーション項目・数式項目・ロールアップ・auto-number など、本来書き込めない non-writable フィールドにも値を書けます。ロジックがそれらに依存する場合でも、`SObject` のままでは作れないテストデータが `IEntry` なら作れます。
3. **取得 → 編集 → 更新が IEntry のまま完結**: `entry.put('Industry__c', value)` でミューテートし、そのまま `eloquent.doUpdate(List<IEntry>)` に渡せます。`SObject` への変換は不要です。
4. **SObject と AggregateResult を区別しなくてよい**: 通常クエリの結果も集計クエリ (`AggregateResult`) の結果も、どちらも `IEntry` として返ります。`entry.get('Industry')` で **オブジェクトのフィールド** を、`entry.get('totalAmount')` で **集計クエリの alias** を、同じ書き方で取り出せます。受け手側は SObject か AggregateResult かを意識する必要がありません。

## IEntry を扱う

### フィールドの読み書き

```apex
List<IEntry> accountEntries = eloquent.get(accountScribe);

for (IEntry accountEntry : accountEntries) {
  // Id と Name は専用 getter (キャスト不要)
  Id accountId = accountEntry.getId();
  String name = accountEntry.getName();

  // その他のフィールドはキャスト必須
  String industry = (String) accountEntry.get('Industry');
  Boolean isActive = (Boolean) accountEntry.get('Active__c');

  // 書き込み
  accountEntry.put('Industry', 'Technology');
}
```

`getId` / `getName` だけ専用 getter があってキャスト不要。それ以外のフィールドは `get(fieldName)` で取り、戻り値をキャストします。書き込みは `put(fieldName, value)`。親や子レコードへのアクセスは [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations) を、シグネチャの一覧は [API リファレンス: IEntry](/ja/apex-stem/docs/apex-eloquent-api-entry) を参照してください。

### 変数命名のコツ

抽象化された型 (`IEntry` / `Scribe` / `IEloquent`) を使う時は、**変数名で中身の SObject を明示する** と読みやすくなります。

```apex
// ❌ r や e のような略は、後で読む人が型を辿らないとわからない
for (IEntry e : eloquent.get(scribe)) { ... }

// ✅ {SObject名}Entry の形に揃える
for (IEntry accountEntry : eloquent.get(accountScribe)) { ... }
```

## DML を実行する

`IEloquent` は標準 DML のラッパーも提供します。`doInsert` / `doUpdate` / `doUpsert` / `doDelete` の 4 種類で、それぞれ `SObject` / `IEntry` / `List<SObject>` / `List<IEntry>` のオーバーロードがあります。

```apex
// Scribe で取得した IEntry をそのまま更新できる
List<IEntry> oppEntries = this.eloquent.label(LBL_FETCH).get(oppScribe);
for (IEntry oppEntry : oppEntries) {
  oppEntry.put('Industry__c', 'Technology');
}
this.eloquent.label(LBL_UPDATE).doUpdate(oppEntries);
```

`List<>` 版を基本とし、bulk 処理に揃えます。単件版は処理対象が常に 1 件と確定している時だけ使います。各オーバーロードのシグネチャは [API リファレンス: IEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent) を参照。

### 実行モード (v3 系)

v3 系では、SOQL / DML の既定が **ユーザーモード** (実行ユーザーの項目・オブジェクト権限を尊重) です。集計・焼き付け・データ移行のように「誰が起こしても完遂すべき処理」だけ、`systemMode()` で明示的にオプトアウトします。

```apex
this.eloquent.systemMode().label(LBL_UPDATE).doUpdate(entries);
```

一度呼ぶとそのインスタンスの以降すべての操作に効きます (sticky)。`MockEloquent` では no-op なので、**単体テストはモードを意識せずに書けます**。

⚠️ `systemMode()` が外すのは **項目・オブジェクト権限だけ**です。**共有 (レコードの可視性) は別軸**で、外すには呼び出し元クラスを `without sharing` にする必要があります。

## MockEloquent でテストする

`MockEloquent` は `IEloquent` のテスト用実装です。Layered Constructor Pattern で Usecase に DI することで、DB に触れずに振る舞いを検証できます。

### コンストラクタ

`new MockEloquent()` で空、`new MockEloquent(entry)` で 1 件、`new MockEloquent(List<IEntry>)` で複数件返す Mock を作れます。

```apex
MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .autoId(1)
  .set('Name', 'Test Opp')
  .set('Amount', 1000);
IEloquent fetchEloquent = new MockEloquent(new List<IEntry>{ oppEntry });
```

### Spy で DML を検証する

`MockEloquent` は本物の `doInsert` / `doUpdate` / `doUpsert` / `doDelete` を実行する代わりに、**渡されたレコードを記録** します。テストで「想定通りの DML が実行されたか」を後から検証できます。

| メソッド | 中身 |
|---|---|
| `upsertedRecordsAt(String label)` | そのラベルで `doInsert` / `doUpdate` / `doUpsert` に渡されたレコード (`List<SObject>`) |
| `deletedCountAt(String label)` | そのラベルでの `doDelete` の件数 (`Integer`) |

> ラベルを使わない (lenient) テストでは `'default'` を渡します。`upsertedRecords` / `deletedCount` という**フィールド**も後方互換で残っていますが `@deprecated` なので、新規コードでは `*At(...)` を使ってください。

```apex
MockEloquent mock = (new MockEloquent())
  .attach(CopyAccountIndustryToOpportunityUsecase.LBL_FETCH, new List<IEntry>{ oppEntry });

(new CopyAccountIndustryToOpportunityUsecase(oppIds, mock)).invoke();

// 想定通り 1 件 update された?
List<SObject> updated = mock.upsertedRecordsAt(CopyAccountIndustryToOpportunityUsecase.LBL_UPDATE);
Assert.areEqual(1, updated.size());

// 想定通りの値が入っている?
Assert.areEqual('Technology', ((Opportunity) updated[0]).Industry__c);
```

### 異常系をモックする

Salesforce では、**失敗を再現するコストが成功を再現するコストより桁違いに高い**という事情があります。実 DML を本当に失敗させようとすると、入力規則を足す、項目権限を落とす、行ロックを競合させる、といった org 側の細工が要ります。遅いうえに、org の状態に依存するので壊れやすい。

`MockEloquent` は、その失敗を**名指しで宣言**できるようにしています。

#### 失敗の指定は 4 つの軸でできている

`failOn*` 系は一見メソッドが多く見えますが、実際には次の 4 軸の組み合わせです。ここを押さえると読み書きが一気に楽になります。

| 軸 | 指定するもの | 既定 |
|---|---|---|
| **何が**失敗するか | `failOnDoUpdate()` / `failOnGet()` / … メソッドごとに 1 本 | — |
| **何で**失敗するか | 引数に渡す `Exception` | 汎用のテスト用例外 |
| **どこで**失敗するか | `.whenLabel(ラベル)` | ラベルなしの呼び出し (`'default'`) 宛て |
| **何回**失敗するか | `failOn*` を積む / `.repeat()` | 次の 1 回だけ |

4 つは直交していて、必要な分だけ足せます。

```apex
MockEloquent mock = (new MockEloquent())
  .failOnDoUpdate(new DmlException('Simulated failure'))  // 何が + 何で
  .whenLabel(YourUsecase.LBL_UPDATE);                     // どこで

try {
  (new YourUsecase(input, mock)).invoke();
  Assert.fail('例外が投げられるはず');
} catch (DmlException e) {
  Assert.isTrue(TraceFlow.isLastAbort());
}
```

#### 「どこで」を絞る意味

`whenLabel` を付けると、**その 1 箇所だけ**を落として、残りは正常に流せます。「集計は成功したが、最後の保存だけ失敗した」といったシナリオがそのまま書けます。DML を複数撃つ Usecase では、これが無いと「どれが落ちたのか」をテストから読み取れません。

`whenLabel` を付けなかった設定は、**ラベルなしの呼び出し (`'default'`) 宛て**になります。本番コードがラベルを使っているなら、`whenLabel` は実質必須です (付け忘れは検出されます。後述)。

なお、ラベルが一致しない呼び出しでは**設定が消費されません**。あとから来る一致した呼び出しが受け取るので、順序を気にせず仕掛けられます。

#### 「何回」は積める

`failOn*` は**キューに積まれます**。同じメソッドに複数回仕掛ければ、その回数だけ順に失敗します。

```apex
// 1 回目と 2 回目の doUpdate は失敗し、3 回目は成功する
MockEloquent mock = (new MockEloquent())
  .failOnDoUpdate(new DmlException('1 回目'))
  .failOnDoUpdate(new DmlException('2 回目'));
```

「2 回失敗して 3 回目に成功する」というリトライの成功パスは、この形で書けます。キューはラベルごとに独立しているので、`whenLabel` を付けた設定でも同じように積めます。

一方 `.repeat()` は、直前に仕掛けた設定を**以降ずっと**繰り返します (回数は指定できません)。

```apex
// 何度呼ばれてもずっと失敗する
MockEloquent mock = (new MockEloquent())
  .failOnDoUpdate(new DmlException('Always fails'))
  .whenLabel(YourUsecase.LBL_UPDATE)
  .repeat();
```

「最大 3 回までリトライ、それでもダメなら中止」というロジックの、**中止側のパス**を確かめたいときに使います。回数を数える必要がないぶん、こちらのほうが意図が明確です。

#### ラベル単位のキューになっている

失敗の設定は **ラベルごとの FIFO キュー** に積まれます (`whenLabel` を付けなかった設定は、ラベルなしの呼び出し = `'default'` 宛てになります)。

そのため、同じメソッドに対して**別々のラベルへ別々の失敗を仕掛けられます**。順番も気にする必要はありません。

```apex
// 取得は 'fetch' で、保存は 'update' で、それぞれ別の理由で落とす
MockEloquent mock = (new MockEloquent())
  .failOnGet(new QueryException('取得に失敗')).whenLabel(YourUsecase.LBL_FETCH)
  .failOnDoUpdate(new DmlException('保存に失敗')).whenLabel(YourUsecase.LBL_UPDATE);
```

#### リトライは同じラベルのまま書ける

ラベルの「1 度だけ」制約が数えるのは、**成功した操作**です。**失敗した操作はラベルを解放する**ので、本番コードが例外を catch して同じラベルでやり直す実装も、そのままテストできます。

```apex
// 1 回目は失敗し、2 回目で成功する — 同じラベルのまま
MockEloquent mock = (new MockEloquent())
  .attach(YourUsecase.LBL_UPDATE, entries)
  .failOnDoUpdate(new DmlException('1 回目')).whenLabel(YourUsecase.LBL_UPDATE);
```

`repeat()` を付ければ、何度リトライしても失敗し続けるので「上限まで試して中止」のパスを確かめられます。

#### 落とし穴: 戻り値を受け取る

テスト設定のビルダー (`attach` / `failOn*` / `whenLabel` / `failSave` / `repeat`) は、いずれも **`this` を変更せず新しいインスタンスを返します**。戻り値を捨てると設定が消えます。

```apex
// ❌ 効かない。mock 自身には何も仕掛かっていない
MockEloquent mock = new MockEloquent();
mock.failOnDoUpdate(new DmlException('...'));

// ✅ チェーンでつなぐか、戻り値を受け直す
MockEloquent mock = (new MockEloquent())
  .attach(YourUsecase.LBL_FETCH, entries)
  .failOnDoUpdate(new DmlException('...'))
  .whenLabel(YourUsecase.LBL_UPDATE);
```

#### 落とし穴: `whenLabel` の付け忘れは検出される

ラベルを使っている (strict mode の) テストで `whenLabel` を付け忘れると、その設定はラベルなしの呼び出し宛てになります。ところが strict mode ではすべての操作にラベルが要るので、**その設定は永久に発火しません**。

黙って無視されると「例外が飛ばないので `Assert.fail` が発火する」という分かりにくい落ち方をするため、その場で診断メッセージ付きの例外になります。付けるべきラベル名もメッセージに出ます。

#### failSave: 例外ではなく「一部だけ保存されない」

`failOn*` が「呼ぶと例外が飛ぶ」なのに対し、`failSave` は**呼び出し自体は成功するが、指定したレコードだけ保存されない**という別種の失敗です。`allOrNone` 付き DML の部分失敗を再現します。

```apex
Id badId = MockEntry.of(Account.class).autoId(2).getId();

MockEloquent mock = (new MockEloquent())
  .failSave(badId, '入力規則で拒否されました');
```

- `allOrNone = false` — そのレコードの `SaveResult` が `success = false` になり、**Spy には積まれません** (保存されていないため)
- `allOrNone = true` — 実際の all-or-nothing DML と同じく、何も記録される前に**操作全体が例外**になります

対象を index ではなく **レコード Id で指名**するので、`MockEntry.autoId()` と対で使います。リストの順序に依存しないため、あとから件数が増えても壊れません。

「失敗したレコードだけログに残して続行する」ような ETL / 増分同期のパスを、org を細工せずに検証できます。

> 各 `failOn*` の全一覧は [API リファレンス: MockEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent) を参照してください。

### 同じ IEloquent で複数のクエリを区別する: ラベル多重化 (重要)

`MockEloquent` は `Scribe` の WHERE 条件を **評価しません**。渡された `IEntry` リストをそのまま返します。これは「`先月のクエリ` と `今期のクエリ` のように、同じ `IEloquent` で複数のクエリを区別したい」ケースを **そのままでは扱えない** ことを意味します。

v2.1 で追加された **`IEloquent.label(String)`** が、これを解決します。1 本の `IEloquent` を **ラベルで多重化** することで、用途別に `IEloquent` を分割 DI する代わりに、同じインスタンスをラベルで仕分けて使えます。

```apex
public with sharing class AggregateAccountActivityUsecase {
  @TestVisible static final String LBL_LAST_MONTH = 'lastMonthEvent';
  @TestVisible static final String LBL_THIS_YEAR = 'thisYearEvent';
  @TestVisible static final String LBL_ACCOUNT_UPDATE = 'accountUpdate';

  private final Set<Id> accountIds;
  private final IEloquent eloquent;

  public AggregateAccountActivityUsecase(Set<Id> accountIds) {
    this(accountIds, null);
  }

  @TestVisible
  private AggregateAccountActivityUsecase(
    Set<Id> accountIds,
    IEloquent eloquent
  ) {
    this.accountIds = accountIds;
    this.eloquent = eloquent ?? new Eloquent();
  }

  public void invoke() {
    List<IEntry> lastMonthEvents = this.eloquent.label(LBL_LAST_MONTH).get(lastMonthScribe);
    List<IEntry> thisYearEvents = this.eloquent.label(LBL_THIS_YEAR).get(thisYearScribe);
    // ... 集計 ...
    this.eloquent.label(LBL_ACCOUNT_UPDATE).doUpdate(updatedAccounts);
  }
}
```

テストでは 1 本の `MockEloquent` に対して `.attach(LBL_LAST_MONTH, ...)` / `.attach(LBL_THIS_YEAR, ...)` で各ラベルにクエリ結果を **個別にプリロード** し、DML の検証は `mock.upsertedRecordsAt(LBL_ACCOUNT_UPDATE)` で取り出します。用途別 DI と同じ独立性を保ったまま、コンストラクタの引数を 1 つに圧縮できます。

なお、`.label()` が初めて呼ばれた瞬間に **opt-in strict mode** が有効になり、以降のすべての操作にラベル付けが必須となります (ラベル忘れの footgun が実行時に塞がれます)。詳しくは [API リファレンス: IEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent) を参照してください。

## MockEntry を構築する

`MockEntry` はテスト用の `IEntry` 実装で、`SObject` では作れないテストデータを作れます。

### 基本

```apex
MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .autoId(1)
  .set('Name', 'Test Opp')
  .set('Amount', 1000);
```

`MockEntry.of(Type)` でエントリ生成、`.set` でフィールドへの書き込み (書き込み不可項目も OK)、`.autoId` で 18 桁の ID 自動生成、`.alias` で後から ID を取り出すための名前付け。シグネチャ一覧は [API リファレンス: MockEntry](/ja/apex-stem/docs/apex-eloquent-api-entry) を参照。

### fetchedBy で SELECT 契約を付ける

`MockEloquent` の `attach` 経由で返すエントリには、`get(scribe)` に渡した `Scribe` から **SELECT 契約が自動的に付きます**。SELECT していない項目に触れば例外になる、あの仕組みです。

一方、**エントリを SUT に直接渡す場合は契約がありません**。`Scribe` を通っていないので、どの項目が SELECT 済みかを知りようがないからです。そこに契約を後付けするのが `fetchedBy` です。

```apex
MockEntry card = MockEntry.of(BusinessCard__c.class)
  .autoId(1)
  .set('CompanyName__c', 'Acme')
  .fetchedBy(RematchCompanyCardsHandler.scope());
```

効くのは主に**バッチ**です。`execute(bc, scope)` のレコードは `IEloquent` を通らないため、本番は実クエリの結果としてプラットフォームが検査してくれますが、テストは自前で組んだエントリなので無検査になります。クエリの組み立てを `@TestVisible` なメソッドに切り出しておけば、本番と同じ `Scribe` をテストから渡せます (詳細は [API リファレンス: Scribe](/ja/apex-stem/docs/apex-eloquent-api-scribe))。

### alias で生成 ID を取り出す

`autoId` で生成された ID を、テストのアサーションで使いたい場合があります。

```apex
MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .alias('opp')
  .autoId(1);

Id oppId = oppEntry.getAliasId('opp');  // 生成された ID を取り出し

// テストで:
Opportunity updated = (Opportunity) mock.upsertedRecordsAt(MyUsecase.LBL_UPDATE)[0];
Assert.areEqual(oppId, updated.Id);
```

### times() で複数件を量産

```apex
List<MockEntry> contactEntries = MockEntry.of(Contact.class)
  .autoId('{#}')
  .set('LastName', 'Contact-{#}')
  .alias('con_{#}')
  .times(3);
// → con_1 / con_2 / con_3 で 3 件、それぞれ Id と LastName が自動展開
```

### template で複数フィールドをまとめてセット

「複数テストで再利用するデフォルト値」を Map にまとめて渡すと、テストで `.set` を繰り返さずに済みます。

```apex
Map<String, Object> defaults = new Map<String, Object>{
  'StageName' => 'Prospecting',
  'CloseDate' => Date.today().addDays(30),
  'Amount' => 1000
};

MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .template(defaults)
  .set('Name', 'Specific name for this test');  // 上書き・個別フィールド追加
```

### 集計結果のモック

集計クエリの結果をモックする時は、`MockEntry.asAggregateResult()` を使います。これは「集計結果は SObject 型に紐づかない」という性質を反映していて、`MockEntry.of(SomeType.class)` のように特定の SObject 型を選ぶ必要がなくなります (読み手の認知ノイズも減ります)。

```apex
MockEloquent eventEloquent = new MockEloquent(
  new List<IEntry>{
    MockEntry.asAggregateResult()
      .set('WhatId', oppAId)
      .set('eventCount', 3),
    MockEntry.asAggregateResult()
      .set('WhatId', oppBId)
      .set('eventCount', 1)
  }
);
```

`set` で渡す値は、本物の SOQL 集計結果と同じく `Decimal` で渡しておくと、ロジック側の `((Decimal) entry.get('eventCount')).intValue()` キャストもそのまま動きます。

### 親子のモック

子から親を見る場合は `setParent`、親から子を見る場合は `setChildren` で構造のまま組み立てられます。コードのインデントがそのままリレーション構造を表すので、後から読み返しても「この親にぶら下がっている子」が一目で分かります。

```apex
// 親 Account に複数の Contact / Opportunity をぶら下げる
MockEntry accountEntry = MockEntry.of(Account.class)
  .alias('acc').autoId(1)
  .set('Name', 'Acme Corporation')
  .setChildren('Contacts', new List<MockEntry>{
    MockEntry.of(Contact.class).autoId(1).set('FirstName', 'John'),
    MockEntry.of(Contact.class).autoId(2).set('FirstName', 'Jane')
  })
  .setChildren('Opportunities', new List<MockEntry>{
    MockEntry.of(Opportunity.class).autoId(1).set('Name', 'Deal 1').set('Amount', 100000),
    MockEntry.of(Opportunity.class).autoId(2).set('Name', 'Deal 2').set('Amount', 150000)
  });
```

逆方向 (子 Opportunity から親 Account を見る) は `setParent`:

```apex
MockEntry oppEntry = MockEntry.of(Opportunity.class)
  .autoId(1)
  .set('Name', 'Major Deal')
  .setParent('AccountId',
    MockEntry.of(Account.class).set('Name', 'Acme Corporation').set('Type', 'Customer')
  );
```

親子の `Id` 連結 (例: `Contact.AccountId` に親の Id を埋める) は `MockEntry` が内部で処理するので、手で埋める必要はありません。

⚠️ `Scribe` 側で子リレーション名を `relationName('CustomOpportunities__r')` で指定している場合は、`setChildren` の第 1 引数も同じ文字列を使ってください。リレーション操作の全体像 (多対多や Junction Object も含む) は [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations) を参照。

## 次に読む

- [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations): リレーションの取得とモック
- [Scribe でクエリを組み立てる](/ja/apex-stem/docs/apex-eloquent-scribe-guide): クエリビルダーの全体像
- [Layered Constructor Pattern](/ja/apex-stem/docs/layered-constructor-pattern): `IEloquent` を Usecase に DI する設計 (差し替えの継ぎ目をどこに置くか)
- [テスト戦略](/ja/apex-stem/docs/test-strategy): `MockEloquent` を中心に据えた Usecase 単体テストの位置づけ
- [ApexEloquent ガイド](/ja/apex-stem/docs/apex-eloquent-guide): ApexEloquent ガイドの目次に戻る
