【Rails備忘録】Railsフォーム実装の「インスタンス変数」と「直接モデル参照」使い分け・バリデーションエラーの値保持

## 目次

1. はじめに
2. インスタンス変数と直接モデル参照の違い
    - 2.1 インスタンス変数を使う場合
    - 2.2 直接モデル参照を使う場合
    - 2.3 それぞれのメリット・デメリット
    - 2.4 コードの記述方法とポイント
    - 2.5 DBモデルの場合の注意
3. バリデーションエラー時の値・エラー保持とリダイレクト
    - 3.1 render :new の場合
    - 3.2 redirect_to new_path で値・エラーを保持する方法
    - 3.3 コード例と解説
    - 3.4 renderとredirect_toの違い
    - 3.5 セッション利用の注意点
4. エラーメッセージの日本語化
    - 4.1 ja.ymlの設定
    - 4.2 テストコードの修正
    - 4.3 I18n設定の確認
5. テストコードの記述例と注意点
    - 5.1 直接モデル参照の場合
    - 5.2 インスタンス変数を使う場合
6. まとめ

---

## 1. はじめに
昨日インスタンス変数に関することを投稿しておきながら、本日作業中にうまく思っているとおりに実装ができず、かなり長時間躓いた結果、頭がパンクしてしまったので、再度情報整理と、本日行った作業を言語化します（完全に自分の備忘録、わからなくなる自信ある）。

Railsでフォームを実装した際、「インスタンス変数でデータを渡す」か「モデルを直接参照する」か迷い、いざインスタンス変数でデータ渡してたのに、テストコードのこと考慮してなくてエラーが出て頭を抱えたりしていました。  
また、バリデーションエラー時の値・エラーの保持や日本語化も、いくつかバリデーションの設定箇所があったりするので、実装やテストでつまずきやすいポイントだと感じました。

---

## 2. インスタンス変数と直接モデル参照の違い

### 2.1 インスタンス変数を使う場合

#### コード例

**コントローラー**
```ruby
def new
  @item = Item.new
  @categories = Category.where.not(id: 1)
end
```

**ビュー**
```erb
<%= f.collection_select(:category_id, @categories, :id, :name, {prompt: '---'}) %>
```

#### 解説
- コントローラーでインスタンス変数（@categories）にデータをセットし、ビューでそれを参照させる
- DBから取得したデータや、複雑な条件で加工したデータを渡したい場合に便利

---

### 2.2 直接モデル参照を使う場合

#### コード例

**ビュー**
```erb
<%= f.collection_select(:category_id, Category.where.not(id: 1), :id, :name, {prompt: '---'}) %>
```

#### 解説
- ビューで直接モデルのクラスメソッドを呼び出す
- ActiveHashなど、定数的なデータやDBアクセス不要なデータに向いてる
- コントローラーの記述が不要で、シンプルに書ける

---

### 2.3 それぞれのメリット・デメリット

| 方法                 | メリット                                                                 | デメリット・注意点                                  |
|----------------------|--------------------------------------------------------------------------|-----------------------------------------------------|
| インスタンス変数      | - 複雑なデータ加工や条件分岐に柔軟<br>- ビューがすっきり<br>- テストしやすい | - コントローラーで毎回セットが必要<br>- 重複しやすい |
| 直接モデル参照        | - コントローラーがシンプル<br>- DRYな設計<br>- パフォーマンス良好           | - ビューがやや長くなる<br>- 複雑な加工には不向き     |

---

### 2.4 コードの記述方法とポイント

- **インスタンス変数**は、コントローラーでセット→ビューで参照
- **直接モデル参照**は、ビューで`Model.where(...)`などをそのまま記述
- ActiveHashや定数的なデータは直接参照が推奨されることが多い
- DBから取得したり、複雑なロジックが必要な場合はインスタンス変数が便利

---

### 2.5 DBモデルの場合の注意

- User.allやProduct.allなど**DBアクセスが発生するモデル**は、毎回ビューで直接呼び出すとリクエストごとにSQLが発行されパフォーマンスが低下してしまう
- **DBモデルはインスタンス変数でコントローラーから渡すのが基本**

