Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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があるのでそれを使います。

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の使い方というよりも基礎がわかっていなくて大分躓いたので、
もっと基本を勉強しよう、と思いました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away