Rails
ActiveRecord

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日目(Day6:#100DaysOfCode)

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