has_oneな関連をaccepts_nested_attributes_forしている場合にattributesで代入した場合idが無いとdestroyされる

これを知らなくて小一時間ハマったので、メモ

# 環境
- Rails 5.2.1


# 結論から言うと
- has_oneなfieldをaccepts_nested_attributes_forする場合は、 `update_only: true` にしておいた方が安全かもしれない

[ActiveRecord::NestedAttributes::ClassMethods accepts_nested_attributes_for(*attr_names)](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for)

# 現象
以下のようなテーブルとモデルがあったとする。

```ruby
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end

class CreateUserProfiles < ActiveRecord::Migration[5.2]
  def change
    create_table :user_profiles do |t|
      t.string :avatar
      t.belongs_to :user

      t.timestamps
    end
  end
end
```

```ruby
class User < ApplicationRecord
  has_one :user_profile, dependent: :destroy
  accepts_nested_attributes_for :user_profile
end

class UserProfile < ApplicationRecord
  belongs_to :user
end
```

`User : UserProfile` が `has_one` つまり `1 : 1` な関係で存在している。

とりあえず、Userのデータを作成する

```ruby
[13] pry(main)> User.create(name: "test", user_profile_attributes: { avatar: "http://hoge.com" })
   (0.1ms)  begin transaction
  User Create (9.7ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "test"], ["created_at", "2018-09-19 02:31:31.405308"], ["updated_at", "2018-09-19 02:31:31.405308"]]
  UserProfile Create (1.6ms)  INSERT INTO "user_profiles" ("avatar", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["avatar", "http://hoge.com"], ["user_id", 3], ["created_at", "2018-09-19 02:31:31.416808"], ["updated_at", "2018-09-19 02:31:31.416808"]]
   (8.3ms)  commit transaction
=> #<User:0x0000560a59d8a630 id: 3, name: "test", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00>

[14] pry(main)> u = User.last
  User Load (2.7ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x0000560a598f3c70 id: 3, name: "test", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00>

[15] pry(main)> u.user_profile
  UserProfile Load (2.5ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a59843500 id: 2, avatar: "http://hoge.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00>
```

`User#attributes` で `idなし` の `user_profile_attributes` をセットする

```ruby
[16] pry(main)> u.attributes = { user_profile_attributes: { avatar: "http://xxxxxxxx.com" } }
   (0.1ms)  begin transaction
  UserProfile Destroy (9.2ms)  DELETE FROM "user_profiles" WHERE "user_profiles"."id" = ?  [["id", 2]]
   (5.7ms)  commit transaction
=> {:user_profile_attributes=>{:avatar=>"http://xxxxxxxx.com"}}
```

attributesに代入しただけで、saveしてないのに、UserProfileはdestroyされている

saveしてないので、あたらしいUserProfileのidはnilになっている

```ruby
[17] pry(main)> u.user_profile
=> #<UserProfile:0x0000560a597113a8 id: nil, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: nil, updated_at: nil>
```

当然、reloadすると、user_profileはnilになる

```ruby
[18] pry(main)> u.reload.user_profile
  User Load (2.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  UserProfile Load (0.6ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> nil

[21] pry(main)> UserProfile.count
   (2.5ms)  SELECT COUNT(*) FROM "user_profiles"
=> 0
```

不用意にrails consoleでattributesの代入をして、データを失うという悲劇。

# どういう時に困るのか？
**User側にvalidationエラーがあったりして、saveできない時。**

 通常、createやupdateメソッドを使っていれば、困らないかもしれない。

```ruby
class User < ApplicationRecord
  has_one :user_profile, dependent: :destroy
  accepts_nested_attributes_for :user_profile

  validates :name, length: { minimum: 10 }
end
```

`User#name` に10文字以上のvalidationを追加した。

