目次
- はじめに
- インスタンス変数と直接モデル参照の違い
- 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やテストの書き方も、実装方針に合わせて柔軟に対応することが大切。