0
0

More than 1 year has passed since last update.

after_createコールバック内の関連テーブル作成処理が2回実行される問題

Posted at

結論

  • 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で動作が違う理由は分かりませんでした。
どなたか理由がわかる方ご指摘お願いします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0