Techioz Blog

多対多モデルでコンソール経由でレコードを作成できない

概要

私が使用しているもの: Rails 6.1.4 と Ruby 3.1.1p18

Rails で多対多の関連付けを試してから 1 分が経ちましたが、これについては少し迷っています。助け/ヒントをいただければ幸いです。

ご要望があれば、投稿を編集してさらに情報を追加させていただきます。

Rails アプリの living_muay_thai/ 内にディレクトリを作成し、その中に 2 つのモデル間に多対多の関係を作成しようとしています。コードはすべて揃っています。コンソール (開発) を使用して、結合テーブルにレコードを作成しようとすると、エラーが発生します。

3.1.1 :009 > sb.errors.full_messages
 => ["Living muay thai student must exist", "Living muay thai badge must exist"]

私のコード:

generate models/migrations:

rails generate model living_muay_thai/Student
rails generate model living_muay_thai/Badge

この時点では、結合テーブルをどのように作成したか思い出せません。いくつかの異なる生成行を試したと思いますが、いずれも長すぎるインデックス名でエラーが発生したため、そのエラーを解消するために手動で編集することになりました。

Migrations:

Students:

class CreateLivingMuayThaiStudents < ActiveRecord::Migration[6.1]
  def change
    create_table :living_muay_thai_students do |t|
      t.string :fname 
      t.string :lname 
      t.timestamps
    end
  end
end

--------------------------
Badges:

class CreateLivingMuayThaiBadges < ActiveRecord::Migration[6.1]
  def up
    create_table :living_muay_thai_badges do |t|
      t.string  :color 
      t.string  :category
      t.integer :number

      t.timestamps
    end
  end

  def down
    drop_table :living_muay_thai_badges
  end
end

------------------
StudentBadges (joins table)

class CreateLivingMuayThaiStudentBadges < ActiveRecord::Migration[6.1]
  def change
    create_table :living_muay_thai_student_badges do |t|
      t.integer :student_id
      t.integer :badge_id

      t.timestamps
    end
    add_index :living_muay_thai_student_badges, :student_id, name: 'students_index'
    add_index :living_muay_thai_student_badges, :badge_id, name: 'badges_index'
  end
end

学生バッジの移行では、いくつかのチュートリアルで説明されている方法でエラーが発生したため、この方法 (上記) を実行しました…インデックス名が長すぎます… したがって、この方法は機能しました。

Models:

Students:

# == Schema Information
#
# Table name: living_muay_thai_students
#
#  id         :bigint           not null, primary key
#  fname      :string
#  lname      :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class LivingMuayThai::Student < ApplicationRecord
    has_many :living_muay_thai_student_badges
    has_many :living_muay_thai_badges, through: :living_muay_thai_student_badges

    has_many :living_muay_thai_levels
end

----------------------
Badges:

# == Schema Information
#
# Table name: living_muay_thai_badges
#
#  id         :bigint           not null, primary key
#  category   :string
#  color      :string
#  number     :integer
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class LivingMuayThai::Badge < ApplicationRecord
    has_many :living_muay_thai_student_badges
    has_many :living_muay_thai_students, through: :living_muay_thai_student_badges
end

---------------------
StudentBadge

# == Schema Information
#
# Table name: living_muay_thai_student_badges
#
#  id         :bigint           not null, primary key
#  badge_id   :integer
#  student_id :integer
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
# Indexes
#
#  badges_index    (badge_id)
#  students_index  (student_id)
#
class LivingMuayThai::StudentBadge < ApplicationRecord
  belongs_to :living_muay_thai_student
  belongs_to :living_muay_thai_badge
end

(??)まだコントローラーを具体化していません。その前にコンソールでこれをテストしたかったのです。それが私の問題と因果関係があるかどうかはわかりません。

コンソール:

# create a student:
3.1.1 :001 > s1=LivingMuayThai::Student.create(:fname => 'John', :lname=>'Smith')
# works

# create a badge:
3.1.1 :002 > b1=LivingMuayThai::Badge.create(:color => 'White', :category => 'Beginner', :number => 1)
# works

# create record in joins table:
3.1.1 :003 > sb=LivingMuayThai::StudentBadge.create(:student_id => s1.id, :badge_id => b1.id)
 => #<LivingMuayThai::StudentBadge:0x00007f713867a970 id: nil, student_id: 11, badge_id: 140, created_at: nil, updated_at: nil> 
3.1.1 :004 > sb.valid?
 => false 
3.1.1 :005 > sb.errors.full_messages
 => ["Living muay thai student must exist", "Living muay thai badge must exist"]

ご覧いただきありがとうございます。とても有難い。

解決策

問題は、関連付けをどのように定義するか、またどのような列とテーブルの名前を付けるかです。現在、それらは Rails 規約に対応していません

結合テーブルの明示的な列名を使用して、belongs_to を次のように定義します。

class LivingMuayThai::StudentBadge < ApplicationRecord
  belongs_to :living_muay_thai_student, class_name: 'LivingMuayThai::Student', foreign_key: :student_id
  belongs_to :living_muay_thai_badge, class_name: 'LivingMuayThai::Badge', foreign_key: :badge_id
end

列名を変更することもできます。この場合、:foreign_key オプションを指定する必要はありません。

change_table :living_muay_thai_student_badges do |t|
  t.rename :student_id, :living_muay_thai_student_id
  t.rename :badge_id, :living_muay_thai_badge_id
end

2 番目のオプションは、列名に基づいて関連付け名を変更することです (列名はそのままにしておきます (student_id と Badge_id)。

class LivingMuayThai::StudentBadge < ApplicationRecord
  belongs_to :student
  belongs_to :badge
end