Rails 4.0 にアップグレードした

最近、Rails 1.2 で開発をスタートした Rails 3.2 のアプリケーションを Rails 4.0 にアップグレードした。

その時に修正が必要になったところのメモを残しておく。

ActiveSupport ^

ActiveSupport::JSON.encode でマルチバイト文字が escape されなくなった ^

https://github.com/rails/rails/commit/8f8397e0a4

マルチバイト文字をエスケープしたくない場面で独自拡張をしていたが、その必要がなくなった。

対応

独自拡張を削除した。

Range#step の拡張 (blockless_step) が削除された ^

https://github.com/rails/rails/issues/6297

この拡張のせいで Rails 3.2 までは

(0..10).step(2)  # => #<Enumerator: 0..10:step(2)>

となるべきところが

(0..10).step(2)  # => [0, 2, 4, 6, 8, 10]

となってしまっていた。

対応

Range#step を使っているのに Array を期待してしまっていたところに明示的に .to_a を追加した。

ActiveModel ^

validates_format_of で multiline option なしに ^ $ を利用できなくなった ^

https://github.com/rails/rails/commit/bc7c0b5c10

対応

\A \z に書き換えた。

validates_confirmation_of のエラーメッセージが追加される attribute が password から password_confirmation に変更された ^

https://github.com/rails/rails/pull/5942

対応

config/locale/ja.ymlpassword_confirmation の表記を追加した。

 ja:
   activerecord:
     user:
       password: パスワード
+      password_confirmation: パスワード(確認)

ActiveRecord ^

lambda / proc を利用しない scope が deprecated になった ^

scope :active, where(active: true)

は deprecated.

対応

scope :active, -> { where(active: true) }

に書き換えた。

has_many, belongs_to などが第2引数に scope を受け取るようになった ^

has_many :users, order: :name

は deprecated.

対応

has_many :users, -> { order(:name) }

に書き換えた。

first(...), find(:first, ...) が deprecated になった ^

User.first(conditions: { active: true })
User.find(:first, conditions: { active: true })

は deprecated.

activerecord-deprecated_finders gem に移されている。

対応

Uesr.find_by(active: true)

に書き換えた。

all(...), find(:all, ...) が deprecated になった ^

User.all(conditions: { active: true })
User.find(:all, conditions: { active: true })

は deprecated.

activerecord-deprecated_finders gem に移されている。

対応

Uesr.where(active: true).to_a  # ほとんどの場所では to_a は必要ない

に書き換えた。

find(id, ...) が deprecated になった ^

Group.find(1, include: :users)

は deprecated.

activerecord-deprecated_finders gem に移されている。

対応

Group.includes(:users).find(1)

に書き換えた。

find_all_by_..., find_or_initialize_by_... などの dynamic finder が deprecated になった ^

User.find_all_by_active(true)
User.scoped_by_active(true)
User.find_or_initialize_by_name('john')

は deprecated.

activerecord-deprecated_finders gem に移されている。

利用していなかったけど find_last_by_..., find_or_create_by_... も同様。

ちなみに find_by_... は Rails 4.0 では deprecated になっていない。

対応

User.where(active: true).to_a  # ほとんどの場所では to_a は必要ない
User.where(active: true)
User.find_or_initialize_by(name: 'john')

に書き換えた。

scoped / with_scope / with_exclusive_scope が deprecated になった ^

# 例えば、初期値を設定して new したいとき
User.scoped(conditions: { active: true }).new
User.with_scope(create: { active: true }) { User.new }
# 例えば、default_scope を無視して取得したいとき
User.with_exclusive_scope { User.all }

は deprecated.

activerecord-deprecated_finders gem に移されている。

対応

# 例えば、初期値を設定して new したいとき
User.where(active: true).new
User.where(active: true).scoping { User.new }
# 例えば、default_scope を無視して取得したいとき
User.unscoped

に書き換えた。

order が Hash を受け付けるようになった ^

User.order('users.id DESC')
# or
User.order(User.arel_table[:id].desc)

を以下のように書けるようになった。

User.order(id: :desc)

また order に Symbol を渡すだけでテーブル名も付加してくれるようになった。

where.not が利用できるようになった ^

User.where('users.deleted_at IS NOT NULL')
# or
User.where(User.arel_table[:deleted_at].not_eq(nil))

を以下のように書けるようになった。

User.where.not(deleted_at: nil)

{Model}::Type クラスが作成されるようになった ^

