本日(日本時間2013年10月17日)、Ruby on Rails 3.2.15がリリースされました。3.2系最新リリースになります。
バグ修正の他、1件のセキュリティFIXが含まれています。3.2系ユーザは可能な限り早めにアップデートしましょう。
なお、予定ではこれが3.2系最終リリースで、これ以降はセキュリティFIXのみが提供されることになっています。
バグ修正はされない予定なので、アクティブなRailsプロジェクトは、早めにRails 4への移行を検討したほうが良さそうです。
セキュリティFIX
DoS攻撃につながる可能性のある脆弱性が修正されています。
ActionMailerのLogSubscriberに以下のようなコードが存在していまいしたが、
info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
これはrecipientsに「test%d@example.com」のような文字列を渡すとArgumentErrorが発生します。
また、やり方は思いつきませんでしたが、仮に%1.fmsの部分を無視させることができれば「%10000d」のようにしてログに非常に長い文字列を出力させることが可能になります。
3.2.15では、この脆弱性が修正されました。
なお、本件のみ3.0, 3.1にも対応パッチがリリースされています。
https://groups.google.com/forum/#!topic/ruby-security-ann/yvlR1Vx44c8
4.0系は、この脆弱性は最初から存在しません。
ActionPackの変更点
意図しないIP spoofing attack
HTTPヘッダのCLIENT_IPとX_FORWARDED_FORに別の値をセットすることで、IP spoofing attackを狙う攻撃が存在します。
対策として、ActionDispatch::RemoteIp::GetIp#calculate_ip
はこの2つの値が異なる場合に例外を発生させるようになっています。
しかし、一部X_FORWARDED_FORが空でCLIENT_IPのみをセットするプロキシが存在するため、その環境では通常アクセスでもIpSpoofAttackErrorがraiseされてしまっていました。
3.2.15ではこの問題が修正され、HTTP_CLIENT_IPとHTTP_X_FORWARDED_FORの両方がセットされている場合のみIP spoofing attackのチェックをするようになりました。
assert_recognizesのconstraints
assert_recognizes
テストで、クエリ文字列にconstraintsを適用するとfailする問題が修正されました。
render partialのformat
以下のようなパターン(通常通りformat = :html
)のとき、_myscript.js.erbと_form.html.erbがrenderされることが期待されます。
# app/controllers/users_controller.rb def index end # app/views/users/index.html.erb <%= render partial: 'myscript', formats: :js %> <%= render 'form' %>
しかし、3.2.14では_form.js.erbがrenderされていました。
3.2.15ではこの問題が修正され、_form.html.erbがrenderされるようになりました。。
assert_redirected_toの第2引数
ActionDispatch::Assertions::ResponseAssertions#assert_redirected_to
の第2引数には、テスト失敗時に表示するメッセージを渡せます。
これが表示されない不具合が修正されました。
ActiveRecordの変更点
inverse_ofその1
ActiveRecordにはinverse_ofという機能があります。
通常のassociationでは、relationを子→親と戻る方向に辿った場合、元のオブジェクトに変更は反映されません。
# app/models/company.rb class Company < ActiveRecord::Base attr_accessible :name has_many :users end # app/models/user.rb class User < ActiveRecord::Base attr_accessible :name, :company belongs_to :company end # rails console # 初期データ User.create(name: '太郎', company: Company.create(name: '会社1')) company = Company.first compnay.name # => '会社1' user = company.users.first user.company.name = '会社2' company.name # => '会社1'
inverse_ofをセットすると、これが反映されるようになります。
# app/models/company.rb class Company < ActiveRecord::Base attr_accessible :name has_many :users, inverse_of: :company end # app/models/user.rb class User < ActiveRecord::Base attr_accessible :name, :company belongs_to :company, inverse_of: :users end # rails console company = Company.first compnay.name # => '会社1' user = company.users.first user.company.name = '会社2' company.name # => '会社2'
この便利なinverse_ofですが、collection_proxyに対してfind_or_initialize_by_*で呼び出し、DBにレコードが存在しない場合に適用されないバグがありました。
# rails console company = Company.first compnay.name # => '会社1' user = company.users.find_or_initialize_by_name('太郎') # DBに存在した user.company.name = '会社2' company.name # => '会社1' ★バグ!
3.2.15ではこの問題が修正され、「会社2」が取得できるようになりました。
※手元で確認したところ、この問題は4.0.0でも発生しました。あとuser = company.users.active.first
のようにscopeを付けるとやはり反映されないですが、これは正常なのかなあ。
inverse_ofその2
上記の例で、Userに以下のようなコールバックを追加します。companyを参照しているのがポイントです。
この状態で、company.usersにUserを追加します。
# app/models/user.rb class User < ActiveRecord::Base attr_accessible :name, :company belongs_to :company, inverse_of: :users after_save do puts "User created in company #{company.name}" end end # rails console company = Company.first company.users << User.new(name: '次郎') # INSERT INTO "users" ("company_id", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?) [["company_id", 1], ["created_at", Thu, 17 Oct 2013 05:30:21 UTC +00:00], ["name", "じろう"], ["updated_at", Thu, 17 Oct 2013 05:30:21 UTC +00:00]] # SELECT "companies".* FROM "companies" WHERE "companies"."id" = 1 LIMIT 1 # User created in company 会社1
after_saveのところで、CompanyをSELECTするSQLが走っています。
inverse_ofされているのだから、UserからCompanyは既にLOADされたものが見えるはずで、これは無駄です。
3.2.15では、この無駄なSELECTが実行されないようになりました。
引数付last(N)の発行するSQL
FinderMethods#lastは、Company.lastのように引数無しで実行するほかに、Company.last(5)のように最後から5個を配列で取得する使い方ができます。
このとき発行されるSQLが、以下のように変更されました。
Company.joins(:users).last(5) # Rails 3.2.14 # => SELECT "companies".* FROM "companies" INNER JOIN "users" ON "users"."company_id" = "companies"."id" ORDER BY id DESC LIMIT 5 # Rails 3.2.15 # => SELECT "companies".* FROM "companies" INNER JOIN "users" ON "users"."company_id" = "companies"."id" ORDER BY "companies"."id" DESC LIMIT 5
3.2.14では、joinしたときにprimary keyが衝突することがありましたが、これが修正されました。
fixture読み込み
fixtures読み込みで、ディレクトリへのsymbolic linkが参照されるようになりました。
lock_versionを使うときのquote_value
lock_versionカラムを使うと簡単に楽観的ロックを実現できます。
このとき、内部的に使われるquote_valueに、カラムの型(この場合数値型であるべき)情報が渡されていませんでした。
これにより、一部の環境(JRuby + activerecord-jdbcmssql-adapter + SQLServer 2000など)で、不適切なクオート処理がされエラーになることがあったようです。
3.2.15ではこの問題が修正され、lock_versionをquoteする処理に型情報が付与されるようになったので、エラーは発生しなくなりました。
ActiveSupportの変更点
ActiveSupport::Cache::FileStore#cleanup
ActiveSupport::Cache::FileStore#cleanupが、each_keyメソッドに依存して正しく動作していませんでした。
3.2.15ではこれが修正され、cleanupを呼ぶと正しく期限切れキャッシュが削除されるようになりました。
TaggedLogging
TaggedLoggingがrespond_to_missing?を実装しました。これは@loggerのrespond_to?に委譲されます。
特別に機能が変わるわけではありませんが、method_missingを使う際のベストプラクティスが適用された形になります。
その他
ActionMailer, ActionPack, ActiveModel, ActiveResource, Railtiesには、機能変更はありません。
まとめ
以上、CHANGELOGとコミットログをまとめてみました。
地味に影響範囲が大きいバグが多数含まれているので、しっかりテストしつつ早めにアップデートしたいですね。
またRails 4のパッチリリースがなかなかでないのが地味に気になりますが、3.2系もいよいよ終盤なので、より積極的にRails 4を使っていきたいです。