# API リファレンス: IEntry / Entry / MockEntry

`IEntry` は ApexEloquent のレコードラッパー (インターフェース)。`Eloquent.get(scribe)` などの戻り値が `List<IEntry>` で返ります。本番は `Entry`、テストは `MockEntry` を使います。

使い方や典型シナリオは [データ取得と DML、IEntry、Mock](/ja/apex-stem/docs/apex-eloquent-data-access) と [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations) を参照してください。

## 3 つの関係

```
IEntry  (interface)        ← Usecase / ビジネスロジックが扱う型
   ↑
   ├── Entry               ← 本番。SObject や AggregateResult を内包
   └── MockEntry           ← テスト。SObject 型に縛られず、自由にフィールドを set できる
```

`IEntry` は `SObject` ラッパーであり、SObject では作れないテストデータ (数式項目・ロールアップ・親リレーション・auto-number への書き込み) を `MockEntry.set` 経由で表現できます。また `AggregateResult` も同じ `IEntry` インターフェースで扱えるので、SObject クエリと集計クエリの取り出し側コードを揃えられます。

## IEntry (インターフェース)

### フィールドアクセス

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `get(String fieldName)` | `Object` | 任意フィールドの値取得 (キャスト必須) |
| `put(String fieldName, Object value)` | `void` | 値の書き込み |
| `getId()` | `Id` | Id 専用 getter (キャスト不要) |
| `getName()` | `String` | Name 専用 getter (キャスト不要) |
| `getRecord()` | `SObject` | 内包する `SObject` を取り出す (最終手段) |
| `setRecord(SObject record)` | `IEntry` | 内部用。SObject 差し替え |
| `setFieldStructure(FieldStructure fs)` | `IEntry` | 内部用。SELECT 漏れ検知のスキーマ設定 |
| `setDescribeResult(Schema.DescribeSObjectResult)` | `void` | 内部用 |

```apex
IEntry oppEntry = eloquent.first(oppScribe);
Id oppId = oppEntry.getId();
String name = oppEntry.getName();
String industry = (String) oppEntry.get('Industry__c');
oppEntry.put('Status__c', 'Active');
```

### リレーション (読み取り)

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `getParent(String parentIdFieldName)` | `IEntry` | 親レコードを取得 (例: `'AccountId'`) |
| `getChildren(String name)` | `List<IEntry>` | 子レコードのリスト |
| `getThrough(String junctionName, String relatedKey)` | `List<IEntry>` | 多対多。Junction オブジェクト経由で関連先を取得 |

`getChildren` / `getThrough` の第 1 引数は、**オブジェクト名と relationship 名のどちらでも解決されます**。まずオブジェクト名として探し、見つからなければ relationship 名として解決します。

```apex
// どちらでも同じ結果になる
List<IEntry> opps = accountEntry.getChildren('Opportunity');    // オブジェクト名
List<IEntry> opps = accountEntry.getChildren('Opportunities');  // relationship 名
```

`Scribe` 側で `relationName(...)` を明示した場合は、その名前でも引けます。

#### 非推奨: `*ByRelationName`

| シグネチャ | 代替 |
|---|---|
| `getChildrenByRelationName(String childRelationName)` | `getChildren(String)` |
| `getThroughByRelationName(String junctionRelationName, String relatedKey)` | `getThrough(String, String)` |

上記の名前解決が統合されたことで、relationship 名専用の入口は不要になりました。後方互換のため残っていますが、**新規コードでは使わないでください**。将来のバージョンで削除される可能性があります。

## Entry (Production)

`IEntry` を実装する本番クラス。SObject 由来と AggregateResult 由来の両モードをサポートし、`Scribe` の選択フィールドに基づいた SELECT 漏れ検知を内部で行います。**追加の public メソッドは無し**。

`Eloquent.get(scribe)` の戻り値の各要素は `Entry` インスタンスです。

### バッチ状態に載せられます (v3.4.1+)

`IEntry` を `Database.Stateful` のバッチのインスタンス変数として持ち回れます。

> ⚠️ **v3.4.0 以前は、チャンクをまたぐ時点で `SerializationException` になることがありました。** 内部に持つ `Schema.DescribeSObjectResult` がシリアライズ不可のためです。しかも**キャッシュが温まっているかどうかで発生が変わる**ため (その型で最初にキャッシュミスしたインスタンスだけが値を持つ)、同じコードが通ったり落ちたりしました。v3.4.1 で該当フィールドを `transient` にし、必要なときに都度導出する形に変えて解消しています。

## MockEntry (Mock 拡張)

テスト用の `IEntry` 実装。`IEntry` の契約に加え、テストデータ構築用の拡張 API を多数持ちます。

### ファクトリ

| シグネチャ | 用途 |
|---|---|
| `MockEntry.of(System.Type recordType)` | 通常の SObject 型でエントリを作る (`MockEntry.of(Account.class)`) |
| `MockEntry.asAggregateResult()` | 集計クエリ結果用のエントリを作る (SObject 型に紐づかない) |
| `MockEntry.asAggregateResult(Map<String, Object> fieldToValue)` | 同上、初期値付き |

### フィールド操作

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `set(String fieldName, Object value)` | `MockEntry` | フィールドへの値設定 (書き込み不可項目も OK) |
| `template(Map<String, Object> fieldToValue)` | `MockEntry` | 複数フィールドを Map で一括セット |

```apex
MockEntry accEntry = MockEntry.of(Account.class)
  .template(new Map<String, Object>{
    'Name' => 'Acme Co.',
    'Industry' => 'Technology'
  })
  .set('Active__c', true);
```

**SObject フィールド名 typo の即時検知**: `set` / `add` / `setParent` / `addParent` で **存在しない SObject フィールド名** を渡すと、即座に `ApexEloquentException` が投げられます。

