複数件の同じようなデータをインサート/アップデートしたいときに全件成功時のみ保存してnil
を返す。
失敗時は全件ロールバックしたうえでエラーメッセージが取得したい…というケースがあった。
このやり方がわからなくて滅茶苦茶困った&PHPのときと同じ考えをしていて大変苦労した。
解決方法:
結論、トランザクションのblock外に変数と返り値を設定すればOK
何故かというとActiveRecord::Rollback
するとtransaction
ブロックを抜けてしまうから。
よって値を返したい場合はtransactionブロック外でreturn
に相当する実装を行えばよい。
今回の実装ではエラーメッセージ、もしくはnil
が返るようになっている。
実行検証はしてないので動かなかったら、すまんな。
イメージはこんな感じということで許してくれ。
def save
errors = []
ActiveRecord::Base.transaction do
[[:name => "Alice"], [:name => "Bob"], [:name => "Charlie"]].each do |row|
person = Person.new(row)
if !person.save
errors << person.errors.full_messages
end
end
raise ActiveRecord::Rollback if errors.present?
end
# ここが重要だった
errors.presence || nil
end
勘違いしていたこと:
PHPのようにActiveRecord::Rollback("メッセージ")
みたいなことが出来る
実際は出来ない。Syntax Errorになる。
rescue ActiveRecord::Rollback
でキャッチする
rescue
節を追加してロールバック後にエラーメッセージ返すのだろうと思っていた、PHPなどではありがちな手法なので。
ところがdef ~ rescue ~ end
をしても実際にはキャッチできなくてめちゃくちゃ困惑した。
ロールバックしたあとに返り値を指定できる
そんなことはなかった、ロールバック後nil
が返っていた。
↓な感じにコードを書いたら動かないかな?と薄い期待をしていた。
当たり前だがロールバックは実行されない、return errors
は実行されるので失敗しているものは保存されず、成功しているものは保存されてしまうという最悪な状態になる。
ActiveRecord::Rollback
return errors
まとめ
他言語の仕様での考えが強くロールバックするとトランザクションブロックを抜ける…という基礎的な部分を理解していなかった。
ロールバック実行後はメソッドを抜けてしまうものだとばかり考えていたが実際にはそうでないという言われてみればごくごく当たり前の話しに気づくことができず苦労した。
当たり前すぎるのかあまりそういう情報がなくて困ったということもあったので書いておこうと思った次第。