Rails 4.2.9 の変更点

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

rails/rails

Rails 4.2.8 の変更点

actionpack

ルーティングパスに自動的に (.:format) を付けるかどうかの判定がより厳密になった

Rails アプリでルーティングを定義すると、それぞれのパスパターンの末尾に自動的に (.:format) が付与され、拡張子ライクなパスによってフォーマットを指定できるようになります。もし既に :format がパターンの中で利用されていた場合は二重に定義されてしまうことになるので、これを回避するように仕向けられていましたが、この判定方法に問題があったため、より厳密に判定されるように変更されました。

Use more specific check for :format in route path · rails/rails@ea11acd

パスを処理する中でエンコーディングが変わってしまう問題が修正された

ActionDispatch::Journey::Router::Utils.normalize_path の実装内で、引数として渡されたパスのエンコーディングが何であれ、戻り値のパスのエンコーディングが UTF-8 に変換されてしまうという問題があり、これが修正されました。ただ、Ruby のバージョンによって文字列リテラル内の先頭以外で ASCII 互換の ASCII-8BIT 文字列を展開した場合の挙動に違いがあり、この変更は Rails 4.2.10 で差し戻されることになります。

副次的な変更点として、これまで引数には #to_s を実装したオブジェクトを渡すことができていましたが、この変更で String#encoding を呼び出すように変更されたため、 String しか渡せなくなっています。これは後に Rails 5.2.0 で修正されることになります。

Maintain original encoding from path by eileencodes · Pull Request #29062 · rails/rails

activerecord

Rails.logger が ActiveRecord のログを二重に出力してしまう問題が修正された

ActiveSupport::Logger.broadcast というメソッドによって、ActiveRecord のログは Rails.logger に加えて標準エラー出力にも同時に出力されるような仕組みになっていました。ところが、Rails.logger が標準出力あるいは標準エラー出力を向いている場合、画面に同じ内容が複数出力されてしまうことになります。

この問題が修正され、Logger の出力先を (非公式なやり方で) 調べ、もしLogger が標準出力あるいは標準エラー出力を向いている場合は broadcast の処理を行わないようになりました。この件に付随して、Logger の出力先を参照するための API が Ruby に要望されているようです。

Backport to fix duplicate console logging in Rails 4-2-stable by bmulvihill · Pull Request #25284 ·…

#table_name= を実行したときにステートメントキャッシュが上手く消えるようになった

find や find_by を実行するときには、 もし特定の条件にあてはまれば、Prepared Statement を利用した SQL に相当する Arel オブジェクトが内部で生成され、メモリ上にキャッシュされます。二度目以降の呼び出しでこのキャッシュを利用することで、Relation や Arel::Node などの内部表現を生成するコストを抑えられます。このキャッシュのことを、ここではステートメントキャッシュと呼びます。

このキャッシュが生成されたあとに table_name が変更された場合、これまではキャッシュが消去されず、以降もそのまま利用されてしまう状態でした。つまり、テーブル名を変更したあとで find や find_by を利用した場合、過去のテーブル名を利用した SQL が生成されてしまう場合があったということです。これが修正され、table_name を変更した際に上手くキャッシュが消されるようになりました。

Make `table_name=` reset current statement cache by namusyaka · Pull Request #27953 · rails/rails

wait_timeout が DATABASE_URL から問題無く指定できるようになった

MySQL には wait_timeout という設定値があり、これは非対話型の接続において、アイドル状態がこの秒数だけ続いた場合に切断されるという値です。

ActiveRecord のデータベースに対する設定では、wait_timeout というキーでこの値を設定することができるようになっていましたが、Integer で値が指定されている場合にしか設定が受け付けられない状態でした。URL 形式で設定する場合は全ての値が String になるため、つまり URL 形式では wait_timeout を設定できない状態でした。これが修正され、URL 形式でも wait_timeout を設定できるようになりました。

Fix `wait_timeout` to configurable for mysql2 adapter by kamipo · Pull Request #26559 · rails/rails

GROUP BY を使う問い合わせにおいて、SELECT で付けた名前を HAVING の中で参照できない問題が修正された

GROUP BY を利用する問い合わせにおいて、幾つかのリレーショナルデータベースエンジンでは、SELECT で付けた名前を HAVING の中で参照することができます。過去の変更で、誤って動かない状態に変更されてしまっていましたが、今回これが修正されました。

Include selects in group query with having clause by eugeneius · Pull Request #28183 · rails/rails

db:migrate:status と db:schema:load がサブディレクトリに対応した

migration のファイルは db/migrate/2015/20150601010101_create_users.rb のようにサブディレクトリに入れられるようになっているはずでしたが、db:migrate:status と db:schema:load の実装がこれに対応していなかったため、修正されました。

Fix `rake db:migrate:status` with subdirectories · rails/rails@612edc2

Fix `rake db:schema:load` with subdirectories · rails/rails@a627483

Ruby 2.4 で Decimal 型のカラムに invalid な String を割り当てようとしたときに例外が発生しなくなった