```apex
MockEntry.of(Account.class).set('Naame', 'foo');
// → ApexEloquentException ("The field 'Naame' does not exist on the SObject.")
```

これまでは typo は `fieldToValue` に黙って入り、SUT が正しいフィールド名 (`get('Name')`) で取りに来た時に `null` が返って、テストは通るが本番で落ちる、という偽陽性の温床でした。今は **テストセットアップの瞬間に叩き出される** ので、セットアップ起源の偽陽性が構造的に塞がれます。

なお `put` は標準 `SObject.put` 経由でフィールド名検証が走るので、元から typo は弾かれており、挙動変更はありません。

> `set` には `add(String, Object)` という同義のメソッドもあります。挙動は同じ (どちらも新しい `MockEntry` を返す) なので、新規コードは `set` に寄せてください。`setParent` / `setChildren` にも同様に `addParent` / `addChildren` があります。

### SELECT 契約を焼き付ける: fetchedBy

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `fetchedBy(Scribe scribe)` | `MockEntry` | その `Scribe` の SELECT 句を、このエントリの契約として焼き付ける |

`MockEloquent` 経由で返すエントリは、**`get(scribe)` に渡した `Scribe` から契約が自動的に付きます**。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) の「Scribe を切り出すと、テストでも SELECT 漏れ検知が効く」を参照してください。

> 本番の `Entry` に `fetchedBy` は要りません。実 SOQL の結果は、未 SELECT の項目に触れた時点でプラットフォーム自身が例外を投げるためです。これはモック側だけに必要な厳密化の道具です。

### リレーション (組み立て)

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `setParent(String parentIdFieldName, MockEntry parent)` | `MockEntry` | 親レコードをぶら下げる |
| `setChildren(String name, List<MockEntry> children)` | `MockEntry` | 子レコードリストをぶら下げる |

`setChildren` の第 1 引数は、Scribe 側で `relationName` を指定したならその名前、指定していないなら **オブジェクト名** (`'Opportunity'` のように単数、`__r` なし)。Scribe と MockEntry で同じキーを使う必要があります。

### ID 自動生成

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `autoId(Integer suffix)` | `MockEntry` | 18 桁の Id を自動生成 (数値サフィックス) |
| `autoId(String suffix)` | `MockEntry` | 18 桁の Id を自動生成 (文字列サフィックス、`{#}` などのプレースホルダ展開対応) |

### 量産 (times)

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `times(Integer count)` | `List<MockEntry>` | テンプレートから `count` 件展開。プレースホルダ `{#}` `{A}` `{a}` を連番に置換 |
| `times(Integer count, Integer startAt)` | `List<MockEntry>` | 連番の開始番号を指定 |
| `times(Integer count, Integer startAt, Integer interval)` | `List<MockEntry>` | 連番の開始番号と increment を指定 |

```apex
List<MockEntry> contacts = MockEntry.of(Contact.class)
  .autoId('{#}')
  .set('LastName', 'Contact-{#}')
  .alias('con_{#}')
  .times(3);  // con_1, con_2, con_3
```

**ネストした掛け算には未対応**: `MockEntry.of(Account.class).times(2)` の中で子側に `times(4)` を持たせて「親 2 件 × 子 4 件ずつ」のように **階層的に掛け算で展開する** ことはできません。子側を個別に `setChildren` 内で列挙する形で組み立ててください。

### alias (生成 Id の取り出し)

| シグネチャ | 戻り値 | 用途 |
|---|---|---|
| `alias(String name)` | `MockEntry` | このエントリに名前を付ける |
| `getByAlias(String name)` | `MockEntry` | 再帰検索で alias から MockEntry を取得 |
| `getAliasId(String name)` | `Id` | alias の自動生成 Id を取得 (アサーション用に便利) |

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

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

### 検知の無効化 (2 系統)

`MockEntry` の検知には **2 系統** あり、それぞれ独立した escape hatch を持ちます。どちらも原則使いません (検知が偽陽性を塞いでいるため)。

| シグネチャ | 戻り値 | 無効化するもの |
|---|---|---|
| `withoutFieldValidation()` | `MockEntry` | **Scribe FieldStructure** チェック (`Scribe` で SELECT されていないフィールドへの `get(...)` アクセスを許可) |
| `withoutSObjectFieldValidation()` | `MockEntry` | **SObject フィールド名** チェック (`set` / `add` / `setParent` / `addParent` で実在しないフィールド名を許可) |

```apex
// 例: 合成フィールド名でデータを保持したいレアケース
MockEntry.of(Account.class)
  .withoutSObjectFieldValidation()
  .set('Synthetic__c', 'value');  // Account には存在しないが許可される
```

**この 2 つは別フラグ**です。`withoutFieldValidation()` で Scribe チェックを緩めても、SObject typo 検知は閉じたまま (= `set('Naame', ...)` のような明らかな typo は依然として叩き出される)。逆も同様。各 escape hatch は自分の責務だけを無効化し、もう片方の安全網は維持されます。

## 次に読む

- [データ取得と DML、IEntry、Mock](/ja/apex-stem/docs/apex-eloquent-data-access): 使い方ガイド
- [親項目・子サブクエリ・多対多](/ja/apex-stem/docs/apex-eloquent-relations): リレーション操作の典型例
- [API リファレンス: IEloquent / Eloquent / MockEloquent](/ja/apex-stem/docs/apex-eloquent-api-eloquent): `IEntry` を返す側
- [API リファレンス: Scribe](/ja/apex-stem/docs/apex-eloquent-api-scribe): SELECT 漏れ検知の元データを作る側
- [ApexEloquent ガイド](/ja/apex-stem/docs/apex-eloquent-guide): ガイド目次に戻る