class Foo < ActiveRecord::Base
end

とすると Foo::Type が作成される。 これは ActiveRecord::AttributeMethods::Serialization::Type というクラスが追加されたため。

アプリケーション側ですでに Foo::Type を作成していたため問題が起きた。

対応

Foo::TypeFoo::BarType などに名前を変更した。

acts_as_paranoid が Rails 4 に対応していない ^

対応

paranoia を使った。

paranoia は datetime 型しか対応しておらず、アプリケーションでは boolean を使っているという別の問題が発生したので、機能追加して凌いだ。

set_table_name が削除された ^

https://github.com/rails/rails/commit/9add7608f1

Rails 3.2 の時から Deprecation Warning は出ていたが、migration で利用されていたため見落としていた。

対応

self.table_name = に書き換えた。

ActiveRecord::Base#connection が deprecated になった ^

https://github.com/rails/rails/commit/992d87db02

対応

self.class.connection に書き換えた。

ActionPack ^

Strong Parameters ^

対応

普通はまじめに Strong Parameters 対応をするか protected_attributes gem を使うかだろうけど、 Rails 1.2 時代から作っているせいで attr_accessible, attr_protected を使わずに独自実装(role 指定可能な attr_accessible に近いもの)していたため、とりあえず無効にした。

config.action_controller.permit_all_parameters = true

徐々に Strong Parameters に移行していく予定。

routes.rb で via なしの match が利用できなくなった ^

https://github.com/rails/rails/issues/5964

対応

get, post, patch など適切なものに書き換えた。

複数 HTTP method を受け付けるような action は via をつけても良いが、action を分割した方がより良いので controller 含め修正した。

update 時の HTTP method が PUT から PATCH になった ^

form_for によって生成される form もそうなるため、 routes.rbresources で指定しているものは問題ないが、

put 'user/:id', to: 'users#update'

などと直接記述している場合、404 になる。

対応

patch に書き換えた。

patch 'user/:id', to: 'users#update'

spec で assigns から返ってくる Hash が HashWithIndifferentAccess に変換されるバグが修正された ^

https://github.com/rails/rails/pull/5082

### controller
@foo = {}

### spec
# Rails 3.2
assings(:foo).class # => HashWithIndifferentAccess
# Rails 4.0
assings(:foo).class # => Hash

対応

Hash として扱うように spec を修正

link_to_function, button_to_function が deprecated になった ^

https://github.com/rails/rails/commit/5d1528740a

対応

link_to, button_tag で書き換えた。

-<%= button_to_function 'click!', 'alert("hello")' %>
+<%= button_tag 'click!', onclick: 'alert("hello")' %>

button_tag<input> タグから <button> タグになること、それにより <form> 内で <input type="submit"> より前に置くと Enter キーに submit より優先して反応することに注意する必要がある。

text_field, text_area のデフォルトサイズ指定がなくなった ^

Rails 3 までは

DEFAULT_FIELD_OPTIONS     = { "size" => 30 }
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }

定義されていたが、 Rails 4.0 では綺麗さっぱりなくなり、デフォルトに頼っていた text_field, text_area が小さく表示されるようになった。

対応

以下の monkey patch を作成した。(prepend が public なのは Ruby 2.1 からなので注意)

module TextFieldWithDefaultSize
  DEFAULT_FIELD_OPTIONS = { 'size' => 30 }
  def render
    @options = @options.stringify_keys
    unless @options.key?('size')
      @options['size'] = @options['maxlength'] || DEFAULT_FIELD_OPTIONS['size']
    end
    super
  end
end

module TextAreaWithDefaultSize
  DEFAULT_TEXT_AREA_OPTIONS = { 'cols' => 40, 'rows' => 20 }
  def render
    @options = DEFAULT_TEXT_AREA_OPTIONS.merge(@options.stringify_keys)
    super
  end
end

require 'action_view/helpers/tags/text_field'
require 'action_view/helpers/tags/text_area'
module ActionView::Helpers::Tags
  TextField.prepend TextFieldWithDefaultSize
  TextArea.prepend TextAreaWithDefaultSize
end

問題のなかった eruby がエラーになった ^

以下のコードがエラーになるようになった。

<%-# comment
%(<%= 1 %>) -%>
2
<%= 3 %>
4

これは Rails 3.2 では 3 4 と出力されるが Rails 4.0 ではエラーになる。

