本日、Ruby on Rails 4.0の最初のアップデートである、Rails 4.0.1がリリースされました。
6月に4.0がリリースされてから約4ヶ月、かなり久しぶりのアップデートとなりますが、どのような変更が含まれているのでしょうか。
まとめ
量が多いので最初にまとめです。
今回のリリースは大量のdiffを含みますが、そのほとんどは細かいバグ修正です。
Railsのリリース時期的には3.2.13 → Rails 4.0.0 → 3.2.14 → 3.2.15 → Rails 4.0.1となるため、3.2.14以降で適用されていた細かい修正も取り込まれています。4系が3系にようやく追いつきました。
4系はなんかデグレっぽいバグや非互換が多いなーと思っていた箇所が、ほぼ解消されいる印象です。
現在、有名プロジェクトで4系への移行があまり進んでいないように感じますが、Rails 4.0.1のリリースが移行の引き金になるかもしれません。
重要な変更点
orderの仕様変更
ActiveRecordで、order()を複数回呼んだときの優先順位が逆になりました。先に呼んだ方が優先されるようになります。
もともとRails 4.0.0では3系の逆になっていたので、元に戻ったことになります。
User.order("name asc").order("created_at desc")
# Rails 4.0.0
# SELECT * FROM users ORDER BY created_at desc, name asc
# Rails 4.0.1
# SELECT * FROM users ORDER BY name asc, created_at desc
非常に影響範囲が大きいので、注意が必要です。gemの対応も確認する必要がありそうです。
逆に、3系からのアップデートをこれから行う人には朗報ですね。
その他、パフォーマンス改善が含まれています。
ActionMailer
defaultはProcの場合のみcallされるようになった
ActionMailerでは、以下のようにしてdefault設定ができますが、設定値にはProcを渡すこともできます。
class MyMailer < ActionMailer::Base
default from: 'no-reply@example.com',
subject: -> { "MAIL #{Time.now}" }
end
Rails 4.0.0では、設定値がProcでなくても、to_proc
を受け取る場合はto_proc
してProcとして扱っていました。
しかしこの場合、たとえばArrayにto_proc
を定義した場合、ActionMailerのdefaultに配列を指定できなくなってしまいます。
Symbol#to_proc
が存在するのはもちろんのこと、ArrayやHashにto_proc
を定義するテクニックは便利なので、割と使われています。
default指定するときには上記サンプルコードのように素直にProcを渡せば良いため、Rails 4.0.1ではto_proc
による変換は行われなくなりました。
ActionPack
redirect時にSCRIPT_NAMEが考慮されるようになった
Mountable Engine内でredirectを使い、そのEngineをアプリからサブディレクトリにマウントした場合、Rails 4.0.0ではエラーになっていました。
Rails 4.0.1ではこれが修正されています。
サンプルプログラムとしては以下のようなものです。
# mountable engine作成
rails plugin new myblog --mountable
# myblog/config/routes.rb
Myblog::Engine.routes.draw do
get '/admin' => redirect('admin/dashboard')
end
# myblog/test/dummy/config/routes.rb
Rails.application.routes.draw do
mount Myblog::Engine => "/myblog"
end
# Rails 4.0.0
Started GET "/myblog/admin" for 127.0.0.1 at 2013-10-26 14:26:07 +0900
ERROR URI::InvalidURIError: the scheme http does not accept registry part: localhost:3000admin (or bad hostname# Rails 4.0.1
Started GET "/myblog/admin" for 127.0.0.1 at 2013-10-26 14:30:41 +0900
Started GET "/myblog/admin/dashboard" for 127.0.0.1 at 2013-10-26 14:30:41 +0900
意図しないIP spoofing attackエラーが修正された
Rails 3.2.15でも修正された意図しないIpSpoofAttackErrorが発生する問題が修正されました。
StrongParametersでネストした数値キーに関するバグが修正された
StrongParametersで以下のようになるバグが修正されました。
# これは当たり前
params = ActionController::Parameters.new(user: { data: { "name" => "hello" }})
params.require(:user).permit(data: ['name'])
=> {"data"=>{"name"=>"hello"}}
# キーが数値だと、Rails 4.0.0では値がnilになる
params = ActionController::Parameters.new(user: { data: { "1" => "hello" }})
params.require(:user).permit(data: ['1'])
=> {"data"=>{"name"=>nil}} # Rails 4.0.0
=> {"data"=>{"name"=>"hello"}} # Rails 4.0.1
HTTPアクセス時にSTSヘッダが送出されないようになった
HTTPSでないHTTPのときにSTS(Strict Transport Security)ヘッダを送出するのは無意味なので、HTTPSのときのみ送出するようになりました。
当然ですが、config.force_sslがtrueのときのみの影響です。
actionにif/falseとアクション指定が両方あるときの挙動
before_actionやafter_actionには、onlyやexceptでactionを限定することができるほか、if/unlessで条件指定することができます。
このとき、Rails 4.0.0では先にif/unless条件がチェックされていましたが、先にaction条件がチェックされるようになりました。
たとえば以下のように2回に1回フィルタを実行するようなものがあったとして、Rails 4.0.0ではshowアクションでもカウンタが増えていました。Rails 4.0.1ではindexアクションでのみカウントされます。
class UsersController < ApplicationController
before_action :myfilter, only: [:index], if: -> { ($c = $c.to_i + 1).even? }
end
※Rails 3系では大分前に対応されていたので、Rails 4.0.0のデグレが修正された形です。
エスケープ文字が含まれるときのroutingやcurrent_page?判定が修正された
URLのパス文字列をパーセントエンコーディングする際、Railsは大文字を使いますが、nginxがリバースプロキシとして動作するときは小文字で送信してきます。
そのため、Rails 4.0.0ではcurrent_page?が意図せずfalseを返したり、routingにマッチしないということがありました。
Rails 4.0.1ではこの問題が修正され、パーセントエンコーディングはデコードされた状態で比較されます。
TestRequestでREMOTE_ADDRなどをオーバーライドできるようになった
ActionDispatch::TestRequest.newにHashを渡すことで、REMOTE_ADDR, HTTP_HOST, HTTP_USER_AGENTをオーバーライドできるようになりました。
text_areaにvalue=nilを指定すると空文字で上書きされるようになった
以下のようなコードの場合、Rails 4.0.0ではform_forに渡したオブジェクトのcommentがvalueとして出力されていました。
Rails 4.0.1では、valueがnilで上書きされるようになりました。これでtext_fieldと同じ挙動になりました。
f.text_area :comment, value: nil
link_toにブロックとURLハッシュを渡したときのバグが修正された
例を見れば一目瞭然ですが、変なバグが修正されました。
link_to(action: 'bar', controller: 'foo') { content_tag(:span, 'Example site') }
# Rails 4.0.0
# => "<a action=\"bar\" controller=\"foo\"><span>Example site</span></a>"
# Rails 4.0.1
# => "<a href=\"/foo/bar\"><span>Example site</span></a>"
partialを再帰的に呼んだときのStack Level Too Deepが修正された
コメントが子コメントを持つ掲示板などで以下のようなコードを書いた場合、Rails 4.0.0ではStack Level Too Deepが発生していました。
# app/views/comments/_comment.html.erb
<%= render partial: 'comments/comment', collection: comment.children %>
Rails 4.0.1ではこの問題が修正されています。
date_fieldにvalueを渡せるようになった
以下のような形式で、date_field/datetime_field/color_fieldにvalueを設定できるようになりました。
<%= date_field 'user', 'birthday', value: Date.new(2000,10,1) %>
これは、Rails 4.0.0ではなぜか無視されていたものです。
link_to_unlessが常時エスケープされるようになった
こちらでも紹介しているRails 3.2.14の変更が適用されました。
ActiveModel
has_secure_passwordがBcryptのcostを参照するようになった
Rails 4.0.0ではhas_secure_passwordはBCrypt::Engine::DEFAULT_COSTを参照していましたが、Rails 4.0.1ではBCrypt::Engine.costを参照するようになりました。
ところで、bcrypt-rubyは仕様上costが31までしか設定できないですが、stretchingを1000回とかにしたい時は自前でやるしかないんですかね?31回ってえらく少ない気がするのですが、詳しい方いたら教えてください。
inclusion/exclusion validatorでRange指定に関するバグが修正された
inclusion/exclusionのvalidatorでは、Range指定した場合、Range#cover?が使われます。
この特性上、以下のような問題が発生していました。
class Student < ActiveRecord::Base
# 成績はA,B,C,Dの4段階
validates :grade, inclusion: { in: :A..:D }
end
Student.new(grade: :AB).valid?
# => true
これではよろしくないので、Range#cover?は数値Rangeの場合のみ使われるようになりました。
ActiveRecord
NullRelation#pluckが複数の引数を受け取るようになった
Rails 4ではpluckが複数カラムに対応しましたが、Rails 4.0.0ではNullRelationだけはなぜか複数カラムに非対応でした。
Rails 4.0.1では複数カラムに対応し、通常のRelationと互換性が保たれます。
# Rails 4.0.0
User.pluck(:id, :name) # => [[1, "Taro"], [2, "Jiro"]]
User.none.pluck(:id, :name) # => ArgumentError
# Rails 4.0.1
User.pluck(:id, :name) # => [[1, "Taro"], [2, "Jiro"]]
User.none.pluck(:id, :name) # => []
orderがコーテーションで囲まれるようになった
order(:id)のようにした場合、カラム名がコーテーションで囲まれるようになりました。
Post.order(:id).to_sql
# Rails 4.0.0
# => ... ORDER BY "posts".id ASC
# Rails 4.0.1
# => ... ORDER BY "posts"."id" ASC
11/3追記
はてブでご指摘頂きましたが、これが影響するのはorderの引数にシンボルを渡したときのみです。
Post.order(id: :desc)
のようにすれば4.0.0でも4.0.1でもコーテーションで囲まれます。
Post.order('id')
のようにすれば4.0.0でも4.0.1でも囲まれません。
whereでプレースホルダー形式を指定したときにサブクエリが生成されるようになった
以下のように、whereに配列で指定した場合にも、サブクエリが発行されるようになりました。
User.where(id: User.where(id: 1))
# => SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."id" = 1)
# Rails 4.0.0
User.where("id in (?)", User.where(id: 1).select(:id))
# => SELECT id FROM "users" WHERE "users"."id" = 1
# => SELECT "users".* FROM "users" WHERE (id in (1))
# Rails 4.0.1
User.where("id in (?)", User.where(id: 1).select(:id))
# => SELECT "users".* FROM "users" WHERE (id in (SELECT id FROM "users" WHERE "users"."id" = 1))
INを発行するには通常配列で指定するため、これはクエリ数が減って嬉しいですね。
joinしたときにincludesの指定が消えるバグが修正された
Rails 4.0.0には、Relationに対してincludesでjoinすると、selectで指定した値が消えるバグがありました。
to_sqlでは発生しなくて分かりづらいのですが、実行すると以下のようになります。
# Rails 4.0.0
User.select('name AS hoge').order('hoge').includes(:posts).where(posts: { id:1})
# SQLite3::SQLException: no such column: hoge:
# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1,
# "posts"."id" AS t1_r0, "posts"."user_id" AS t1_r1 FROM "users"
# LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
# WHERE "posts"."id" = 1 ORDER BY hoge
# Rails 4.0.1
User.select('name AS hoge').order('hoge').includes(:posts).where(posts: { id:1})
=> #<ActiveRecord::Relation []>
# SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1,
# "posts"."id" AS t1_r0, "posts"."user_id" AS t1_r1, name AS hoge FROM "users"
# LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
# WHERE "posts"."id" = 1 ORDER BY hoge
NullRelationがwhere_values_hashを上書きしなくなった
Rails 4.0.0では、NullRelationがwhere_values_hashを上書きしていたため、Relationに設定されていたwhereがクリアされていました。
Rails 4.0.1ではこれが改善され、NullRelationでもwhere_values_hashが維持されるようになりました。
これは、次のような違いをもたらします。
# Rails 4.0.0
Post.where(user_id: 1).none.build
=> #<Post id: nil, body: nil, user_id: nil>
# Rails 4.0.1
Post.where(user_id: 1).none.build
=> #<Post id: nil, body: nil, user_id: 1>
Rails 4.0.1のほうが直感的な挙動ですね。
inverse_ofが設定されていてもfind([1])がArrayを返すようになった
Post.find([1,2])のようにすると、複数のデータを配列で取得できます。Post.find([1])だと当然、要素数1のArrayが取得できます。
しかしRails 4.0.0では、inverse_ofが設定されている場合に要素数が1だとArrayになりませんでした。Rails 4.0.1ではこれが修正されています。
class User < ActiveRecord::Base
has_many :posts, inverse_of: :user
end
class Post < ActiveRecord::Base
belongs_to :user, inverse_of: :posts
end
# Rails 4.0.0
User.first.posts.find([1]) # => #<Post id: 1>
User.first.posts.find([1,2]) # => [#<Post id: 1>, #<Post id: 2>]
# Rails 4.0.1
User.first.posts.find([1]) # => [#<Post id: 1>]
User.first.posts.find([1,2]) # => [#<Post id: 1>, #<Post id: 2>]
inverse_ofが設定されていているcallbackはメモリ上のparentを参照するようになった
こちらでも紹介しているRails 3.2.15の修正が適用され、inverse_ofが適用されているオブジェクトにcallbackを設定した場合、parentの参照はDBアクセスをしないようになりました。
特定条件でjoinsが不正なSQLを出力するバグが修正された
has_manyの第2引数にprocを渡して条件を追加し、その条件としてEQとそれ以外を組み合わせた場合、symbol指定でjoinしたときに不正なSQLが出力される問題が修正されました。
class User < ActiveRecord::Base
has_many :hoge_posts,
-> { where(title: 'HOGE').where('body LIKE "%HOGE%"') },
class_name: 'Post'
end
class Post < ActiveRecord::Base
end
# Rails 4.0.0
User.joins(:hoge_posts)
# SQLite3::SQLException: syntax error
# SELECT "users".* FROM "users" INNER JOIN "posts"
# ON "posts"."user_id" = "users"."id"
# AND "posts"."title" = "HOGE", (body LIKE "%HOGE%")
# Rails 4.0.1
User.joins(:hoge_posts)
# SELECT "users".* FROM "users" INNER JOIN "posts"
# ON "posts"."user_id" = "users"."id"
# AND "posts"."title" = "HOGE" AND (body LIKE "%HOGE%")
find_in_batchesとfind_eachがlogger無しで動くようになった
find_in_batchesとfind_eachは、Rails 4.0.0ではActiveRecord::Base.loggerがnilの場合にエラーになっていましたが、Rails 4.0.1ではこれが修正されました。
(単純にログが出力されないようになりました)
has_oneでの空トランザクションが抑制された
以下のような無駄なトランザクションが発行されないようになりました。
# User has_one Accountとする
# Rails 4.0.0
User.new.account = Account.new
# BEGIN TRANSACTION
# COMMIT TRANSACTION
# Rails 4.0.1
User.new.account = Account.new
# (NO SQL)
inverse_ofなassociationでfindしたときに全件取得しなくなった
inverse_ofのassociationにfindを呼んだ場合、Rails 4.0.0では問答無用で全件取得してメモリに展開してから選択していました。
これはchildrenが多いときに壊滅的に遅くなるので、Rails 4.0.1では必要なもののみfindするようになりました。findしたものはinverse_ofなのでメモリ上に残ります。
class User < ActiveRecord::Base
has_many :posts, inverse_of: :user
end
class Post < ActiveRecord::Base
belongs_to :user, inverse_of: :posts
end
# Rails 4.0.0
user.posts.find(1)
# user.postsが全件取得されている!
# SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1
# Rails 4.0.1
user.posts.find(1)
# postsは1件しか取得しない
# SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = 1 AND "posts"."id" = 1
exists?がtrue/falseを返すようになった
ActiveRecord::FinderMethods#exists?が必ずtrue/falseを返すようになりました。
Rails 4.0.0ではnilだったり1だったり色々あったのでわかりやすくなりました。
fixturesがsymlinkに対応
こちらでも紹介しているRails 3.2.15の修正が適用され、fixturesディレクトリのsymbolic linkが辿られるようになりました。
autosaveではinsertの前にdeleteが実行されるようになった
autosaveなassociationで、mark_for_destructionしている場合、INSERT文が発行される前にDELETE文が発行されるようになりました。
これにより、UNIQUE制約が付いているDBでのエラーが解消されます。
class User < ActiveRecord::Base
has_many :posts
# accepts_nested_attributes_forをするとautosave: trueになる
accepts_nested_attributes_for :posts
end
Post.connection.add_index :body, unique: true
@user.posts.create(body: 'TEST')
@user.posts[0].mark_for_destruction
@user.posts.build(body: 'TEST')
@user.save!
# => Rails 4.0.0ではActiveRecord::RecordNotUniqueが発生
counter_cacheが増えないバグが修正された
4.0.0でデグレしていた箇所が修正されました。
class User < ActiveRecord::Base
has_many: :posts
end
class Post < ActiveRecord::Base
has_many: :user, counter_cache: 'posts_count'
end
@user.posts << Post.create(body: 'HELLO')
# Rails 4.0.0だとposts_countがインクリメントされない!
ActiveSupport
ActiveSupport::Cache::FileStore#cleanup
こちらでも紹介しているRails 3.2.15の修正が適用され、ActiveSupport::Cache::FileStore#cleanupの問題が修正されました。
ActiveSupport::Deprecation.behaviorに:raiseが追加された
以下のようにすることで、DEPRECATEDな機能を呼び出したときにActiveSupport::DeprecationExceptionをraiseさせることができるようになりました。
# config/environments/development.rb
ActiveSupport::Deprecation.behavior = :raise
テストを回すときには便利そうです。
DateTimeにusecとnsecが追加された
DateTimeにusecとnsecが追加されました。ActiveSupport::TimeWithZoneもあわせて対応します。
Time.zone = 'Tokyo'
DateTime.now.in_time_zone.usec
# => 0 (Rails 4.0.0)
# => 94805 (Rails 4.0.1)
Time.atがUTC offsetを維持するようになった
例を見るとわかりやすいと思います。
Time.zone = 'Tokyo'
# Rails 4.0.0
Time.now # => 2013-10-26 20:49:17 +0900
Time.utc(2000) # => 2000-01-01 00:00:00 UTC
Time.at(Time.utc(2000)) # => 2000-01-01 09:00:00 +0900
Time.at(Time.utc(2000)).zone # => "JST"
# Rails 4.0.1
Time.now # => 2013-10-26 20:49:17 +0900
Time.utc(2000) # => 2000-01-01 00:00:00 UTC
Time.at(Time.utc(2000) # => 2000-01-01 00:00:00 UTC
Time.at(Time.utc(2000)).zone # => "UTC"
Railties
イベント名が変更された
loggerでsubscribeするときなどに使うイベント名が、action_dispatch.request
からrequest.action_dispatch
に変更されました。
config.log_levelがカスタムロガーにも対応した
config.log_level = :info
のように設定したログレベルが、標準のLoggerだけでなくカスタムで設定したLoggerにも適用されるようになりました。
skip-javascript時にgeneratorがturbolinksのパラメータを出力しなくなった
Rails 4.0.0では、rails newしたときに、app/views/layout/application.html.erbで以下のようにturbolink設定が含まれます。
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
ただし、rails new --skip-javascriptしたときにはこれは無駄なので、Rails 4.0.1ではskip-javascript時にはturbolinksのattributeが付かないようになりました。
最後に
それなりに変更点を書き出してみましたが、他にもたくさんあります。
より詳しく知りたい方はソースコードを読んでみてください。