ActiveRecordを使って複数テーブルへBULK INSERTする方法

# 目的 
- Qiitaの記事に大まかな流れをテンプレート化しておき、作業効率を上げる。
- 今後、railsを学ぶ方に向けての参考に役立てる。

# 前提条件
- 実行環境
    - Ruby 2.5.1
    - Rails 5.2.3
    - MySQL 5.7
  
- gem
    - [activerecord-import](https://github.com/zdennis/activerecord-import)

- 想定
    - 大量のデータ(CSVファイル)をseedで一括に取り込みたい

# 手順
1. CSVファイルを設置する
2. seeds.rbにCSVをBULK INSERTするコードを記述
3. 各modelにオプションを設定
4. `rails db:seed`(完)

## 1. CSVファイルを設置する

```tex
db
├── data
│   ├── hoge.csv  ← 初期データとして取込みたいCSVファイルを設置
│   └── fuga.csv  ← 初期データとして取込みたいCSVファイルを設置
├── migrate
│   ├── 20191118191522_create_huges.rb
│   └── 20191119060537_create_fugas.rb
├── schema.rb
└── seeds.rb
```

## 2. seeds.rbにCSVをBULK INSERTするコードを記述

```ruby:db/data/seeds.rb
# csvライブラリを読み込みます。
require 'csv'

ActiveRecord::Base.transaction do

  # Procオブジェクト
  # ブロックの部分だけを先に定義して変数に代入しておき、後からブロック付きメソッドを渡す
  import = ->(klass, file_path) do

    options = {
      encoding:          "UTF-8",
      headers:           true,
      # header_converters: Array
      # 値を変換する前に UTF-8 にエンコーディング変換を試みます
      # エンコーディング変換に失敗した場合はヘッダは変換されません。
      header_converters: klass.csv_header_converters,
      # converters: Hash
      converters:        klass.csv_converters
    }

    # 登録件数が多いことを想定して念のため1000件ずつ処理を行うように設定
    CSV.read(file_path, options).each_slice(1000) do |rows|
      values = rows.map(&:to_h).map { |row| klass.new(row) }

      klass.import! values, validate: true
    end
  end

  import.(Hoge, Rails.root.join('db', 'data', 'hoge.csv'))
  import.(Fuga, Rails.root.join('db', 'data', 'fuga.csv'))

end
```

## 3. 各modelにオプションを設定

```ruby:app/models/hoge.rb
class Hoge < ApplicationRecord

belongs_to :fuga

validates :label, :fuga_id, :year, :month, presence: true

  class << self
    def csv_header_converters
      headers =  {
          ID:            :id,
          Firstname:     :firstname,
          Lastname:      :lastname,
          City:          :city,
          Num_Of_People: :num_of_people,
          Has_Child:     :has_child
        }
      # 引数nameにはHashのkeyが入る
      -> (name) {
       headers[name] || raise
      }
    end

    def csv_converters
      nil
    end
  end
end
```

```ruby:app/models/fuga.rb
class Fuga < ApplicationRecord

has_many :hoge

validates :firstname, :lastname, :city, presence: true
validates :has_child, inclusion: { in: [true, false] }


  class << self
    def csv_header_converters
      headers =  {
          ID:            :id,
          Firstname:     :firstname,
          Lastname:      :lastname,
          City:          :city,
          Num_Of_People: :num_of_people,
          Has_Child:     :has_child
        }

      # 引数nameにはHashのkeyが入る
      -> (name) {
       headers[name] || raise
      }
    end

    def csv_converters
      -> (field, field_info) {
        if field_info.header == :has_child
          field == 'Yes'
        else
          field
        end
      }
    end
  end
end
```

## 4. `rails db:seed`(完) 👍
```bash:bash
rails db:seed
```

# 関連URL
[Ruby標準添付CSVライブラリのCSV::Convertersについて調べてみた](https://tech-kazuhisa.hatenablog.com/entry/20111123/1322059380)
[CSV のヘッダー行の日本語をシンボルに変換して要素にアクセスしたりとか](https://qiita.com/tyamagu2/items/950aad7e67de1592e9d3)
[メソッド呼び出し(super・ブロック付き・yield)](https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fcall.html)
[手続きオブジェクトの挙動の詳細](https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2flambda_proc.html)

[instance method CSV#convert](https://docs.ruby-lang.org/ja/latest/method/CSV/i/convert.html)