Galapagos Tech Blog

株式会社ガラパゴスのメンバーによる技術ブログです。

【Ruby on Rails】一括インサートを行うinsert_allとは

こんにちは

ADA事業部の社内システムチームの高橋です。
最近xxx to earnが気になってしょうがないです。

最近Railsの開発してた時に
チームリーダーより勧められた
insert_allというメソッドについて今日はご紹介したいと思います。

結論から言うと
勧められたのですが使うのを断念しました。
とても便利かつパフォーマンスも上がる?し
素晴らしいメソッドだなと思っているのですが
使えないケースが存在するのでそちらもご紹介したいと思います。

insert_allとは

簡単に言うと一括インサート、またの名をバルクインサート!
を行なってくれるメソッドです。
公式ドキュメントは下記となります。

api.rubyonrails.org

下記のようなUserモデルがあった時

# == Schema Information
#
# Table name: users
#
#  id         :integer          not null, primary key
#  name       :string           not null
#  email      :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class User < ApplicationRecord
  
end
user_attributes = (1..100).map { { name: "name", email: "hoge@hoge.com", created_at: Time.current, updated_at: Time.current } }
User.insert_all user_attributes

こんな感じで記述すると
複数のSQL文を発行せず一つのSQL文で
複数件のレコード登録が行えるようになります。

createメソッドとのSQL文の違いは下記

# createメソッド
User.create(name: "name", email: "hoge@hoge.com")
# SQL文は下記
DEBUG: User Create (0.7ms) INSERT INTO `users` (`id`, `name`, `email`, `created_at`, `updated_at`) VALUES (1, 'name', 'hoge@hoge.com', '2022-04-18 08:28:08.003019', '2022-04-18 08:28:08.003019')

# insert_allメソッド、先ほどのコードでセットしたuser_attributesを渡す
User.insert_all user_attributes
# SQL文は下記
DEBUG:   User Bulk Insert (3.4ms) 
INSERT INTO `users` (`name`,`email`,`created_at`,`updated_at`) VALUES ('name', 'hoge@hoge.com', '2022-04-18 08:36:35.875333', '2022-04-18 08:36:35.875372'), ('name', 'hoge@hoge.com', '2022-04-18 08:36:35.875385', '2022-04-18 08:36:35.875398').....

スゲー!めっちゃ楽!!
バージョンによってはタイムスタンプが自動更新されません。
私の開発しているシステムでは更新するオプションが存在しませんでしたので
こういったサンプルを紹介しています。

オプションを使用したい方は下記を参照

techracho.bpsinc.jp

これなら複数件のレコード登録は
脳死でコレで決まりだね!と思ったアナタ。いや私。

抑えておくべき点が調べてたらあったんですね。。

抑えておきたい点

使用する前に、いくつかの抑えておかなきゃいけない点が二つあります。
insert_allを使うかどうかを考える上で重要な視点なので
まずは確認した方が良いです。

バリデーションを行わない

まず一つ目はですね、バリデーションが行われないです。

モデルに下記のように記述することで
アプリケーション側でレコードの各カラムなどのバリデーションをおこなっていると思うのですが
insert_allではこれらが全て行われません。
コールバックアクションも同様です。

class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }

  before_save { self.email = email.downcase }
end

なのでinsert_allを使用する際は
これらバリデーションを行わなくても問題がないケースであるかどうかを
見極める必要があります。

createしたオブジェクト情報が返ってこない

もう一つはですね、登録されたオブジェクト情報が返ってこないんです。

例えば下記のようにcreateメソッドを使用した時
登録されたオブジェクト情報が返ってくると思います。

User.create(name: "name", email: "hoge@hoge.com")
# => #<User id: 1, name: "name", email: "hoge@hoge.com",...>

APIによっては結果をこのまま返したいじゃないですか。
そんな要望がですね、叶える事ができない。。
しかもPostgreSQLしか叶える事ができない。。
辛い。。MySQLデファクトスタンダードじゃねーの。。。
(そんな事ないですね、すいません)

ちなみに下記のように記述すれば
PostgreSQLなら返したいカラム情報も指定することが出来ます。

users = (1..100).map { { name: "name", email: "hoge@hoge.com", created_at: Time.current, updated_at: Time.current } }
User.insert_all(users, returning: %i[id name]) # idとnameのみ返す

なので登録したオブジェクト情報を使用したい場合は
PostgreSQL以外では辞めた方が良いかもしれません。

まとめ

  • 検証を行わない
  • 登録されたオブジェクト情報を返さない(PostgreSQL以外)
    上記でも問題ない場合に使ってみる事をお勧めします。