0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ActiveRecord-Import を使うとバリデーションがかかる理由

Posted at

はじめに

Railsにおいて、データの一括登録、更新処理としてinsert_allupsert_allなどの便利なメソッドがあるが、ぼーっと実装しているとバリデーションが適用されないことがあるため、記事としてとりまとめた。

結論

activerecord-importimportメソッドを使って、ActiveRecordインスタンスを渡す。

upsert_all の問題点

Rails には upsert_all という便利なメソッドがありますが、これを使うと バリデーションが適用されない という問題があります。

例えば、StockPrice モデルに以下のバリデーションがあるとします。

class StockPrice < ApplicationRecord
  validates :company_code, presence: true
  validates :date, presence: true
  validates :close_price, numericality: { greater_than_or_equal_to: 0 }
end

しかし、upsert_all を使うと、このバリデーションが無視されてしまいます。

StockPrice.upsert_all([{ company_code: 1234, date: "2025-03-16", close_price: -100 }])

close_price-100 を入れてもエラーにならず、そのまま保存される

なぜなら、upsert_allSQL を直接実行 するため、ActiveRecord の機能(バリデーションやコールバック)がすべてスキップされるため。

import を使うとバリデーションがかかる理由

activerecord-importimport メソッドを使うと、渡すデータの形式によってバリデーションが適用されるかどうかが決まる というのがポイント。

(1) ActiveRecord インスタンスを渡す場合(バリデーション適用される)

stock_prices = quotes.map do |quote|
  StockPrice.new(
    company_code: quote["Code"].to_i,
    date: quote["Date"],
    close_price: quote["Close"]
  )
end

StockPrice.import(stock_prices, on_duplicate_key_update: { conflict_target: [:company_code, :date], columns: [:close_price] })

new を使うことで ActiveRecord の通常の処理が適用され、バリデーションも実行される

(2) ハッシュを渡す場合(バリデーション適用されない)

stock_prices = quotes.map do |quote|
  {
    company_code: quote["Code"].to_i,
    date: quote["Date"],
    close_price: quote["Close"]
  }
end

StockPrice.import(stock_prices, on_duplicate_key_update: { conflict_target: [:company_code, :date], columns: [:close_price] })

→ この方法だと validates が効かない

activerecord-import の仕組み

ActiveRecord のオブジェクト (StockPrice.new) を渡すと

  • import は通常の save のように ActiveRecord の機能を通してデータを処理する。
  • バリデーション (validates) が実行される。
  • コールバック (before_save, after_save) も実行される。

ハッシュを渡すと

  • import は SQL を直接実行する形になる。
  • バリデーションがスキップされる。
  • コールバック (before_save, after_save) も実行されない。
  • upsert_all に近い動作になり、DB には高速にデータが入るが、安全性が下がる。

activerecord-import の公式ドキュメントより

activerecord-import の公式ドキュメントには、以下のように記述されています。

If you pass in model instances, ActiveRecord validations and callbacks will be executed.
If you pass in an array of hashes, validations and callbacks will be skipped.
(モデルのインスタンスを渡すと、ActiveRecord のバリデーションとコールバックが実行される。ハッシュの配列を渡すと、バリデーションとコールバックはスキップされる。)

→ インスタンス (new で作成) を渡せばバリデーションが適用される


さいごに

使うメソッドによって下記の通り挙動が異なるため、アプリ仕様にならいメソッドの使い分けが重要。

メソッド バリデーション コールバック パフォーマンス
upsert_all ❌ なし ❌ なし ⭕ 超高速
import (ハッシュ) ❌ なし ❌ なし ⭕ 高速
import (インスタンス) ⭕ あり ⭕ あり ⭕ 高速
0
0
0

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
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?