Rails 4.2.8 の変更点

Rails 4.2.7.1 から 4.2.8 にかけての変更点をまとめます。

rails/rails

activerecord

MySQL サーバとの再接続失敗時に発生する例外が改善された

MySQL サーバとの接続が閉じられたときに、これまではインスタンス変数 connection に nil が格納されていましたが、connection を再度利用するような処理が試みられた場合に NoMethodError が発生する状態でした。これが改善され、接続が閉じられたときにもインスタンス変数 connection はそのまま保持されるようになり、再接続試行時に適切な例外が発生するようになりました。

4-2-backport: activerecord/mysql2: Avoid setting @connection to nil, just close it by dylanahsmith…

attribute でモデルの属性の定義を更新したときに attribute_names のキャッシュが適宜更新されるようになった

モデルクラスに属性を追加したり、あるいは既存の属性の型情報を上書きしたり、またこのときに独自の変換ロジックを持つ型情報を与えたりできる、attribute というメソッドがあります。また、モデルの持つ属性一覧を返す attribute_names というメソッドがあります。ここで属性と呼んでいるものは、カラムとほぼ同義のものです。

attribute メソッドを呼び出して新しくモデルに属性を追加した場合、これまでは attribute_names のキャッシュが更新されず、追加した属性の内容が結果に反映されない状態でした。これが、attribute メソッドを呼び出したときに適切にキャッシュが更新されるように改善され、attribute_names が意図通りの結果を返すようになりました。

Fix attributes names caching by jcoleman · Pull Request #26705 · rails/rails

属性の型情報が以前の問い合わせ時と同等のものであれば、以前のクエリキャッシュが再利用されるようになった

ActiveRecord は、各クラスの属性を表現するために、属性ごとに ActiveRecord::Type::Value というクラスのインスタンスを生成します。attribute メソッドが実行されたり、属性の情報を再読込するような処理が実行された場合、このインスタンスは再生成されます。

データベースへの問い合わせ時、ActiveRecord はクエリキャッシュを利用することを試みます。このとき、クエリキャッシュが利用できるかどうかの判断材料として、属性がそのキャッシュが作成されたときのものと同じかどうかという情報が利用されます。この実装には、Hash が利用されています。

Hash が利用されるということは、同じキーであるかどうか調べるために、eql? メソッドと hash メソッドが利用されます。これまでの ActiveRecord::Type::Value の実装では eql? や hash は特に上書きされておらず、完全に同じオブジェクトであるかどうかで判断される状態でした。これでは、前述したような理由でインスタンスが再生成されたあとでデータベースに問い合わせときに、例え以前と属性の情報が同じでも、クエリキャッシュが利用されなくなってしまいます。

今回の変更によって Hash のキーとしての同値性判定ロジックが改善され、別のインスタンスであっても、内部情報が同等のものであれば同じキーとして認識されるようになりました。この結果、上手くクエリキャッシュが利用されるようになりました。

Don't bust AR query cache when types are not same object (backport bug fix) by jcoleman · Pull…

joinsを呼び出したときに、unscoped ブロックの効果が意図通り発揮されるようになった

unscoped ブロックの中で処理を実行することで、それまでに付与されていた default_scope などのスコープが適用されないようにする機能があります。これまでの実装では、joins の呼び出し時にこの unscoped の効果が発揮されない状態でした。これが改善され、意図通りの効果が発揮されるようになりました。

Allow `joins` to be unscoped · rails/rails@c55a5d6

composed_of で実装された属性について、この属性に Hash を割り当てようとしたときに発生する不具合が修正された

複数の値から成る1つの仮想的な属性を定義するために、 ActiveRecord では composed_of を利用できます。compose_of で実装された属性に対して値を割り当てるときに、特定の仕様を満たした Hash を利用した場合に割り当て方が変化するという挙動がそれまでに実装されていましたが、そこで Hash であるかどうかしか確認しておらず、それ以外のケースで Hash を割り当てたい場合に問題になっていました。

これが改善され、従来通りのユースケースにおいても Hash を意図通り割り当てることが可能になりました。

Don't assume all hashes are from multiparameter assignment in `compos... · rails/rails@4e7cfbf

activesupport

DateTime#utc が DateTime ではなく Time を返すようになった

ActiveSupport は DateTime#utc というメソッドを定義しています。このメソッドの戻り値が、DateTime ではなく Time に変更されました。新しく追加される DateTime#localtime との一貫性を考慮しての変更だと思われます。

この Time を返すようにするという変更自体は、Ruby 2.4 の #to_time の挙動に対応するための言わば付随的な変更でした。パッチリリースで変更するには大きすぎる変更だとみなされ、後に 4.2.9 で元の状態に戻されます。

Make getlocal and getutc always return instances of Time · rails/rails@bcda029

DateTime#subsec が追加された

DateTime の秒数部分を分数として得るメソッドとして、DateTime#subsec が追加されました。

Time#subsec との対称性を考慮して追加されたのではないかと思います。内部実装としては、単に DateTime#sec_fraction を利用しているだけのようです。

Add DateTime#subsec · rails/rails@bc7c566

DateTime#getgm と DateTime#gmtime が追加された

ActiveSupport#TimeWithZone や Time との対称性を考慮し、DateTime にも DateTime#getgm と DateTime#gmtime が追加されました。これらは DateTime#utc の alias となっています。

Add additional aliases for DateTime#utc · rails/rails@a9097a8

