結論
- after_createコールバック内で関連テーブルを作成する場合注意が必要。
書き方によっては関連テーブルが2つ作成されてしまう。
環境
Ruby 3.0.2p107
Rails 6.1.4.6
やりたかった事
- 生徒が講義の登録を行う多対多構造の作成
- 生徒の登録と同時に必修科目”math”の登録
model/course.rb
class Course < ApplicationRecord
has_many :course_students
has_many :students ,through: :course_students
end
model/student.rb
class Student < ApplicationRecord
after_create :regist_essensial_course
has_many :course_students
has_many :courses, through: :course_students
private
def regist_essensial_course
courses.create(name:"math")
end
end
model/course_student.rb
class CourseStudent < ApplicationRecord
belongs_to :course
belongs_to :student
end
問題点
このままstudentを登録すると、必修科目が2回登録されてしまいます。
irb(main):001:0> s = Student.create(name:"tarou")
(0.5ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
Student Create (0.8ms) INSERT INTO "students" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "tarou"], ["created_at", "2022-02-15 10:37:40.116549"], ["updated_at", "2022-02-15 10:37:40.116549"]]
Course Create (0.1ms) INSERT INTO "courses" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "math"], ["created_at", "2022-02-15 10:37:40.134345"], ["updated_at", "2022-02-15 10:37:40.134345"]]
CourseStudent Create (0.5ms) INSERT INTO "course_students" ("course_id", "student_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["course_id", 2], ["student_id", 2], ["created_at", "2022-02-15 10:37:40.139247"], ["updated_at", "2022-02-15 10:37:40.139247"]]
CourseStudent Create (0.0ms) INSERT INTO "course_students" ("course_id", "student_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["course_id", 2], ["student_id", 2], ["created_at", "2022-02-15 10:37:40.141084"], ["updated_at", "2022-02-15 10:37:40.141084"]]
TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1
=> #<Student:0x0000000115ae65a8 id: 2, name: "tarou", created_at: Tue, 15 Feb 2022 10:37:40.116549000 UTC +00:00, updated_at: Tue, 15 Feb 2022 10:37:40.116549000 UTC +00:00>
irb(main):002:0> s.courses
Course Load (6.5ms) SELECT "courses".* FROM "courses" INNER JOIN "course_students" ON "courses"."id" = "course_students"."course_id" WHERE "course_students"."student_id" = ? [["student_id", 2]]
=>
[#<Course:0x000000011503ce18 id: 2, name: "math", created_at: Tue, 15 Feb 2022 10:37:40.134345000 UTC +00:00, updated_at: Tue, 15 Feb 2022 10:37:40.134345000 UTC +00:00>,
#<Course:0x00000001242cb238 id: 2, name: "math", created_at: Tue, 15 Feb 2022 10:37:40.134345000 UTC +00:00, updated_at: Tue, 15 Feb 2022 10:37:40.134345000 UTC +00:00>]
解決方法
下記のように中間テーブルを明示的に作成すると、中間テーブルは1つしか作成されません。
class Student < ApplicationRecord
after_create :regist_essensial_course
has_many :course_students
has_many :courses, through: :course_students
private
def regist_essensial_course
# courses.create(name:"math")
c = Course.create(name:"math")
CourseStudent.create(course:c, student:self)
end
end
irb(main):001:0> s = Student.create(name:"tarou")
(0.8ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
TRANSACTION (0.0ms) SAVEPOINT active_record_1
Student Create (0.7ms) INSERT INTO "students" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "tarou"], ["created_at", "2022-02-15 10:48:24.842740"], ["updated_at", "2022-02-15 10:48:24.842740"]]
Course Create (0.1ms) INSERT INTO "courses" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "math"], ["created_at", "2022-02-15 10:48:24.863115"], ["updated_at", "2022-02-15 10:48:24.863115"]]
CourseStudent Create (0.1ms) INSERT INTO "course_students" ("course_id", "student_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["course_id", 2], ["student_id", 2], ["created_at", "2022-02-15 10:48:24.869031"], ["updated_at", "2022-02-15 10:48:24.869031"]]
TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1
=> #<Student:0x000000011510e058 id: 2, name: "tarou", created_at: Tue, 15 Feb 2022 10:48:24.842740000 UTC +00:00, updated_at: Tue, 15 Feb 2022 10:48:24.842740000 UTC +00:00>
irb(main):002:0> s.courses
Course Load (0.2ms) SELECT "courses".* FROM "courses" INNER JOIN "course_students" ON "courses"."id" = "course_students"."course_id" WHERE "course_students"."student_id" = ? [["student_id", 2]]
=> [#<Course:0x0000000115cc63f0 id: 2, name: "math", created_at: Tue, 15 Feb 2022 10:48:24.863115000 UTC +00:00, updated_at: Tue, 15 Feb 2022 10:48:24.863115000 UTC +00:00>]
結論(再掲)
- after_createコールバック内で関連テーブルを作成する場合注意が必要。
書き方によっては関連テーブルが2つ作成されてしまう。
ps
下記のように登録処理をafter_saveコールバックで行えば、明示的に中間テーブルを指定しなくても
必修科目は1つしか登録されません。
model/student.rb
class Student < ApplicationRecord
# after_create :regist_essensial_course
after_save :regist_essensial_course
has_many :course_students
has_many :courses, through: :course_students
private
def regist_essensial_course
courses.create(name:"math")
end
end
irb(main):001:0> s = Student.create(name:"tarou")
(0.4ms) SELECT sqlite_version(*)
TRANSACTION (0.0ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
Student Create (0.5ms) INSERT INTO "students" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "tarou"], ["created_at", "2022-02-15 11:03:57.812200"], ["updated_at", "2022-02-15 11:03:57.812200"]]
Course Create (0.1ms) INSERT INTO "courses" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "math"], ["created_at", "2022-02-15 11:03:57.819082"], ["updated_at", "2022-02-15 11:03:57.819082"]]
CourseStudent Create (0.2ms) INSERT INTO "course_students" ("course_id", "student_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["course_id", 2], ["student_id", 2], ["created_at", "2022-02-15 11:03:57.825033"], ["updated_at", "2022-02-15 11:03:57.825033"]]
TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1
=> #<Student:0x000000012447b5b0 id: 2, name: "tarou", created_at: Tue, 15 Feb 2022 11:03:57.812200000 UTC +00:00, updated_at: Tue, 15 Feb 2022 11:03:57.812200000 UTC +00:00>
irb(main):002:0> s.courses
Course Load (0.2ms) SELECT "courses".* FROM "courses" INNER JOIN "course_students" ON "courses"."id" = "course_students"."course_id" WHERE "course_students"."student_id" = ? [["student_id", 2]]
=> [#<Course:0x0000000126d0bfc0 id: 2, name: "math", created_at: Tue, 15 Feb 2022 11:03:57.819082000 UTC +00:00, updated_at: Tue, 15 Feb 2022 11:03:57.819082000 UTC +00:00>]
しかし、今回はStudentのcreateの時のみ中間テーブルを作成したかったので、updateの時も走ってしまうafter_saveは使えませんでした。
いろいろなパターンで検証してみましたが、結局after_saveとafter_createで動作が違う理由は分かりませんでした。
どなたか理由がわかる方ご指摘お願いします。