## attributesでセットした場合
```ruby
[29] pry(main)> u = User.last
  User Load (0.8ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x0000560a5ab7b2a8 id: 3, name: "test", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00>

[30] pry(main)> u.user_profile
  UserProfile Load (1.1ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x00007f698c867638 id: 4, avatar: "http://xxxxxxxx.co", user_id: 3, created_at: Wed, 19 Sep 2018 02:41:30 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:41:30 UTC +00:00>

[31] pry(main)> u.attributes = { name: "hoge" , user_profile_attributes: { avatar: "http://hoge.com" } }
   (0.1ms)  begin transaction
  UserProfile Destroy (7.7ms)  DELETE FROM "user_profiles" WHERE "user_profiles"."id" = ?  [["id", 4]]
   (6.7ms)  commit transaction
=> {:name=>"hoge", :user_profile_attributes=>{:avatar=>"http://hoge.com"}}

[32] pry(main)> u.save
   (0.1ms)  begin transaction
   (0.0ms)  rollback transaction
=> false

[34] pry(main)> u.errors
=> #<ActiveModel::Errors:0x00007f698c7e5c50
 @base=#<User:0x0000560a5ab7b2a8 id: 3, name: "hoge", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00>,
 @details={:name=>[{:error=>:too_short, :count=>10}]},
 @messages={:name=>["is too short (minimum is 10 characters)"]}>

[35] pry(main)> u.reload.user_profile
  User Load (3.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  UserProfile Load (0.6ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> nil
```

こうなるので、Userがsave出来ない状態で、UserProfileが削除されてしまう。

```ruby
if user.save
  …
else
  render :edit
end
```

 のように、例外が発生しない状態だとrollbackも出来ず、データが消えてしまう。

## updateで更新する場合

```ruby
[40] pry(main)> u = User.last
  User Load (2.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x00007f698c012348 id: 3, name: "1234567890", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>

[41] pry(main)> u.user_profile
  UserProfile Load (2.5ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a5aa0b670 id: 5, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>

[42] pry(main)> u.update name: "1234", user_profile_attributes: { avatar: "http://xxxxxxxx.com" }
   (0.1ms)  begin transaction
  UserProfile Destroy (7.7ms)  DELETE FROM "user_profiles" WHERE "user_profiles"."id" = ?  [["id", 5]]
   (3.2ms)  rollback transaction
=> false

[43] pry(main)> u.user_profile
=> #<UserProfile:0x0000560a5a9ace68 id: nil, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: nil, updated_at: nil>

[44] pry(main)> u.reload.user_profile
  User Load (1.9ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  UserProfile Load (0.5ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a5a733510 id: 5, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>
```

updateメソッド実行時に、transactionがrollbackされているのがわかる。
u.user_profileには新しいレコードのデータがセットされているが、
destroyがロールバックされているので、reloadすると、元に戻る

ただし、ここでこんなことをすると、ゴミデータが出来てしまう。

```ruby
[45] pry(main)> u.update name: "1234", user_profile_attributes: { avatar: "http://x.com" }
   (0.1ms)  begin transaction
  UserProfile Destroy (8.2ms)  DELETE FROM "user_profiles" WHERE "user_profiles"."id" = ?  [["id", 5]]
   (3.7ms)  rollback transaction
=> false

[46] pry(main)> u.user_profile
=> #<UserProfile:0x0000560a5a669bc0 id: nil, avatar: "http://x.com", user_id: 3, created_at: nil, updated_at: nil>

[47] pry(main)> u.user_profile.save
   (0.1ms)  begin transaction
  UserProfile Create (10.5ms)  INSERT INTO "user_profiles" ("avatar", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["avatar", "http://x.com"], ["user_id", 3], ["created_at", "2018-09-19 02:58:50.666477"], ["updated_at", "2018-09-19 02:58:50.666477"]]
   (7.3ms)  commit transaction
=> true

[48] pry(main)> u.reload.user_profile
  User Load (2.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  UserProfile Load (0.6ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a5a394d00 id: 5, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>

[49] pry(main)> UserProfile.all
  UserProfile Load (2.4ms)  SELECT "user_profiles".* FROM "user_profiles"
=> [#<UserProfile:0x0000560a59feac68 id: 5, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>,
 #<UserProfile:0x0000560a59feab00 id: 6, avatar: "http://x.com", user_id: 3, created_at: Wed, 19 Sep 2018 02:58:50 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:58:50 UTC +00:00>]
```

ただ、これはUnique制約をちゃんと入れておけば回避できるかと。

