Edited at

(初心者向け)activerecord-importの使い方

More than 1 year has passed since last update.

RailsでBULK INSERTをする時にややハマったので。

ActiveRecord::Relationって何?AcitveRecord::Baseって何?というあたりの人に向けて書いています。


BULK INSERTとは

テーブルに複数のレコードを登録する時に、

1行1行INSERTすると効率が悪いので、まとめてINSERTすること。

詳細かつとてもわかりやすい説明、また基本的な使い方は以下をご参照ください。

ActiveRecordで複数レコード、BULK INSERTする方法とパフォーマンスについて


RailsでBULK INSERTをするには

上の記事でも紹介されていますが、activerecord-importというgemがあるのでそれを使います。


Gemfile

gem 'activerecord-import'


$ bundle install


使い方はとても簡単で、登録するレコード(model)のインスタンスをすべて配列にいれて、クラスメソッドのimportにそれを渡すだけです。


初学者的には簡単ではなかったので、恥を忍んで同じような方の救いになればと思い書いてます。

なので、もっといいやり方などあったらぜひ教えていただけると幸いです。


テーブルを集計して別のテーブルにimportするには

例えば昨月の本のレビューと売り上げを集計してテーブルに保存したい場合。

(とあるテーブルのデータを別のテーブルにコピーしたい場合)

book_sales = Book.joins(:reviews, :sales).where(some_conditions).select(some_columns).group("books.id")

SaleLastMonth.import book_sales

とすると、ArgumentError: Invalid arguments!が返ってきます。

そうはいってもsome_columnsで指定したカラムとSaleLastMonthのカラムはちゃんとあってますし…。

Gemが親切にエラーを出してくれているので、ソースを読んでみます。


ruby/2.5.0/gems/activerecord-import-0.22.0/lib/activerecord-import/import.rb

if args.last.is_a?( Array ) && args.last.first.is_a?(ActiveRecord::Base)

...(中略)
# supports array of hash objects
elsif args.last.is_a?( Array ) && args.last.first.is_a?(Hash)
...(中略)
# supports empty array
elsif args.last.is_a?( Array ) && args.last.empty?
...(中略)
# supports 2-element array and array
elsif args.size == 2 && args.first.is_a?( Array ) && args.last.is_a?( Array )
...(中略)
else
raise ArgumentError, "Invalid arguments!"
end

ふむふむ。察するにargsにレコードたちが入ってるんですね。

そこでargs.last.first.classを見てみると、ActiveRecord::Relation

受け付けてくれるのはActiveRecord::Base

…って、ActiveRecord::RelationActiveRecord::Base、どう違うの??

というのは以下のページに詳しいですが、ようは


ActiveRecord::Relationは


  • クエリを生成するための条件を持っておいて、必要に応じて適切なSQLクエリを生成・発行してくれる

  • その結果からオブジェクトを作って返したり保持したりしてくれる

ActiveRecord::Baseは


  • レコード一行を取り扱うクラス


つまりこうなる。

example1= Book.where(id: 1)

example1.is_a?(ActiveRecord::Base) # => false: ActiveRecord::Relation
example2 = Book.find_by_id(1)
example2.is_a?(ActiveRecord::Base) # => true: ActiveRecord::Base

ActiveRecord::Relationとは一体なんなのか

ActiveRecord::RelationとActiveRecord::Baseの違いを説明してください #4

なお、findとwhereの理解が怪しい方には次のページがおすすめです。

【Rails初心者必見!】ひたすら丁寧にデータ取得を説明(find, where)


完成形

book_sales = Book.joins(:reviews, :sales).where(some_conditions).select(some_columns).group("books.id")でとれるのはActiveRecord::Relationなので、SaleLastMonth.importにはActiveRecord::Baseを渡してあげる。

これはeachすればいいだけ、とか思いきやカラム名が違うって怒られるので、最終的にはこうしました。

row_book_sales = Book.joins(:reviews, :sales).where(some_conditions).select(some_columns).group("books.id")

book_sales = row_book_sales.map do |sales|
sales_last_month = SalesLastMonth.new
[:some, :columns, :name].each do |column|
sales_last_month.send("#{column.to_s}=", sales.send(column))
end
sales_last_month
end
SalesLastMonth.import book_sales

Gemの使い方というよりも基礎がわかっていなくて大分躓いたので、

もっと基本を勉強しよう、と思いました。