---

## 3. バリデーションエラー時の値・エラー保持とリダイレクト

### 3.1 render :new の場合

- バリデーションエラー時に`render :new`で同じ画面に戻す
- 入力値・エラーは@itemに保持されるので、フォームの値もエラーもそのまま表示できる

### 3.2 redirect_to new_path で値・エラーを保持する方法

- 通常の`redirect_to`では入力値・エラーが消えてしまう
- **セッション（session）を使って一時的に値・エラーを保存し、newアクションで復元する**ことで「リダイレクト＋値・エラー保持」を実現

### 3.3 コード例と解説

```ruby
def create
  @item = Item.new(item_params)
  if @item.save
    redirect_to root_path
  else
    session[:item_params] = item_params.to_h
    session[:item_errors] = @item.errors.messages
    redirect_to new_item_path
  end
end

def new
  if session[:item_params]
    @item = Item.new(session[:item_params])
    if session[:item_errors]
      session[:item_errors].each do |attr, messages|
        messages.each { |msg| @item.errors.add(attr, msg) }
      end
    end
    session.delete(:item_params)
    session.delete(:item_errors)
  else
    @item = Item.new
  end
end
```
- `create`でエラー時に値・エラーをセッションに保存し、リダイレクト
- `new`でセッションから値・エラーを復元し、フォームに反映

---

### 3.4 renderとredirect_toの違い

- **render**はリクエストを再発行せず、URLは変わらない。入力値・エラーはそのまま保持される
- **redirect_to**はリクエストを再発行し、URLが変わる。通常は入力値・エラーが消えるが、セッション等で保持可能

---

### 3.5 セッション利用の注意点

- セッションはサイズ制限があり、大量データや機密情報の保存には向かない
- **フォームの一時的な値・エラー保持用途に限定して使う**のが安全

---

## 4. エラーメッセージの日本語化

### 4.1 ja.ymlの設定

ja.yml 例
```yaml
ja:
  activerecord:
    attributes:
      item:
        name: "商品名"
        # ...他の属性
  errors:
    messages:
      blank: "を入力してください"
      other_than: "を選択してください"
      # ...他のメッセージ
```

application.rb
```ruby
config.i18n.default_locale = :ja
```

### 4.2 テストコードの修正

- テストの期待値も日本語エラー文に合わせて修正が必要
- 例:  
  `"カテゴリー を選択してください"`  
  `"ニックネーム を入力してください"`

### 4.3 I18n設定の確認

- 日本語化が反映されない場合は、application.rbの`config.i18n.default_locale = :ja`を再確認すること

---

## 5. テストコードの記述例と注意点

### 5.1 直接モデル参照の場合

```ruby
it 'カテゴリーが---（id:1）では登録できない' do
  @item.category_id = 1
  @item.valid?
  expect(@item.errors.full_messages).to include('カテゴリー を選択してください')
end
```
- ビューで直接モデル参照している場合、テストではインスタンス変数を意識しなくてよい

### 5.2 インスタンス変数を使う場合

```ruby
describe 'GET #new' do
  it '@categoriesがセットされていること' do
    get :new
    expect(assigns(:categories)).to eq(Category.where.not(id: 1))
  end
end
```
- コントローラーやビューのテストで「インスタンス変数がセットされているか」を検証できる

---

## 6. まとめ

- **ActiveHash等は直接モデル参照でOK。複雑なデータや再利用性重視ならインスタンス変数も有効**
- **DBモデルはインスタンス変数で渡すのが基本**
- **リダイレクト時の値・エラー保持にはセッションを活用（用途・サイズに注意）**
- **エラーメッセージ日本語化時はテストも日本語に修正。I18n設定も要確認**
- **実装とテストの前提が揃っていれば、どちらの方法でも問題なく動作する**

---

## まとめのまとめ（本日の作業の所感）
保守性・要件に合わせて、「インスタンス変数」と「直接モデル参照」を使い分けていきたい。  
また、バリデーションエラー時のUXやテストの書き方も、実装方針に合わせて柔軟に対応することが大切。