クイズ:なぜバリデーションエラーになるでしょう


要約

  • railsのcallbackで中間テーブルを生成する方法をミスるとvalidationエラーになるアンチパターン

詳細

  • モデル構成
class User < ApplicationRecord
  has_many :notification_settings, dependent: :destroy
  has_many :notifications, through: :notification_settings, dependent: :destroy
  after_create :create_default_notifications
end

class NotificationSetting < ApplicationRecord
  belongs_to :user
  belongs_to :notification

  validates :user_id,  uniqueness: { scope: [:notification_id] }
end

class Notification < ApplicationRecord
  has_many :notification_settings, dependent: :destroy
  has_many :users, through: :notification_settings, dependent: :destroy
end

とかで、ユーザに通知設定が複数ひもづいている状態で、ユーザが作成されたタイミングでデフォルトの通知設定を作りたくてcreate_default_notificationsの中で

def create_default_notifications
  self.notifications = Notification.all
end

てやっていると一見OKっぽいんだけど、validates :user_id, uniqueness: { scope: [:notification_id] }で引っかかってvalildationエラーになる。

理由


self.notifications = Notification.all

が評価されたタイミングでNotificationSettingにinsertが発行されてしまって、
その後autosaveも動くので、結果2回createされてしまってuniqueじゃなくなってエラーになる。
has_manyにassignすると即insertされるというのがわかりにくかったという問題。
=はsaveされないという思い込みでした。

修正

丁寧に1件1件buildする


Notification.all.each do |notification|
  self.notification_settings.build(notification: notification)
end