サイトアイコンmaita tomoya dev io

#48 Rails db:seedエラー完全攻略!NoMethodErrorとActiveRecord例外の原因と解決方法

Rails db:seedエラー完全攻略!NoMethodErrorとActiveRecord例外の原因と解決方法

Rails開発においてrails db:seedは開発環境のデータ投入やデバッグに欠かせないコマンドです。しかし、アソシエーション関係やデータ整合性の問題で様々なエラーに遭遇することがあります。この記事では、実際のエラー事例を基に、効果的なトラブルシューティング手法を解説します。

よくあるseedエラーの種類

1. NoMethodError - nil参照エラー

2. ActiveRecord::RecordInvalid - バリデーションエラー

3. ActiveRecord::StatementInvalid - SQL実行エラー

4. PG::ObjectInUse - データベース接続エラー

これらのエラーを実際のケーススタディで見ていきましょう。

ケーススタディ:アソシエーション関連のNoMethodError

エラー内容

NoMethodError: undefined method `invitation_point_amount' for nil:NilClass

        amount: owner.invitation_point_amount,
                     ^^^^^^^^^^^^^^^^^^^^^^^^
/app/app/models/campaign_code.rb:78:in `grant_invitation_signup_points!'
/app/db/seeds.rb:335:in `block in <main>'

問題の分析

1. エラーメッセージの読み方

NoMethodError: undefined method `invitation_point_amount' for nil:NilClass

この部分から以下が分かります:

  • invitation_point_amountメソッドが呼び出されている
  • 呼び出し元オブジェクトがnil(NilClass)
  • つまりownerオブジェクトが存在しない

2. スタックトレースの追跡

/app/app/models/campaign_code.rb:78:in `grant_invitation_signup_points!'
/app/db/seeds.rb:335:in `block in <main>'

エラーの流れ:

  1. seeds.rb:335でメソッド呼び出し
  2. campaign_code.rb:78owner.invitation_point_amountを実行
  3. ownernilのためエラー発生

3. 関連コードの確認

CampaignCodeモデル(問題箇所):

def grant_invitation_signup_points!(user)
  create_invitation_points!(
    user,
    {
      amount: owner.invitation_point_amount,  # ← ここでエラー
      title: FRIEND_INVITATION_TITLE,
      kind: :exchange_campaign_code,
      user_platform: user.last_access_platform
    }
  )
end

アソシエーション定義:

class CampaignCode < ApplicationRecord
  belongs_to :owner, class_name: 'User', optional: true
end

データベーススキーマ:

# Table name: campaign_codes
#  owner_id   :uuid

原因の特定

seeds.rbの問題箇所:

# 問題のあるコード
10.times.each do |i|
  CampaignCode.create!(
    name: "ポケカキャンペーンコード#{i}",
    amount: 100 * (i + 1)
    # owner_idが設定されていない!
  )
end

campaign_code = CampaignCode.first
campaign_code.grant_invitation_signup_points!(user)  # ownerがnilでエラー

解決方法

修正版コード

# 修正後のコード
user = User.find_by name: 'sosotown'  # 既存のユーザーを取得

10.times.each do |i|
  CampaignCode.create!(
    name: "ポケカキャンペーンコード#{i}",
    amount: 100 * (i + 1),
    owner: user  # ← ownerを設定
  )
end

campaign_code = CampaignCode.first
campaign_code.grant_invitation_signup_points!(user)  # 正常に動作

なぜこの修正で解決するのか

# Railsアソシエーションの動作
campaign_code = CampaignCode.create!(owner: user)

puts campaign_code.owner_id    # => user.id(自動設定)
puts campaign_code.owner       # => userオブジェクト
puts campaign_code.owner.invitation_point_amount  # => 正常に動作

データベース関連エラーの対処法

PG::ObjectInUse エラー

PG::ObjectInUse: ERROR:  database "app_development" is being accessed by other users
DETAIL:  There are 9 other sessions using the database.

原因

  • 他のプロセスがデータベースに接続している
  • Rails console、テスト実行、他の開発者の接続等

解決方法

1. Dockerコンテナの場合:

# 全コンテナを停止して再起動
docker compose down
docker compose up -d

# データベースを再構築
docker compose exec web rails db:drop db:create db:migrate db:seed

2. ローカル環境の場合:

# PostgreSQLの接続を確認
psql -c "SELECT pid, usename, application_name, state FROM pg_stat_activity WHERE datname = 'app_development';"

# 必要に応じて接続を終了
psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'app_development' AND pid <> pg_backend_pid();"

ActiveRecord::RecordInvalid エラー

ActiveRecord::RecordInvalid: すでに使用されたslugです。別のslugをお試しください。

原因と解決法

1. 重複データの問題:

# 問題のあるseed
Tag.create!(name: "Ruby", slug: "ruby")
Tag.create!(name: "Ruby on Rails", slug: "ruby")  # slug重複でエラー

解決法: find_or_create_byの使用

# 重複を避けるseed
Tag.find_or_create_by!(slug: "ruby") do |tag|
  tag.name = "Ruby"
end

Tag.find_or_create_by!(slug: "rails") do |tag|
  tag.name = "Ruby on Rails"
end

2. データベースリセットが必要な場合:

