4
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-19

目的

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

前提条件

  • 実行環境

    • Ruby 2.5.1
    • Rails 5.2.3
    • MySQL 5.7
  • gem

  • 想定

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

手順

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

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

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するコードを記述

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にオプションを設定

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
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
rails db:seed

関連URL

Ruby標準添付CSVライブラリのCSV::Convertersについて調べてみた
CSV のヘッダー行の日本語をシンボルに変換して要素にアクセスしたりとか
メソッド呼び出し(super・ブロック付き・yield)
手続きオブジェクトの挙動の詳細

instance method CSV#convert

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3