#415 Upgrading to Rails 4
- Download:
- source codeProject Files in Zip (63.4 KB)
- mp4Full Size H.264 Video (35.6 MB)
- m4vSmaller H.264 Video (17.7 MB)
- webmFull Size VP8 Video (18.6 MB)
- ogvFull Size Theora Video (46.4 MB)
先週のRailsConfでRails 4.0のRC1が発表されました。この機会に自分のアプリケーションをRails 4にアップグレードし、動作確認をして、見つけたバグを報告してみてはいかがでしょうか。Rails 4の新機能の多くについてエピソード400で紹介したので、今回はRails 3.2のアプリケーションをアップグレードする方法に焦点をあてます。そうすることによって、自分のアプリケーションを使って順を追って理解することができるでしょう。
まず最初に、Rails 3.2の最新バージョンで動作していること、アプリケーションのgemがすべて最新版であることを確認します。またRuby 1.9.3以降が必要です。合わせてアップグレード作業の前に、アプリケーションのテストスイートがすべてパスすることを確認しておくのがいいでしょう。自動化されたテストがない場合、マニュアルでのテストが必要になるので作業はより難しくなるでしょう。少なくとも、作業を始める前に何らかの統合テストを追加しておくことをおすすめします。今回のアプリケーションにはテストがありすべてがパスしているので、Rails 4へのアップグレード作業を進めることにします。これは独立したブランチで行うほうがいいので、ここで作成します。
$ git checkout -b rails4
次にGemfileのRailsのバージョンを3.2.13
から4.0.0.rc1
(あるいは現在の最新バージョン)にアップグレードします。他のいくつかのgem(assets
グループ内のものも含む)についてもバージョン番号を変更する必要があります。Rails 4はこのグループを削除するので、ここでもそれを削除してそこにあったgemをすべてのグループに含めます。
gem 'rails', '4.0.0.rc1' gem 'sqlite3' gem 'sass-rails', '~> 4.0.0.rc1' gem 'coffee-rails', '~> 4.0.0.rc1' gem 'uglifier', '>= 1.3.0'
Rails 4のデフォルト設定では、production環境は動的にアセットを生成しようとはせず、事前にコンパイルされた静的なアセットを使用するので、この変更に合わせるために、アセットはproduction環境では事前にプリコンパイルされます。アセットのプリコンパイルにrake assets:compile
を実行する場合は、その前に環境をproduction
に設定します。
$ RAILS_ENV=production bundle exec rake assets:precompile
この変更によるプラスの副作用として、アプリケーションの設定ファイルが単純化されます。Rails 3ではassets groupに一致したgemをロードする必要がありましたが、それはdevelopment
とtest
の環境だけでした。つまり、以下のコード、
if defined?(Bundler) # If you precompile assets before deploying to production, use this line Bundler.require(*Rails.groups(:assets => %w(development test))) # If you want your assets lazily compiled in production, use this line # Bundler.require(:default, :assets, Rails.env) end
を次のように書き換えることができます。
Bundler.require(:default, Rails.env)
production環境の設定でassets groupの指定が必要な場合は、自分でその記述を追加してこのファイルでグループの読み込みの振る舞いをカスタマイズします。
実際にこれを試すために、bundle update
を実行して新バージョンをインストールしてみます。これはうまくいくように見えますが、このコマンドは時々予想しない振る舞いをする場合があります。その例を見るために、 bundle outdated
を実行します。あまり知られていないコマンドですが、古くなったgemを探すときに便利です。
$ bundle outdated Fetching gem metadata from https://rubygems.org/......... Fetching gem metadata from https://rubygems.org/.. Resolving dependencies... Outdated gems included in the bundle: * builder (3.2.0 > 3.1.4) * paper_trail (2.7.1 > 1.6.4)
今回のアプリケーションに対してこれを実行すると、バージョンが古いgemが2つ見つかります。builder
のバージョンは少し古く、paper_trail
がかなり古い状態です。Rails 4と互換性のある新しいバージョンをみつけられないというメッセージの理由がこれです。これが、Gemfileが許容するバージョンについて厳密であるべき理由の一つです。Gemfileではpaper_trail
のバージョンを指定していなかったので、ここでそれを指定します。
gem 'paper_trail', '~> 2.7.1'
bundle update
を実行すると、バージョンの衝突に関するエラーが発生します。PapertrailはActiveRecordのバージョンが3.0以上であることを求めるため、Rails 4で動作する別のバージョンを探さなくてはいけません。PapertrailにはRails 4用のGit branchがあるのでそれをGemfileで使用できます。しかしこの方法は他のgemでは適用できないかも知れないので、影響を受けるそれぞれのgemのissue trackerで状態を確認する必要があります。
gem 'paper_trail', github: 'airblade/paper_trail', branch: 'rails4'
bundle updateを実行すると、PapertrailのGithubプロジェクトにあるバージョンが取得されてアップデートは成功します。
これでgemがすべて最新版になったのでspecを実行してみます。ですがその結果は悲惨なものです。出力にはエラーが表示され、 EpisodesController
のcaches_page
メソッドが未定義とあります。これは我々のアプリケーションがRails 4で削除されたpage cachingを使用しているからです。Rails 4ではその他にもobservers、protected model attributes、ActiveResourceなどが削除されました。救いとしては、これらの機能が必要な場合は、gemを使って簡単に追加することができます。使ったほうがいいものについて、アップグレードの過程でいくつか追加します。
gem 'protected_attributes' gem 'rails-observers' gem 'actionpack-page_caching' gem 'actionpack-action_caching' gem 'activerecord-deprecated_finders'
これによって、Rails 4から除かれた機能に関連するエラーが出なくなります。今回のようなメジャーバージョンのアップグレードを行う場合は、できるだけ早くアプリケーションを動作する状態にすることが大事なので、大規模なリファクタリングやその他の大きな変更は避けるべきで、そのためにはこれらのgemを追加するのが便利です。テストがパスしたら、そこから初めてコードを整理して、できるのならこれらのgemに依存しないように変更するのがいいでしょう。bundle
コマンドでこれらのgemをインストールし、再度specを実行してパスするか確認します。今回は、いくつかのdeprecation warningと一緒に、routeに関連したエラーが表示されます。
$ spec . ... You should not use the `match` method in your router without specifying an HTTP method. If you want to expose your action to both GET and POST, add `via: [:get, :post]` option. If you want to expose your action to GET, use `get` in the router: Instead of: match "controller#action" Do: get "controller#action" ...
match
メソッドは受け付けられなくなりました。通常であれば単純にこれをgetに置き換えればいいのですが、複数のメソッドをサポートしたい場合はそれらを追加するかvia:
オプションを付けてmatchを使用します。
# match 'new', to: 'episodes#new', via: [:get, :post] get 'new', to: 'episodes#new'
specを実行すると、多くのテストが落ちます。そのほとんどはprotected attributesに対するmass assignmentに関するもので、その中にはPapertrailのVersion
モデルのものも含まれています。Rails 4にはstrong parametersがあるのでこれは動作するはずですが、ここではまだprotected attributesを使用しています。この問題を回避するには、アプリケーションの設定ファイルを修正して、active_record.whitelist_attributes
の設定をfalse
にします。デフォルト設定はこれはtrue
になっていて、すべてのモデルについてattr_accessible
が定義されていることを想定していますが、今回はstrong parameterを使用しているアプリケーションの移行のためこれには当たりません。
config.active_record.whitelist_attributes = false
これでspecがパスしましたが、まだ多くのdeprecation warningがあります。これらを個別に確認して、分類してみましょう。多くはwhiny_nils
などの設定オプションに関連していますが、それらは必要ではなくなりました。developmentの設定ファイルでwhiny_nils
に関連する行を削除し、eager_load
オプションを追加してfalse
に設定します。その他の必要なくなったオプションも同じく削除します。
Screencaster::Application.configure do # Settings specified here will take precedence over those in config/application.rb # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false config.eager_load = false # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = false # Print deprecation notices to the Rails logger config.active_support.deprecation = :log # Expands the lines which load the assets config.assets.debug = true end
次にproduction環境の設定を行ないます。ここでeager loadingを有効にし、config.assets.compress
オプションを変更します。
config.eager_load = true # Compress JavaScripts and CSS config.assets.js_compressor = :uglifier
test環境ではwhiny_nils
オプションを削除し、eager loadingをfalse
に設定します。strong parameterに移行するので、mass_assignment_sanitizer
は削除します。
# Log error messages when you accidentally call methods on nil # config.whiny_nils = true config.eager_load = false # Raise exception on mass assignment protection for Active Record models # config.active_record.mass_assignment_sanitizer = :strict
asset pipelineについて、一つ注意点があります。アプリケーションの設定ファイルを見ると、config.assets.version
オプションがtrue
に設定されて残っています。Rails 4ではasset pipelineがデフォルト設定で有効になるのでこれは必要ありません。このアプリケーションでasset pipelineを使わないのであればこれをfalse
に設定します。使う場合はこのオプションを削除するだけです。
もう一ヶ所設定を変更しなくてはいけないのが、secret token initializerです。Rails 4ではこのファイルの設定オプションが、secret_token
からsecret_key_base
に名前が変わりました。Rails 3からの移行中には両方のオプションを指定する必要がありますが、アプリケーションの移行が成功したらsecret_token
オプションは削除できます。secret_key_base
には違うトークンを使う方がいいでしょう。
Screencaster::Application.config.secret_token = '762a2f23950e306261908d4e5519ffe71ce626b119e9fc03a012ba86f46d82ef32d72f283633bacc2f59cf94ce5968552fe97d157e7f00560c1217d4592dda09' Screencaster::Application.config.secret_key_base = 'xx762a2f23950e306261908d4e5519ffe71ce626b119e9fc03a012ba86f46d82ef32d72f283633bacc2f59cf94ce5968552fe97d157e7f00560c1217d4592dda09'
これが必要な理由は、クライアント側に保存されているシリアライズされたcookieを暗号化されたcookieに移行させるからです。これによってユーザがセッションクッキーの内容を簡単に見えるのを防ぎます。
ここでは多くの設定の変更を行ないましたが、これで全てではありません。Rails 4アプリケーションを新規作成し、生成された設定ファイルを見て、現在アップグレード中のアプリケーションと比較して、さらにコピーしなくてはいけない部分がないかをチェックします。
これらの変更が済んだところで再度specを実行して、どこかを壊していないか、またdeprecationエラーの数が減ったかどうかを確認します。テストはすべてパスしますが、まだdeprecation warningが出るので、これらを減らすことができるのかどうか見てみます。警告の一つは、Episode
モデルの中で発生しています。名前付きスコープを定義したときはいつも、次の例のlambdaのような、呼び出し可能なオブジェクトを2つ目のオブジェクトとして渡すようになりました。
scope :published, -> { where('published_on <= ?', Time.now.to_date) }
これが必要な理由は、scopeに動的なもの(例えばcurrent time)を含んで設定することが簡単になるからです。もしlambdaを使わなければクラスがロードされたときの時間が使用されますが、今回の変更によって毎回scopeが呼び出されたときの時間が使用されることになります。まだ発生しているその他のdeprecationが、EpisodesController
のindex
アクションの中にあります。
def index @episodes = Episode.published.find_all_by_pro(false) end
このような動的なfinderメソッドはサポートされなくなりました。その代わりに、find
メソッドを使ってオプションとしてフィールドを渡します。
def index @episodes = Episode.published.where(pro: false) end
ここでspecを実行すると、すべてパスしてdeprecation warningも出ません。
deprecationをすべて片付けたので、strong parameterのようなその他の移行作業に集中することができます。strong parameterについてはエピソード371で取り上げましたが、基本的にはモデルからmass assignmentの制限を移動させて、attr_accessible
を使う代わりにコントローラで制限をかけます。Episode
モデルから属性を削除します。
#attr_accessible :description, :name, :seconds, :published_on, :timecode
コントローラでepisode_params
というprivateメソッドを定義します。これは従来からのやり方ですが、その他の方法もあります。
def episode_params params.require(:episode).permit(:description, :name, :seconds, :published_on, :timecode) end
このメソッドでは、渡されたパラメータに対してpermit
を呼び出して、それに対してpermitされたパラメータを渡します。params[:episode]
の代わりにparams.require(:episode)
を使って、パラメータのhashが存在することを保証することで、なかった場合にnil
の例外が発生することを防ぎます。フォームからパラメータを取得するときにはいつでもこのメソッドを呼び出すことができます。
def create @episode = Episode.new(episode_params) if @episode.save redirect_to @episode, notice: 'Episode was successfully created.' else render action: "new" end end
すべての置き換えが終わったら、Gemfileからprotected_attributes
gemを削除します。合わせてアプリケーションの設定ファイルから、config.active_record.whitelist_attributes
などの関連する設定オプションを削除します。
アプリケーションから移行関連のgemの一つをなんとか削除することができましたが、その他のものを削除するのは簡単ではないかもしれません。rails-observers
の代わりにcallbackを使うことができます。page_caching
はまだ役に立つ場面があるので使い続けることにします。HTTP cachingについてはエピソード321で紹介しましたが、action_caching
の置き換えに適しています。activerecord-deprecated_finders
についてはRails 4.0では依存関係にあるので削除できます。Rails 4.1からは削除される予定です。
最後にいくつかの修正すべき部分について触れておきます。まずコントローラでは、before_filter
がbefore_action
に変わりました。まだdeprecation warningは出ませんが、新しい名前の方が多少意味がわかりやすいでしょう。routesファイルでは、putを使っていた部分は代わりにpatch
を使います。このほうがレコードの更新という実態には合っています。
アプリケーションに/test
ディレクトリがある場合は構造が少し変わったので、新規のRails 4アプリケーションを見て変更点を確認することをお勧めします。また/vendor/plugins
はなくなったので、プラグインはgemに置き換えるか/lib
に移動します。
ここで紹介できなかったRails 4の機能はまだ多くありますが、それらについては過去のエピソードで紹介しました。RC公開に関するブログ記事の中にもいくつかの情報源が挙げられているので参照してください。