目次
- はじめに
 - インスタンス変数と直接モデル参照の違い
- 2.1 インスタンス変数を使う場合
 - 2.2 直接モデル参照を使う場合
 - 2.3 それぞれのメリット・デメリット
 - 2.4 コードの記述方法とポイント
 - 2.5 DBモデルの場合の注意
 
 - バリデーションエラー時の値・エラー保持とリダイレクト
- 3.1 render :new の場合
 - 3.2 redirect_to new_path で値・エラーを保持する方法
 - 3.3 コード例と解説
 - 3.4 renderとredirect_toの違い
 - 3.5 セッション利用の注意点
 
 - エラーメッセージの日本語化
- 4.1 ja.ymlの設定
 - 4.2 テストコードの修正
 - 4.3 I18n設定の確認
 
 - テストコードの記述例と注意点
- 5.1 直接モデル参照の場合
 - 5.2 インスタンス変数を使う場合
 
 - まとめ
 
1. はじめに
昨日インスタンス変数に関することを投稿しておきながら、本日作業中にうまく思っているとおりに実装ができず、かなり長時間躓いた結果、頭がパンクしてしまったので、再度情報整理と、本日行った作業を言語化します(完全に自分の備忘録、わからなくなる自信ある)。
Railsでフォームを実装した際、「インスタンス変数でデータを渡す」か「モデルを直接参照する」か迷い、いざインスタンス変数でデータ渡してたのに、テストコードのこと考慮してなくてエラーが出て頭を抱えたりしていました。
また、バリデーションエラー時の値・エラーの保持や日本語化も、いくつかバリデーションの設定箇所があったりするので、実装やテストでつまずきやすいポイントだと感じました。
2. インスタンス変数と直接モデル参照の違い
2.1 インスタンス変数を使う場合
コード例
コントローラー
def new
  @item = Item.new
  @categories = Category.where.not(id: 1)
end
ビュー
<%= f.collection_select(:category_id, @categories, :id, :name, {prompt: '---'}) %>
解説
- コントローラーでインスタンス変数(@categories)にデータをセットし、ビューでそれを参照させる
 - DBから取得したデータや、複雑な条件で加工したデータを渡したい場合に便利
 
2.2 直接モデル参照を使う場合
コード例
ビュー
<%= f.collection_select(:category_id, Category.where.not(id: 1), :id, :name, {prompt: '---'}) %>
解説
- ビューで直接モデルのクラスメソッドを呼び出す
 - ActiveHashなど、定数的なデータやDBアクセス不要なデータに向いてる
 - コントローラーの記述が不要で、シンプルに書ける
 
2.3 それぞれのメリット・デメリット
| 方法 | メリット | デメリット・注意点 | 
|---|---|---|
| インスタンス変数 | - 複雑なデータ加工や条件分岐に柔軟 - ビューがすっきり - テストしやすい  | 
- コントローラーで毎回セットが必要 - 重複しやすい  | 
| 直接モデル参照 | - コントローラーがシンプル - DRYな設計 - パフォーマンス良好  | 
- ビューがやや長くなる - 複雑な加工には不向き  | 
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 コード例と解説
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 例
ja:
  activerecord:
    attributes:
      item:
        name: "商品名"
        # ...他の属性
  errors:
    messages:
      blank: "を入力してください"
      other_than: "を選択してください"
      # ...他のメッセージ
application.rb
config.i18n.default_locale = :ja
4.2 テストコードの修正
- テストの期待値も日本語エラー文に合わせて修正が必要
 - 例:
"カテゴリー を選択してください"
"ニックネーム を入力してください" 
4.3 I18n設定の確認
- 日本語化が反映されない場合は、application.rbの
config.i18n.default_locale = :jaを再確認すること 
5. テストコードの記述例と注意点
5.1 直接モデル参照の場合
it 'カテゴリーが---(id:1)では登録できない' do
  @item.category_id = 1
  @item.valid?
  expect(@item.errors.full_messages).to include('カテゴリー を選択してください')
end
- ビューで直接モデル参照している場合、テストではインスタンス変数を意識しなくてよい
 
5.2 インスタンス変数を使う場合
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やテストの書き方も、実装方針に合わせて柔軟に対応することが大切。