2019年10月22日追記
Rails6ではバルクインサートが標準でサポートされました。
なので、Rails6以上を使う場合はgemを使用する必要がありません。
詳しくはRails 6のB面に隠れている地味にうれしい機能たち(翻訳) - Active Recordのinsert_allをご参照ください。
追記ここまで
RailsでBULK INSERTをする時にややハマったので。
ActiveRecord::Relationって何?AcitveRecord::Baseって何?というあたりの人に向けて書いています。
#BULK INSERTとは
テーブルに複数のレコードを登録する時に、
1行1行INSERTすると効率が悪いので、まとめてINSERTすること。
詳細かつとてもわかりやすい説明、また基本的な使い方は以下をご参照ください。
ActiveRecordで複数レコード、BULK INSERTする方法とパフォーマンスについて
#RailsでBULK INSERTをするには
上の記事でも紹介されていますが、activerecord-importというgemがあるのでそれを使います。
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が親切にエラーを出してくれているので、ソースを読んでみます。
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::Relation
とActiveRecord::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の使い方というよりも基礎がわかっていなくて大分躓いたので、
もっと基本を勉強しよう、と思いました。