【Rails Advent Calendar 2020】大好きな changes が deprecated になるなんて間違っている!!!【2日目】
Rails Advent Calendar 2020 2日目の記事になります。
この記事では最近までずーっっっっと Rails で勘違いしていた事があったのでその事の顛末を簡単にまとめてみたいと思います。
Rails 5.1 から changes 等が deprecated になる?
さて、わたしが Rails を始めた頃ですかね。当時は Rails 5.1 あたりが最新版だったので Rails 5.1 関連の記事を見かけることが多かったと思います。
その頃に『 changes
を使うと DEPRECATION WARNING
という記事』を見かけて当時あまり Rails に精通してなかったわたしは『へー changes
とかって deprecated なんだー』ぐらいにしか思っていませんでした。
実際レビュー等で『 changes
とかは deprecated だから使わないほうがいいよ』みたなコメントも見たような気がします、多分。
と、言う感じで長らく『あ〜 changes
とかは deprecated だから使わないほうがいいんだなあ』と何も考えずに思っていました。
ちなみにと DEPRECATION WARNING
なのは以下のようなメソッドになります(参照
attribute_was(attr_name)
attribute_change(attr_name)
attribute_changed?(attr_name)
changed
changes
changed?
changed_attributes
changed?
それ、本当に deprecated ですか
『changes
って deprecated なんだなぁ』という認識はあったんですが実際に changes
などと使っても警告などは出ずに『なんで出ないんだろうか』と疑問には思っていました。
で、最近重い腰を上げて調べてみたり調べてもらったりしたのでその経緯を簡単にまとめてみたいと思います。
結論からいうと changes
などは最新版では DEPRECATION WARNING
ではないので普通に使うことができます。
そもそも何が DEPRECATION WARNING
だったのか
そもそも Rails 5.1 でどうなっていたのか確認してみましょう。
まず、 changes
を使っただけでは警告は出ません。
pp ActiveRecord::VERSION::STRING "5.1.0" class User < ActiveRecord::Base end user = User.create(name: "mami") user.name = "homu" # ただ使っただけでは警告は出ない pp user.changes # => {"name"=>["mami", "homu"]}
では、どういうときに警告が出ていたのかというと after_xxx
などのコールバック内で使用した場合に警告が出ていました。
class User < ActiveRecord::Base # こっちは警告は出ない before_update { pp changes } # => {"name"=>["mami", "homu"]} # ここで changes を呼び出すと警告が出る after_update { pp changes } # => {"name"=>["mami", "homu"]} # warning: # DEPRECATION WARNING: The behavior of `changed_attributes` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes.transform_values(&:first)` instead. (called from block in <class:User> at /home/worker/test/ruby/rails/active_record/deprecated_changes/main.rb:44) # DEPRECATION WARNING: The behavior of `changes` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes` instead. (called from block in <class:User> at /home/worker/test/ruby/rails/active_record/deprecated_changes/main.rb:44) # DEPRECATION WARNING: The behavior of `changed` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_changes.keys` instead. (called from block in <class:User> at /home/worker/test/ruby/rails/active_record/deprecated_changes/main.rb:44) # DEPRECATION WARNING: The behavior of `attribute_change` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_change_to_attribute` instead. (called from block in <class:User> at /home/worker/test/ruby/rails/active_record/deprecated_changes/main.rb:44) # DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_change_to_attribute?` instead. (called from block in <class:User> at /home/worker/test/ruby/rails/active_record/deprecated_changes/main.rb:44) end user = User.create(name: "mami") user.name = "homu" pp user.changes # => {"name"=>["mami", "homu"]} user.save! pp user.changes # => {}
まずこの時点でかなり勘違いしていたことがわかりました。
Rails 5.2 でどうなった?
では次に Rails 5.2 でどうなったのか見てみましょう。
先程のコードを Rails 5.2 で動作させると以下のようになります。
pp ActiveRecord::VERSION::STRING # => "5.2.0" class User < ActiveRecord::Base before_update { pp changes } # => {"name"=>["mami", "homu"]} # after_xxx で警告が出なくなる after_update { pp changes } # => {} end user = User.create(name: "mami") user.name = "homu" pp user.changes # => {"name"=>["mami", "homu"]} user.save! pp user.changes # => {}
Rails 5.2 では after_update
で changes
を使っていても警告は出なくなっていますね。
それよりも注目なのは changes
の戻り値が異なっている点です。
各バージョンで after_update
内で changes
を呼んだ場合、以下のようになります。
after_update { pp changes } # 5.0 => no wanirng: {"name"=>["mami", "homu"]} # 5.1 => DEPRECATION WARNING: {"name"=>["mami", "homu"]} # 5.2 => no wanirng: {}
なんてこったい。
DEPRECATION WARNING
な何に対しての警告だったのか
一旦話をまとめるとこういう挙動になっていました。
DEPRECATION WARNING
after_xxx
でchanges
を使うと出ていたDEPRECATION WARNING
は Rails 5.1 でのみ出ていたafter_xxx
でchanges
を使用した場合、5.1 と 5.2 で戻り値が変わっていた
ここで重要なのが『Rails 5.1 -> 5.2 で破壊的変更が入った』という点です。
つまりこの話で出てきた DEPRECATION WARNING
というのはこの『非互換になる挙動』に対する警告だったのです!!!!(多分
あくまでもこの非互換に対する警告であって『 change
自体の呼び出しに対する警告』ではなかったということですね。
なので Rails 5.2 やそれ以降のバージョンではすでに DEPRECATION WARNING
は出ずに changes
などを安全に使うことができます。
最終的な挙動のまとめは以下のとおりです。
class User < ActiveRecord::Base before_update { pp changes } # 5.0 => no warning: {"name"=>["mami", "homu"]} # 5.1 => no warning: {"name"=>["mami", "homu"]} # 5.2 => no warning: {"name"=>["mami", "homu"]} after_update { pp changes } # 5.0 => no wanirng: {"name"=>["mami", "homu"]} # 5.1 => DEPRECATION WARNING: {"name"=>["mami", "homu"]} # 5.2 => no wanirng: {} end user = User.create(name: "mami") user.name = "homu" pp user.changes # => {"name"=>["mami", "homu"]} # before_update / after_update が呼ばれる user.save! pp user.changes # 5.0 => {} # 5.1 => {} # 5.2 => {}
まとめ
と、言うことで長年勘違いしていた事が解決できてすっきりしました。
この手の話は盲目的に信用せずどういう意図なのかをちゃんと調べないとダメですね…。
とはいえ全部に対して1つ1つ調べていくのも時間がかかるので難しいところ。
むしろわたしは『こういうのを書く事』が多いのでなるべく注意して情報を発信していきたいですねえ。