# seeds.rbの最初でデータをクリア
Tag.destroy_all
# または
ActiveRecord::Base.connection.execute("TRUNCATE tags RESTART IDENTITY CASCADE")

効果的なデバッグ手法

1. ログを活用したデバッグ

# seeds.rbにログを追加
puts "Creating Campaign Code..."
user = User.find_by name: 'sosotown'
puts "Found user: #{user.inspect}"

10.times.each do |i|
  campaign_code = CampaignCode.create!(
    name: "ポケカキャンペーンコード#{i}",
    amount: 100 * (i + 1),
    owner: user
  )
  puts "Created campaign code: #{campaign_code.id}, owner: #{campaign_code.owner&.name}"
end

2. Railsコンソールでの事前確認

# rails console
user = User.find_by name: 'sosotown'
puts user.present? ? "User found" : "User not found"

# アソシエーションのテスト
campaign_code = CampaignCode.new(name: "test", amount: 100, owner: user)
puts campaign_code.valid?
puts campaign_code.errors.full_messages if !campaign_code.valid?

# メソッド呼び出しのテスト
if user&.respond_to?(:invitation_point_amount)
  puts "invitation_point_amount method exists: #{user.invitation_point_amount}"
else
  puts "invitation_point_amount method not found"
end

3. トランザクションを使った安全なseed実行

# seeds.rb
ActiveRecord::Base.transaction do
  puts 'Creating Campaign Code...'
  
  user = User.find_by name: 'sosotown'
  raise "User 'sosotown' not found" unless user
  
  10.times.each do |i|
    CampaignCode.create!(
      name: "ポケカキャンペーンコード#{i}",
      amount: 100 * (i + 1),
      owner: user
    )
  end
  
  campaign_code = CampaignCode.first
  campaign_code.grant_invitation_signup_points!(user)
  
  puts 'Campaign Code creation completed successfully!'
rescue => e
  puts "Error occurred: #{e.message}"
  puts "Backtrace: #{e.backtrace.first(5).join("\n")}"
  raise # トランザクションをロールバック
end

予防策とベストプラクティス

1. 依存関係の明確化

# seeds.rbで実行順序を明確にする
puts "=== Seed Data Creation ==="

puts "Step 1: Creating Users..."
user = User.find_or_create_by!(name: 'sosotown') do |u|
  u.email = 'sosotown@example.com'
  u.firebase_uid = SecureRandom.uuid
end

puts "Step 2: Creating Campaign Codes..."
10.times.each do |i|
  CampaignCode.find_or_create_by!(name: "ポケカキャンペーンコード#{i}") do |cc|
    cc.amount = 100 * (i + 1)
    cc.owner = user
  end
end

puts "Step 3: Testing Association Methods..."
campaign_code = CampaignCode.first
if campaign_code.owner&.respond_to?(:invitation_point_amount)
  campaign_code.grant_invitation_signup_points!(user)
  puts "Association methods tested successfully!"
else
  puts "Warning: invitation_point_amount method not available"
end

2. 環境別のseed管理

# seeds.rb
case Rails.env
when 'development'
  load Rails.root.join('db', 'seeds', 'development.rb')
when 'staging'  
  load Rails.root.join('db', 'seeds', 'staging.rb')
when 'production'
  load Rails.root.join('db', 'seeds', 'production.rb')
end
# db/seeds/development.rb
puts "Loading development seeds..."

# 開発用の豊富なテストデータ
100.times do |i|
  User.find_or_create_by!(email: "user#{i}@example.com") do |user|
    user.name = "テストユーザー#{i}"
    user.firebase_uid = SecureRandom.uuid
  end
end

3. バリデーションエラーの詳細出力

def safe_create!(model_class, attributes, description = nil)
  record = model_class.new(attributes)
  if record.save
    puts "✓ Created #{description || model_class.name}: #{record.id}"
    record
  else
    puts "✗ Failed to create #{description || model_class.name}:"
    record.errors.full_messages.each { |msg| puts "  - #{msg}" }
    puts "  Attributes: #{attributes.inspect}"
    nil
  end
end

# 使用例
safe_create!(CampaignCode, {
  name: "テストキャンペーン",
  amount: 1000,
  owner: user
}, "test campaign code")

まとめ

Rails seedエラーの効果的な対処法:

エラー分析のステップ

  1. エラーメッセージを正確に読む - nil参照、バリデーション失敗、SQL制約違反を区別
  2. スタックトレースを追跡 - エラーの発生箇所と呼び出し経路を確認
  3. 関連コードを確認 - モデル定義、アソシエーション、バリデーションをチェック
  4. データベース状態を確認 - 既存データとの整合性を検証

予防策

  • 依存関係の明確化 - データ作成の順序を適切に管理
  • find_or_create_byの活用 - 重複エラーを回避
  • トランザクション使用 - エラー時の自動ロールバック
  • 詳細なログ出力 - 問題箇所の特定を容易に

デバッグツール

  • Rails console での事前テスト
  • ログ出力による状態確認
  • 安全なヘルパーメソッドの作成

これらの手法を活用することで、seedエラーを迅速に解決し、安定した開発環境を維持できます。エラーは学習機会でもあるので、一つ一つ丁寧に分析することが重要です。

参考リンク