じゃるログ

その辺にいるエンジニア

Rails で多対多の中間テーブルにレコードが正しく保存されない時

問題発生フェーズ

以下のような多対多のテーブル設計がありまして f:id:soccer1356abc:20180922203332p:plain

以下のように関連付けが実装されています。 (なお、一部の実装を省いています)

# group/profile.rb
has_many :group_profile_users, class_name: "::Group::ProfileUser", foreign_key: :group_profile_id
has_many :users, through: :group_profile_users, class_name: "::User"

# group/profile_user.rb
belongs_to :user, class_name: "::User"
belongs_to :group_profile, class_name: "::Group::Profile"

# user.rb
has_many :group_profile_users, class_name: "::Group::ProfileUser", foreign_key: :user_id
has_many :group_profiles, through: :group_profile_users, class_name: "::Group::Profile"

これでgroup_profileを保存しようとするとvalidation errorで引っかかてしまい、以下のようなエラーが出ます。 (実はusersだけでなくtagsというのもあるのでした) f:id:soccer1356abc:20180922210324p:plain

不正な値とだけ怒られてしまっています。そもそもまだ validation 実装してないのになぜ怒られなきゃいけないんだ…!

問題解決フェーズ

そもそもまだvalidation実装してないし、多分関連付けの部分で何か良くないことが起きてそうだなということで、belongs_to あたりを調べてみました。belongs_toの公式ドキュメントを読んでいてoptionalというオプションがありました。そういえば去年くらいにデフォルトがoptinal: falseに仕様変更されてた気がするなーと…しかも一年くらい前に一度同じことでハマった気がするなーと…

api.rubyonrails.org

optional: falseになっていると、アソシエーション先でpresence: trueになってしまいます。
つまり、今回のケースだとgroup_profileとgroup_profile_usersのレコードを同時に作成しようとすると、group_profile_usersの保存の際に、まだ存在しないgroup_profileのidを持った保存とみなされてしまい、validationに引っかかってしまうということでした。

なので中間テーブルのbelongs_toに以下のように設定を追加してあげます。

# group/profile_user.rb
belongs_to :user, class_name: "::User", optional: true
belongs_to :group_profile, class_name: "::Group::Profile", optional: true

optional: trueを追加しただけです。無事解決しました。
去年もハマったし、あんまり検索しても出てこなかったのでまとめときました。