登録したデータが削除される?!
build_associationで新規登録しようとすると、 既存のデータが削除されてしまい、混乱したのでメモとして残します。
実行環境
ruby 2.6.5 Rails 6.0.2.2
かんたんなケースで動作確認してみる
UserとUserProfileというモデルを作ります。
1対1の関係であるとき、以下のようにmodelで設定されるかと思います。
なおUserProfileテーブル自体にuniqueインデックスが追加されているとします。
class User < ApplicationRecord has_one :user_profile end
class UserProfile < ApplicationRecord belongs_to :user end
UserProfileの登録処理を実装します。
createアクションの中で、
・既存のUserに紐づくUserProfileが存在する場合は、例外を発生させる。存在しない場合は登録成功とする。
のような実装をしたいとします。
最初に思いついたのは以下の実装です。build_associationメソッドを使用しています。
Railsガイド belongs-toで追加されるメソッド
def create user = find_user! user.build_user_profile(hoge: params[:hoge]).save! render_some_page end
しかし、上記のコードでは、例外は発生しません。
既存のデータは削除され、新規で登録されてしまいまいます。
# 既存のuserに紐づくuser_profileあり [1] pry(main)> user.user_profile => #<UserProfile:0x0000560151435258 id: 4, user_id: 1, hoge: 2, created_at: Sat, 02 Dec 2023 15:50:13 JST +09:00, updated_at: Sat, 02 Dec 2023 15:50:13 JST +09:00> # id=4がdestroyされています...! [2] pry(main)> user.build_user_profile(hoge: 2).save! (0.8ms) BEGIN UserProfile Destroy (1.0ms) DELETE FROM `user_profiles` WHERE `user_profiles`.`id` = 4 (71.2ms) COMMIT (0.4ms) BEGIN UserProfile Create (0.9ms) INSERT INTO `user_profiles` (`user_id`, `hoge`, `created_at`, `updated_at`) VALUES (1, 2, '2023-12-02 07:19:07', '2023-12-02 07:19:07') (44.2ms) COMMIT => true # id=5で新しくレコードが作成されてしまいます。 [3] pry(main)> user.user_profile => #<UserProfile:0x000056015145fc88 id: 5, user_id: 1, hoge: 2, created_at: Sat, 02 Dec 2023 16:19:07 JST +09:00, updated_at: Sat, 02 Dec 2023 16:19:07 JST +09:00>
意図して削除して新規作成したい場合は、上記で良いと思いますが、知らずに使うと期待しない結果になってしまうので、注意が必要だと思いました。
さて、今回のように、
・既存のUserに紐づくUserProfileが存在する場合は、例外を発生させる。存在しない場合は登録成功とする。
を実装したい場合はどうしたら良いのか。
パターン1
newしてsaveをする
[4] pry(main)> user.user_profile => #<UserProfile:0x000056015145fc88 id: 5, user_id: 1, hoge: 2, created_at: Sat, 02 Dec 2023 16:19:07 JST +09:00, updated_at: Sat, 02 Dec 2023 16:19:07 JST +09:00> [5] pry(main)> user_profile = UserProfile.new(user_id: 1, hoge: 2) => #<UserProfile:0x00005601512faf78 id: nil, user_id: 1, hoge: 2, created_at: nil, updated_at: nil> # 例外が発生する [6] pry(main)> user_profile.save! (0.9ms) BEGIN User Load (1.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 UserProfile Create (2.1ms) INSERT INTO `user_profiles` (`user_id`, `hoge`, `created_at`, `updated_at`) VALUES (1, 2, '2023-12-02 07:28:56', '2023-12-02 07:28:56') (5.6ms) ROLLBACK ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry '1' for key 'user_profiles.index_user_profiles_on_user_id' from /app/.bundle/ruby/2.6.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query' Caused by Mysql2::Error: Duplicate entry '1' for key 'user_profiles.index_user_profiles_on_user_id' from /app/.bundle/ruby/2.6.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
パターン2
create_user_profile(create_user_profile!)を使う
[7] pry(main)> user.user_profile => #<UserProfile:0x000056015145fc88 id: 5, user_id: 1, hoge: 2, created_at: Sat, 02 Dec 2023 16:19:07 JST +09:00, updated_at: Sat, 02 Dec 2023 16:19:07 JST +09:00> # 例外が発生する [8] pry(main)> user.create_user_profile!(hoge: 2) (0.8ms) BEGIN UserProfile Create (2.2ms) INSERT INTO `user_profiles` (`user_id`, `hoge`, `created_at`, `updated_at`) VALUES (1, 2, '2023-12-02 07:38:18', '2023-12-02 07:38:18') (5.6ms) ROLLBACK ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry '1' for key 'user_profiles.index_user_profiles_on_user_id' from /app/.bundle/ruby/2.6.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query' Caused by Mysql2::Error: Duplicate entry '1' for key 'user_profiles.index_user_profiles_on_user_id' from /app/.bundle/ruby/2.6.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
※ なおRails7以上だとcreate_associateの挙動が変わっているので注意(検証せずに削除→新規作成される)