# 原因調査してみる
 - `accepts_nested_attributes_for` のソースを見てみる
	 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/nested_attributes.rb#L333-L354
	 - `generate_association_writer` でメソッドを定義してるっぽい
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/nested_attributes.rb#L369-L376
	 - 定義されたメソッドの中で、今回のケースは `has_one` なので `assign_nested_attributes_for_one_to_one_association`　が呼ばれる
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/nested_attributes.rb#L407-L437
	 - この中で、今回のケースでは、 `build_user_profile` が呼ばれていた
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/nested_attributes.rb#L429-L431
 - `has_one`  な関連のbuildメソッドはなにをしているのか？
	 - `ActiveRecord::Associations::SingularAssociation#build` が呼ばれているはず
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/singular_association.rb#L20-L24
	 - ここで、 `set_new_record` している。（あやしい）
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/singular_association.rb#L60-L62
		 - `replace`  …  なんとなくニュアンス的にここあやしい
	 - SingularAssociation#replaceは実装されていないので、HasOneの方をみる
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/singular_association.rb#L56-L58
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/has_one_association.rb#L26-L50
	 - `remove_target!(options[:dependent])`  してる
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/has_one_association.rb#L37
	 - **今回のケースでは `dependent: :destoy` をUser#user_profileに設定して居たため、ここでdestroyされていた**
		 - https://github.com/rails/rails/blob/5-2-1/activerecord/lib/active_record/associations/has_one_association.rb#L80-L97

すっきりした

# 対応策
 さきに書いてしまったけど、`accepts_nested_attributes_for` のオプションに `update_only: true` を入れることで、既存のレコードを更新する事ができる

```ruby
class User < ApplicationRecord
  has_one :user_profile, dependent: :destroy
  accepts_nested_attributes_for :user_profile, update_only: true

  validates :name, length: { minimum: 10 }
end
```

```ruby
[53] pry(main)> u = User.last
  User Load (1.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x0000560a5950e740 id: 3, name: "1234567890", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>

[54] pry(main)> u.user_profile
  UserProfile Load (0.7ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a5942b1c0 id: 7, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00, updated_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00>

[55] pry(main)> UserProfile.all
  UserProfile Load (2.9ms)  SELECT "user_profiles".* FROM "user_profiles"
=> [#<UserProfile:0x0000560a593b1ac8 id: 7, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00, updated_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00>]

[56] pry(main)> u.attributes =  {name: "1234", user_profile_attributes: { avatar: "http://x.com" }}
=> {:name=>"1234", :user_profile_attributes=>{:avatar=>"http://x.com"}}

[57] pry(main)> u.user_profile
=> #<UserProfile:0x0000560a5942b1c0 id: 7, avatar: "http://x.com", user_id: 3, created_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00, updated_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00>

[59] pry(main)> u.user_profile.changed?
=> true

[60] pry(main)> u.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false

[61] pry(main)> u.errors
=> #<ActiveModel::Errors:0x00007f698c8a6bd0
 @base=#<User:0x0000560a5950e740 id: 3, name: "1234", created_at: Wed, 19 Sep 2018 02:31:31 UTC +00:00, updated_at: Wed, 19 Sep 2018 02:53:35 UTC +00:00>,
 @details={:name=>[{:error=>:too_short, :count=>10}]},
 @messages={:name=>["is too short (minimum is 10 characters)"]}>

[62] pry(main)> u.user_profile
=> #<UserProfile:0x0000560a5942b1c0 id: 7, avatar: "http://x.com", user_id: 3, created_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00, updated_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00>

[63] pry(main)> u.reload.user_profile
  User Load (1.8ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  UserProfile Load (0.6ms)  SELECT  "user_profiles".* FROM "user_profiles" WHERE "user_profiles"."user_id" = ? LIMIT ?  [["user_id", 3], ["LIMIT", 1]]
=> #<UserProfile:0x0000560a5abe6be8 id: 7, avatar: "http://xxxxxxxx.com", user_id: 3, created_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00, updated_at: Wed, 19 Sep 2018 03:34:16 UTC +00:00>
```

- attributesに代入したタイミングで、destroyされなくなった
- 既存のUserProfileが更新されている
	- changed?がtrue
- validationエラーでsaveされない
	- reloadすれば元に戻る