DateTime#getlocal と DateTime#localtime が追加された

DateTime#getlocal と、その alias である DateTime#localtime が追加されました。システムのタイムゾーンで、レシーバと同等の日時を持つ Time のインスタンスを返します。

そもそも DateTime#getlocal が定義されたのは、Ruby 2.4 の #to_time の挙動が変更されることから、ActiveSupport の設定次第でこの挙動を変更可能にしようとしたためです。このための作戦として、ActiveSupport の設定を参照しつつ getlocal というメソッドを利用しながら適切な Time のインスタンスを返す、#to_time が定義された module が用意されました。この module をActiveSupport::TimeWithZone、DateTime、Time に prepend することで、#to_time の挙動を設定によって制御できるようになるという流れです。

Time#getlocal はそのままで利用可能でしたが、ActiveSupport::TimeWithZone#getlocal はそのままでは問題がありました。というのも、当時の ActiveSupport::TimeWithZone は Time あるいは DateTime のどちらかのインスタンスを内包するような仕組みであり、DateTime が内包されている場合、#getlocal では DateTime#to_time が利用されることになっていたからです。これでは #to_time を呼ぶために #getlocal が必要で、#getlocal を呼ぶために #to_time が必要となり、循環参照してしまう問題がありました。DateTime#utc についての変更の箇所でも前述した通り、これは常に Time を内包するように変更されたため、問題は解消されました。

DateTime#getlocal は元々定義されていなかったため、このタイミングで定義されました。少し長くなりましたが、これが DateTime#getlocal が定義された背景です。#localtime については、DateTime#getlocal の定義場所が calculations.rb に移動されたタイミングで、一貫性を考慮して追加されています。

Add compatibility for Ruby 2.4 `to_time` changes · rails/rails@d569f8d

Move `DateTime#getlocal` to `/core_ext/date_time/calculations.rb` · rails/rails@1fc3bda

Time#sec_fraction が追加された

DateTime#sec_fraction との対称性を考慮して、Time#sec_fraction が追加されました。

内部実装は Rational を返すように実装されていますが、これは後に Time#subsec を使うように変更されることになります。

DateTime#subsec と Time#sec_fraction が追加されたことで、DateTime と Time に対して、#subsec と #sec_fraction のどちらを呼び出しても秒数部分の分数表現が得られるようになったと言えます。

Add Time#sec_fraction · rails/rails@3edf968

ActiveSupport.to_time_preserves_timezone が追加された

Ruby 2.4 から #to_time がレシーバのタイムゾーンを引き継ぐようになったことを承けて、ActiveSupport 側でその挙動を制御するための設定が追加されました。デフォルトでは引き継がないように設定されています。

Add compatibility for Ruby 2.4 `to_time` changes · rails/rails@d569f8d

activesupport 5 で serialize された ActiveSupport::TimeWithZone のインスタンスが 4 で deserialize できない問題が修正された

ActiveSupport::TimeWithZone のインスタンスを YAML 形式に serialize しようとしたとき、これまでは UTC+0 のタイムゾーンに変換された Time のインスタンスとしてエンコードされる状態でした。変換前のタイムゾーンを持つ ActiveSupport::TimeWithZone のインスタンスとして deserialize されるよう、activesupport 5 でこれが改善されました。しかし、5 で serialize したデータを 4 で deserialize しようとしたときに問題が起こることが分かったため、これが修正されました。

Improve ActiveSupport::TimeWithZone conversion to YAML by pixeltrix · Pull Request #17333 ·…

Add init_with to AS::TimeWithZone and AS::TimeZone · rails/rails@e9bd957

ActiveSupport::TimeWithZone#in が夏時間に対応していない問題が修正された

ActiveSupport では、Date や DateTime や Time において、#in が #since の alias となるように実装されていました。しかし ActiveSupport::TimeWithZone#in が #since に alias されていなかったため、ActiveSupport::TimeWithZone#since で加えられている夏時間への対応が加わらず、method_missing により Time#since の実装が利用される状態でした。

この問題が修正され、ActiveSupport::TimeWithZone#in にも夏時間への対応が含まれるようになりました。

Fix ActiveSupport::TimeWithZone#in · rails/rails@a6edbeb

railties

config/initializers/to_time_preserves_timezone.rb が追加された

#to_time の挙動を制御する設定が追加されたことに関連して、Rails が生成するファイルに config/initializers/to_time_preserves_timezone.rb が追加されました。内容は ActiveSupport.to_time_preserves_timezone を true に設定するものです。

Add compatibility for Ruby 2.4 `to_time` changes · rails/rails@d569f8d

ActionDispatch::IntegrationTest で同時に複数のセッションを利用したときの不具合が修正された

ActionDispatch::IntegrationTest では、それまでの変更により、新しいセッションを作成するために Object#dup が利用されるようになっていました。このとき、それまで利用しているセッションが再利用されるような実装になっていたため、複数のセッションを利用しようとすると問題が起きる状態でした。

これが修正され、複数のセッションを利用しても問題が起きないようになりました。

Fix unexpected session sharing by sina-s · Pull Request #27096 · rails/rails

before_configuration フックが適切なタイミングで実行されるようになった

Rails 4.2 で加えられた変更によって、before_configuration フックの実行されるタイミングに問題が発生していました。今回この問題が修正され、適切なタイミングで before_configuration フックが実行されるようになりました。

run `before_configuration` callbacks as soon as application constant inherits from…