【Rails 6.0】 複数レコードの一括保存について

# はじめに
個人アプリで受発注管理機能を実装したので、その時に学んだ複数レコードの一括保存のやり方についてアウトプットします。

## 参考資料
今回の記事を作成するにあたり、以下の記事を大いに参考にさせていただきました。本当にありがとうございました。
[【Rails 5】モデルを一括登録する手順](https://qiita.com/Ryoga_aoym/items/91a3940cfa4de268fca4)
[【Rails 6】form_with用いて一括登録する](https://ryucoding.com/programming/rails-form-bulk-create)
[1.2. 一括登録フォームの実装](https://rails.densan-labs.net/form/bulk_registration_form.html)

## 制作物

![商品登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605789/f2654d5f-ae41-d402-77c1-7c6e62357bf3.png)

`チェックボックスにチェックを入れた商品のみを登録する`
![商品保存.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605789/31de32fc-5b12-a910-695a-5315b1af145c.png)

![商品一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605789/bb5110f4-9ddf-c181-3089-4f3fc9b55610.png)

## 前提条件
Productモデル

|Column|Type|
|:--|:-:|
|name|string|
|price|integer|
|unit|string|
|availability|boolean|

```ruby:models/product.rb
class Product < ApplicationRecord

  with_options presence: true do
   validates :name
   validates :unit
   validates :price, numericality: {only_integer: true, greater_than_or_equal_to: 0 }
   validates :availability, inclusion: { in: [true, false] }
  end

end

```

## 方針
1. 複数商品を一括保存するためのコレクションモデルを作成
2. コントローラーで、一括登録のための処理を記述
3. ビュー画面で複数の商品を一括登録できるフォームを作成

### 1. 複数商品を一括保存するためのコレクションモデルを作成
modelsディレクトリの中にformディレクトリを作成し、その中にProductCollectionモデルとBaseモデルを作成します。

```ruby:models/form/product_collection
class Form::ProductCollection < Form::Base
  FORM_COUNT = 10 #ここで、作成したい登録フォームの数を指定
  attr_accessor :products 

  def initialize(attributes = {})
    super attributes
    self.products = FORM_COUNT.times.map { Product.new() } unless self.products.present?
  end
  
  def products_attributes=(attributes)
    self.products = attributes.map { |_, v| Product.new(v) }
  end

  def save
    Product.transaction do
      self.products.map do |product|
        if product.availability # ここでチェックボックスにチェックを入れている商品のみが保存される
          product.save
        end
      end
    end
      return true
    rescue => e
      return false
  end
end

```
```ruby:models/form/base.rb
class Form::Base
  include ActiveModel::Model
  include ActiveModel::Callbacks
  include ActiveModel::Validations
  include ActiveModel::Validations::Callbacks

end
```
### 2. コントローラーでインスタンスの生成および、データの処理

```ruby:controllers/products_controller.rb

class ProductsController < ApplicationController
  
  def new
    @form = Form::ProductCollection.new
  end

  def create
    @form = Form::ProductCollection.new(product_collection_params)
    if @form.save
      redirect_to products_path, notice: "商品を登録しました"
    else
      flash.now[:alert] = "商品登録に失敗しました"
      render :new
    end

  end

  private
  
    def product_collection_params
        params.require(:form_product_collection)
        .permit(products_attributes: [:name, :price, :unit, :availability])
    end
    
end

```

`@form = Form::ProductCollection.new`により、先ほど作ったモデルのインスタンスが生成されます。
ストロングパラメータはproducts_attributesで受け取ります。

### 3. ビュー画面で複数の商品を一括登録できるフォームを作成

```ruby:views/products/new.html.haml
= form_with model: @form, url: products_path, method: :post, local: true do |form|
  %table
    %thread
      %tr
        %th 登録
        %th 商品名
        %th 販売価格（円）
        %th 発注単位
  %tbody
    = form.fields_for :products do |f|
      %tr
        %td.text-center
          = f.check_box :availability
        %td
          = f.text_field :name
        %td
          = f.text_field :price
        %td
          = f.text_field :unit
  = form.submit "一括登録"
  = link_to "戻る", :back
```

form_withとfields_forを使って複数登録できるフォームを作成しています。

これで、一括保存ができると思います。

## 最後に
ご覧いただきありがとうござました。何か間違いがあれば、ご指摘をお願いします。