search
LoginSignup
52
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
52
Help us understand the problem. What are the problem?