patorashのブログ

方向性はまだない

activerecord-importの削除は難しい

Rails6にしたので、insert_allメソッドが使えるようになったので、意気揚々とgem activerecord-importを削除しようと修正していってたのだけれど、思ったより難しそうだったので、一旦止めることにした。

activerecord-importの削除が難しい理由

recursiveオプションが便利で、それを使っていた

activerecord-importには再起的に関連データをインポートできる仕組みがあって、それを外していくことの影響度が大きくてテストが落ちまくるようになりました…。gemを削除するためだけ調査するには時間がかかりすぎるので棚上げすることにしました。

問題

insert_allメソッドはHashを対象とするので、モデルの配列を渡してもNGでした。そのため、activerecord-importのimportメソッドをinsert_allに変えただけではダメ。また、insert_allには以下のような問題がありました。

  • idにnilが設定されていると落ちる
  • created_at, updated_atが自動的に入らない

解決できるもの

上記の問題は、attributes_without_idメソッドをApplicationRecordに作成することで大体問題なく動いてくれました。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  # これを定義
  def attributes_without_id
    self.attributes.except('id').tap do |hash|
      hash[:created_at] ||= Time.zone.now
      hash[:updated_at] ||= Time.zone.now
    end
  end
end

これで、activerecord-importのメソッドは以下のように置換できます。

books = FactoryBot.build_list(:book, 100)

# activerecord-importの場合
Book.import(books)

# insert_allの場合
Book.insert_all(books.map(&:attributes_without_id))

# もしくは、attributes_for_listにすれば、モデルのオブジェクトの生成が不要に。
# 新規に使っていくならこちらがベストかな?
books = FactoryBot.attributes_for_list(:book, 100)
Book.insert_all(books)

最後のやつが最も効率よくデータを作られるはず…(試してない)

まとめ

insert_allは速いけれど、関連データがたくさんあるケースだと厳しい…。無理に削除せずに共存していくことにします。