accepts_nested_attributes_forによるCRUD操作について学ぶ_100DaysOfCodeチャレンジ7日目(Day_7:#100DaysOfCode)

## はじめに

この記事はTwitterで人気のハッシュタグ`#100DaysOfCode`をつけて、
100日間プログラミング学習を続けるチャレンジに挑戦した7日目の記録です。

## 動作環境

* ruby 2.4.1
* Rails 5.0.1

## 現在学習している内容のリポジトリ

https://github.com/yuta-ushijima/notebook-api-on-rails

## 本日学んだこと

* accepts_nested_attributes_forによるCRUD操作

`accepts_nested_attributes_for`については以下の記事で解説しています。

[accepts_nested_attributes_forについて学ぶ_100DaysOfCodeチャレンジ6日目(Day_6:#100DaysOfCode)](https://qiita.com/yuta-ushijima/items/22b823bfa3e2dd358fa0)

## CRUD(クラッド)とは?

CRUDとは、以下の頭文字を組み合わせて作成された用語です。

* Create(登録)
* Read(参照)
* Update(更新)
* Destroy(削除)

CRUDはシステムを構築する上で重要な4つのメイン機能を、一つの言葉として表しているんですね。

乱暴な言い方をすれば、CRUD操作ができれば、最低限システムとして成り立っていることになります。

## accepts_nested_attributes_forでCRUD操作をやってみる

`accepts_nested_attributes_for`メソッドを使ってCRUD操作を行う時、いくつかポイントがあるのでみて行きましょう。

### Create(Strong_paramaters)

Strong_paramatersは、Rails4系から搭載されたActiveRecordの機能。

requireメソッドでモデル名、permitメソッドでモデルが持つ属性名を指定することで、その指定条件にマッチしたパラメーターだけを受け付けるというものです。

`accepts_nested_attributes_for`でCreateをする場合、Strong_paramatersで対象となるモデルの属性名を指定する必要があります。

`#{関連したモデル名}_attributes: [:属性名]`の形式でpermitメソッドの引数に渡してあげればCreateできるようになりますね。

#### Contactモデル

```
# app/models/contact.rb
class Contact < ApplicationRecord
  # モデル同士の関連付け
  belongs_to :kind
  has_many :phones

  accepts_nested_attributes_for :phones
end
```

#### Phoneモデル

```
class Phone < ApplicationRecord
  belongs_to :contact, optional: true
end
```

#### contactコントローラー
```

# POST /contacts
  def create
    @contact = Contact.new(contact_params)

    if @contact.save
      render json: @contact, status: :created, location: @contact
    else
      render json: @contact.errors, status: :unprocessable_entity
    end
  end

private

  def contact_params
    params.require(:contact).permit(:name, :email, 
                                    :birthdate, :kind_id,
                                    phones_attributes: [:number])
  end
```

## Read(include)

`accepts_nested_attributes_for`のRead操作は、対象となるアクションにincludeメソッドを使ってモデル名をシンボルで渡してあげればOK。

#### contactコントローラー

```
# GET /contacts/1
  def show
    render json: @contact, include: [:kind, :phones]
  end
```

renderによってJSONでの表示結果が、KindモデルとPhoneモデルもネストされた状態で出力されるようになります。

```
# render jsonによる出力結果
{
    "id": 4,
    "name": "菅原 拓海",
    "email": "providenci@swaniawski.co",
    "birthdate": "2012-06-03",
    "created_at": "2018-06-20T14:02:48.716Z",
    "updated_at": "2018-06-20T14:02:48.716Z",
    "kind_id": 1,
    "kind": {
        "id": 1,
        "description": "Goodmorning!",
        "created_at": "2018-06-20T14:02:47.349Z",
        "updated_at": "2018-06-20T14:02:47.349Z"
    },
    "phones": [
        {
            "id": 6,
            "number": "080-8807-5906",
            "contact_id": 4,
            "created_at": "2018-06-20T14:02:49.660Z",
            "updated_at": "2018-06-20T14:02:49.670Z"
        },
        {
            "id": 7,
            "number": "080-6345-8352",
            "contact_id": 4,
            "created_at": "2018-06-20T14:02:49.678Z",
            "updated_at": "2018-06-20T14:02:49.684Z"
        },
        {
            "id": 8,
            "number": "090-5822-0671",
            "contact_id": 4,
            "created_at": "2018-06-20T14:02:49.691Z",
            "updated_at": "2018-06-20T14:02:49.698Z"
        },
        {
            "id": 225,
            "number": "070-1407-9142",
            "contact_id": 4,
            "created_at": "2018-06-20T14:46:42.691Z",
            "updated_at": "2018-06-20T14:46:42.691Z"
        }
    ]
}
```

## Update(id)

`accepts_nested_attributes_for`のUpdate操作は、対象となるIDを指定する必要があります。

#### Contactモデル

```
# app/models/contact.rb
class Contact < ApplicationRecord
  # モデル同士の関連付け
  belongs_to :kind
  has_many :phones

  accepts_nested_attributes_for :phones
end
```

この場合、Phoneモデルの情報を更新させたい場合には、contact_idに紐づいたPhoneモデルのidを指定する必要があるということになりますね。

#### Before
```

{
    "id": 4,
    "name": "菅原 拓海",
    "email": "providenci@swaniawski.co",
    "birthdate": "2012-06-03",
    "created_at": "2018-06-20T14:02:48.716Z",
    "updated_at": "2018-06-20T14:02:48.716Z",
    "kind_id": 1,
    "kind": {
        "id": 1,
        "description": "Goodmorning!",
        "created_at": "2018-06-20T14:02:47.349Z",
        "updated_at": "2018-06-20T14:02:47.349Z"
    },
    "phones": [
        {
            "id": 6,
            "number": "080-8807-5906",
            "contact_id": 4,
            "created_at": "2018-06-20T14:02:49.660Z",
            "updated_at": "2018-06-20T14:02:49.670Z"
        }
    ]
}
```

たとえばPhonesのnumberを更新させたい場合、最低限Afterのように記述する必要があります。

#### After

```
{
    "id": 4,
    "name": "菅原 拓海",
    "email": "providenci@swaniawski.co",
    "birthdate": "2012-06-03",
    "created_at": "2018-06-20T14:02:48.716Z",
    "updated_at": "2018-06-20T14:02:48.716Z",
    "kind_id": 1,
    "kind": {
        "id": 1,
        "description": "Goodmorning!",
        "created_at": "2018-06-20T14:02:47.349Z",
        "updated_at": "2018-06-20T14:02:47.349Z"
    },
    "phones": [
        {
            "id": 6,
            "number": "0120-234-5678",
        }
    ]
}
```


## Delete(allow_destroy: true,_destroy)


`accepts_nested_attributes_for`によるDelete操作は、モデルに`allow_destroy: true`を指定するという特殊な方法で行います。

APIでPhoneモデルのレコードを削除する場合、対象となるidを指定した上でハッシュのkeyに`_destroy`、valueに`1`または`true`を記述します。

その後、Strong_Paramatersのpermitメソッドにシンボルで`:_destroy`を渡すと、削除されるようになります。

```
{
    "id": 4,
    "name": "菅原 拓海",
    "email": "providenci@swaniawski.co",
    "birthdate": "2012-06-03",
    "created_at": "2018-06-20T14:02:48.716Z",
    "updated_at": "2018-06-20T14:02:48.716Z",
    "kind_id": 1,
    "kind": {
        "id": 1,
        "description": "Goodmorning!",
        "created_at": "2018-06-20T14:02:47.349Z",
        "updated_at": "2018-06-20T14:02:47.349Z"
    },
    "phones": [
        {
            "id": 6,
            "_destroy": 1
        }
    ]
}
```

## 参考リンク

https://apidock.com/rails/v3.2.3/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for