既存のコードを残したまま <%= 1 %> の部分をコメントアウトしたつもりらしい。むしろこれが今まで動いてたのがすごい。

どの修正が原因かは追っていない。(Erubis の version は上がってない)

対応

必要のないコメントだったので削除した。

ActionPack その他 ^

Railties ^

Gemfile から assets group が削除された ^

https://github.com/rails/rails/commit/49c4af43ec

production に必要のない gem (sass-rails, uglifier, coffee-rails) が assets group から外されたため、production 環境でインストールされる上に、ExecJS に Could not find a JavaScript runtime. と怒られる。

対応

--- a/Gemfile
+++ b/Gemfile
@@ -9,8 +9,8 @@
# Use SCSS for stylesheets
-gem 'sass-rails', '~> 4.0.0'
+gem 'sass-rails', '~> 4.0.0', group: :assets

 # Use Uglifier as compressor for JavaScript assets
-gem 'uglifier', '>= 1.3.0'
+gem 'uglifier', '>= 1.3.0', group: :assets

 # Use CoffeeScript for .js.coffee assets and views
-gem 'coffee-rails', '~> 4.0.0'
+gem 'coffee-rails', '~> 4.0.0', group: :assets

として、

# build 環境
RAILS_ENV=production bin/rake assets:precompile
# production 環境
bundle install --without development test assets

とするようにした。

Rails 3.2 で行われてたような config/application.rbBundler.require の修正や、rake assets:precompile 時の RAILS_GROUP=assets は特に必要なく動いている。

おそらく Rails 4.0 では Bundler を以下のように利用しているからだと思われる。

  1. config/boot.rbBundler.setup を引数なしで実行する
    • Bundler.setup.bundle/configBUNDLE_WITHOUT で指定した group 以外の gem を $LOAD_PATH に追加する
      • BUNDLE_WITHOUTbundle install --without <group> で指定した group が記述されている
  2. config/application.rbRails.env に合わせて Bundle.require を実行する
    • Bundle.require は引数で指定された group の gem を require する

そのため、この対応で sass-rails などは config/application.rb 時点では require されなくなるが、おそらく必要なときに適切に require されるため、development/test 環境、rake assets:precompile で問題は起きていない。

Rack ^

v1.4.5 -> v1.5.2

Rack::Session::Abstract::SessionHash が Hash を継承しなくなった ^

https://github.com/rack/rack/commit/e5b4d961e5

この影響で以下のような spec が動かなくなった。

session.should include('foo' => :bar)
session.should include(baz: :qux)

対応

以下のように書き換えた。

session.to_hash.should include('foo' => :bar)
session.to_hash.should include('baz' => :qux)

対応過程で Rails 4 で修正された reset_session で session が壊れるバグも踏んだ。

Sprockets ^

v2.2.2 -> v2.10.1

config.assets.precompile にセットした filter が full path を受け取れるようになった ^

https://github.com/sstephenson/sprockets/commit/93e5103bc9

この拡張のお陰で app/assets と vendor/assets で挙動を変えるというようなことが簡単に行えるようになった。

しかし、この影響で config.assets.precompile に call 可能な object を渡すと arity method が呼ばれるようになった。

config.assets.precompile << -> path { ... }

であれば気にしなくてよいが

class PrecompileFilter
  def call(path)
  end
end
config.assets.precompile << PrecompileFilter.new

としていた場合に NoMethodError: undefined method 'arity' となってしまう。

対応

以下を追加

  def arity
    method(:call).arity
  end

ActiveResource ^

Rails から削除された ^

対応

gem 'activeresource'

acts_as_list ^

v0.2.0 -> v0.3.0 (Rails 4 対応版)

callback が Symbol から String になった ^

https://github.com/swanandp/acts_as_list/commit/923484eba1

spec で acts_as_list の機能を一時的に off にしたいときに

skip_callback :create, :before, :add_to_list_top

としているところでエラーになった。

対応

skip_callback :create, :before, '(add_to_list_top)'

と書かないといけなくなった。

(これは pull req 案件だろうけど出してない。そのうち直ってまた修正が必要になりそうな気がする。)

その他 ^

  • activerecord-import 0.3.1 -> 0.4.1 (Rails 4 対応版)
  • rails-i18n 3.0.0 -> 4.0.0 (Rails 4 対応版)
  • i18n-js の挙動が微妙に変わった
    • .gitignore に /app/assets/javascripts/i18n/ を追加した
    • assets:precompile 前に i18n:js:export の実行が必要になった