BigDecimal(“”) のように invalid な String を与えたときの挙動が、Ruby 2.4 から変更された影響で、Decimal 型のカラムに同様の String を格納しようとしたとき、意図せず例外が発生するようになってしまいました。これを承け、このような状況では 0.0 相当の値が割り当てられるように変更されました。

Merge branch 'jhawthorn-ruby_2_4_bigdecimal_casting' · rails/rails@3aa729c

環境変数 VERSION が空文字列なときに、誤って migration が実行されないようになった

String#to_i によって空文字列の VERSION が 0 として評価されてしまわないよう、まず空文字列かどうかを確認するようになりました。

fix migrate with empty version by quantumlicht · Pull Request #28485 · rails/rails

collection_singular_ids= に重複する値を渡した場合に発生してしまう例外が修正された

has_many によって、model.book_ids = [1, 2] のように呼び出し可能なメソッドが定義されます。このとき、既に book_ids に 1 や 2 が含まれる状態でこのようにメソッドを呼び出すと、意図せず例外が発生してしまう状態でしたが、この問題が修正されました。

Fix regression caused by collection_singular_ids= ignoring different ... by npezza93 · Pull Request…

activesupport

freeze された ActiveSupport::TimeWithZone に対して #to_datetime を呼ぶと発生していた例外が修正された

freeze されているオブジェクトのインスタンス変数を変更しようとすると RuntimeError が発生します。ActiveSupport::TimeWithZone#to_datetime は、一度目の呼び出し結果をインスタンス変数に格納して memoize しておくような実装だったため、freeze されているインスタンスに対して #to_datetime を呼び出すと例外が発生する状態になっていました。

同様の実装手段が取られている #period, #utc, #time については、#freeze の呼び出し時に評価しておくことで問題が回避されていました。#to_datetime もこの方法に従うことで、この問題が修正されました。なお #to_time にも同様の問題がありますが、これは後に修正されることになります。

Preload to_datetime before freezing a TimeWithZone instance by HashNotAdam · Pull Request #28104 ·…

ActiveSupport::HashWithIndifferentAccess#compact が ActiveSupport::HashWithIndifferentAccess を返すようになった

Ruby が Hash#compact を実装するようになったことを承け、Hash を継承していた ActiveSupport::HashWithIndifferentAccess では、独自で実装していた #compact を取り除くことになりました。しかし Hash#compact の実装と以前の実装とでは、戻り値のクラスが異なっていました。

どちらのクラスのインスタンスを返すべきかという議論はありますが、これまでの挙動を残すべきという考えから、あらためて #compact が用意され、ActiveSupport::HashWithIndifferentAccess のインスタンスが返るようになりました。

ensure `#compact` of HWIDA to return HWIDA by y-yagi · Pull Request #27392 · rails/rails

上書きしていた Marshal.load が第二引数に対応した

ActiveSupport では、読み込み中に autoload を試行する機能を拡張するために Marshal.load を Override していますが、元々の Marshal.load が第二引数を受け付けるにも関わらず、第一引数しか受け取らないように Override されていたため、これが修正されました。

Allow ActiveSupport::MarshalWithAutoloading#load to take a Proc by fareastside · Pull Request…

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

4.2.8 では、Ruby 2.4 の #to_time への対応の一環として、 DateTime#localtime との一貫性を考慮し、DateTime#utc の戻り値が DateTime から Time に変更されていました。しかし後方互換性を考慮して、やはりこれまでの挙動を残すように変更が差し戻されることになりました。

Restore the return type of `DateTime#utc` · rails/rails@fccc031

freeze された Time や TimeWithZone に対して #to_time を呼べるようになった

Ruby 2.4 の #to_time に対応するため、#to_time を共通して上書きするための module が 4.2.8 で導入されました。しかし、DateTime、Time、TimeWithZone の実装では、#to_time が呼び出されたときにどう振る舞うべきかが異なることが分かりました。

これに対応するため、module で上書きするのではなく、個々のクラスで #to_time を適切に上書きするように変更されました。結果、Time#to_time では Ruby 2.4 方式の挙動の場合に self を返すようになったため、freeze されたインスタンスに対しても呼び出し可能になりました。

また TimeWithZone についても、#freeze が呼び出されたタイミングで予め #to_time を呼び出して memoize を行うように変更された結果、freeze されたインスタンスに対しても #to_time が呼び出し可能になりました。

Allow Time#to_time on frozen objects. Return frozen time rather than "RuntimeError: can't modify…

Hash.from_xml に freeze された String を渡されるようになった

Hash.from_xml の内部実装で利用されている XML パーサでは、データを末尾まで読み込んだかどうかの判定処理において、データを変更するような操作が行われていました。これが修正された結果、freeze された String を Hash.from_xml に渡せるようになりました。

Fixes Hash.from_xml with frozen strings for all engines by joshnuss · Pull Request #28815 ·…

railties

rails server が二重にログを出力してしまう問題が修正された

前述した activerecord が二重にログを出力してしまう問題が rails server にも存在しており、同様に修正されました。

Backport fix duplicate rails server logging output in Rails 4-2-stable by jmgarnier · Pull Request…