Ruby on Rails チュートリアル

プロダクト開発の0→1を学ぼう

第2版 目次

第12章Rails 4.0へのアップグレード

本章は、Railsを最新バージョンであるRails 4.0にアップグレードするために追加された補足資料です。本書はRails入門を目的としたチュートリアルであるため、その意味では本書のRails 3.2対応版とRails 4.0対応版には大きな違いは生じません。実際、本章で補足すべきRail 4.0の重要な新機能は、Strong Parameterしかありません。Rail 4.0を使用した本格的なWeb開発を学びたいという要望に応えて、Rails 4.0対応版のRailsチュートリアルも公開いたしました。オンライン版は無料にて提供、電子書籍版 (PDF/EPUB) は http://tatsu-zine.com/books/railstutorial から購入することができます。なお、Rails 3.2対応版とRails 4.0対応版の主な違いについては、Rails 4.0向けチュートリアルのコラム1.1を参照してください。

本章の最初の目的は、Railsチュートリアルのサンプルアプリケーションを、Rails 3.2からRails 4.0にアップグレードすることです (12.1)。次に、主要な変更点である、attr_accessible (6.1.2.2) からStrong Parameter (12.2) への変更について説明します。最後に、2つの小さな機能をサンプルアプリケーションに追加します。この追加機能は、Rails 4.0とは本質的には関係ありませんが、セキュリティ上の理由から、この場を借りて12.3で解説します。

加えて、本章の内容を補うためのスクリーンキャストも制作しました。こちらもrailstutorial.jp/screencasts から購入することができます。本スクリーンキャストは、サンプルアプリケーションをRails 3.2からRails 4.0にアップグレードするところをライブデモする動画です。Strong Parametersやセキュリティ上の更新を実際にデモしながら解説しています。

12.1Rails 3.2からRails 4.0へのアップグレード

Railsとそれを取り囲むエコシステムは比較的成熟してきましたが、Railsは今でも急速に変化し続けています。今回のようなメジャーな更新が発生すると、多くの細かな機能が損なわれるのが普通です。実際、著者自身も既存のRailsプロジェクトをその場でアップグレードしようとした際、しばしば苦い経験をしてきました。そしてそのたびに、最後にはrails newを使ってアプリケーションを最初から作り直すはめになってしまいました。それというのも、rails newによって生成される大量の設定ファイルにはさまざまな数値やデフォルト値が設定されているのですが、これらの値はバージョンによって異なっているからです。そしてこれらの値を既存のプロジェクトに反映する作業は非常に困難です。そこで本節では、次のような方針でアップグレードの作業を進めることにします。最初に、空のRails 4.0のプロジェクトを作成し、プロジェクトをとにかく動く状態にします。その後、既存のプロジェクトから順次コードを移植していき、アプリケーションが正しく動作することを1つずつ確認しながら、作業を進めていきます。

Railsアプリケーションのアップグレードは難易度の高い作業です。したがって、本節の難易度も高くなっています。本節をお読みいただければわかるように、Railsのアプリケーションのアップグレードは、残念ながら「本に載っている手順に従うだけで完了するような作業」ではありません。実際、本節で行うのは演習の延長線上にある応用作業です。これまでの演習との違いは、多くの役に立つヒントとマニュアル (スクリーンキャスト) が用意されている点です。たとえば「アップグレードされたスクリーンキャスト」では、著者が悪戦苦闘の末にアップグレードを成功させる様子を見ることができます。なお、「Railsアプリケーションのアップグレードの途中で行き詰まってしまったけど、スクリーンキャストは購入したくない」という場合は、GitHub上のアップグレード済みサンプルアプリケーションを参考にしてください。

12.1.1Rails 4.0のセットアップ

Rails 4.0を使ってアプリケーションをセットアップする手順は、第1章から第3章までの手順と実質的には同じです。具体的には、1) RVMのgemsetの作成 (またはrbenvの環境構築)、2) Railsのインストール、3) Bundlerを使ったgemのインストール、4) RSpecの設定、という手順で進めます。

もしRVMまたはrbenvを既に使っているのであれば、Rails 4.0対応版チュートリアルの第1章に従って、システムを設定することできます。たとえばRVMを使っている場合は、Rails 4.0のサンプルアプリケーション用に新しくgemsetを作成することをお勧めします。その場合は次のコマンドを実行します。

$ rvm use 2.0.0@railstutorial_rails_4_0_upgrade --create

次に、Rails 4.0をインストールします。

$ gem install rails -v 4.0.0

次に、アプリケーションをアップグレードするための新規プロジェクトを作成します。

$ cd ~/rails_projects
$ rails new sample_app_4_0 --skip-test-unit
$ cd sample_app_4_0

この時点で、rails serverを実行してアプリケーションをローカルで走らせ、正常に動作していることを確認してください。

次に、アップグレードに必要な全てのgemをインストールします。具体的には、Gemfileをリスト12.1のように書き換えます。

リスト12.1 サンプルアプリケーションのアップグレードに必要なGemfile
source 'https://rubygems.org'
ruby '2.0.0'

gem 'rails', '4.0.0'
gem 'bootstrap-sass', '2.3.2.0'
gem 'bcrypt-ruby', '3.0.1'
gem 'faker', '1.1.2'
gem 'will_paginate', '3.0.4'
gem 'bootstrap-will_paginate', '0.0.9'

group :development, :test do
  gem 'sqlite3', '1.3.7'
  gem 'rspec-rails', '2.13.1'
  # The following optional lines are part of the advanced setup.
  # gem 'guard-rspec', '2.5.0'
  # gem 'spork-rails', github: 'sporkrb/spork-rails'
  # gem 'guard-spork', '1.5.0'
  # gem 'childprocess', '0.3.6'
end

group :test do
  gem 'selenium-webdriver', '2.0.0'
  gem 'capybara', '2.1.0'
  gem 'factory_girl_rails', '4.2.0'
  gem 'cucumber-rails', '1.3.0', :require => false
  gem 'database_cleaner', github: 'bmabey/database_cleaner'

  # Uncomment this line on OS X.
  # gem 'growl', '1.0.3'

  # Uncomment these lines on Linux.
  # gem 'libnotify', '0.8.0'

  # Uncomment these lines on Windows.
  # gem 'rb-notifu', '0.0.4'
  # gem 'win32console', '1.3.2'
end

gem 'sass-rails', '4.0.0'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.0'
gem 'jquery-rails', '2.2.1'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'

group :doc do
  gem 'sdoc', '0.3.20', require: false
end

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end

gem 'protected_attributes'

リスト12.1の最後の行に注目してください。このgem は、attr_accessible (6.1.2.2) をRails 4.0のアプリケーションでも使えるようにするために必要なgemです。

gem 'protected_attributes'

なお、attr_accessibleをStrong Parametersに変更し終わったらこのgemは必要なくなるので、このgemは12.2で削除する予定です。

いつもと同様に、bundle installを実行することでリスト12.1のgemをインストールできます。また、bundle updateも実行しておいてください (念のため、もう一度bundle installを実行しておくと安心です)。

$ bundle install --without production
$ bundle update
$ bundle install

gemをインストールする際に (rspec-railsと一緒に) RSpecもインストールされますが、generateコマンドを使ってRSpecを再度インストールする必要があります。

$ rails generate rspec:install

それではRails 3.2のアプリケーションの移植作業を始めましょう。最初に、生成されたREADMEファイルを削除して、Markdown 版の README (リスト3.2) に置き換えてください。

$ rm -f README.rdoc
$ cp ../sample_app_2nd_ed/README.md .
$ cp ../sample_app_2nd_ed/.rspec .

このとき、RSpec の設定ファイル (.rspec) も一緒にコピーしておいてください。

この時点で、サンプルアプリケーションをGit管理下に置くのが賢い方法です。Railsのアップグレードのような多くのエラーが発生する作業では、Gitは本当に役に立ちます。また、今のうちにRailsが自動生成した.gitignoreファイルをリスト12.2の内容に置き換えておくことをお勧めします。

リスト12.2 サンプルアプリケーションの .gitignore ファイル。
# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea
.secret

リスト12.2では、.secretファイルを無視する行が追加されていることに注目してください。このファイルは、セキュリティの向上のために生成されたファイルです。詳細については12.3.1で説明します。

最後に、Capybaraのドメイン固有言語 (DSL: domain-specific language) を明示的にインクルードする設定が必要です。これは、RSpecのrequest spec (統合テスト) 機能を使うために、デフォルトの文法を変更しておく必要があるからです。実際には、リスト12.3に示すように、spec_helper.rbに1行追加するだけで設定できます。

リスト12.3 Capybara DSLをRSpecに追加する。
spec/spec_helper.rb
.
.
.
RSpec.configure do |config|
  .
  .
  .
  config.include Capybara::DSL
end
バージョン管理にGitを使っているのであれば、この時点で最初のコミットを行うとよいでしょう。
$ git init
$ git add .
$ git commit -m "Initialize repository"

本書の内容を簡潔にするために、以後、Gitコミットの指示を省略しますが、折を見てGitコミットするように心掛けてください。

12.1.2テストをパスするようにする

アップグレードを完了するために、本チュートリアルで全面的に使用されているテストスイートをここでも活用することにします。このようなときには、既存アプリケーションのtestディレクトリまたはテストファイルを (一度にすべてではなく) 1つずつ新しいRailsプロジェクトにコピーし、そのたびにそれに対応するアプリケーションコードを順次コピーする、という手順をお勧めします。最初はテストを実行できる状態にするだけで試行錯誤が必要になることでしょう。それを終え、実際にテストを行うと、当初はほとんどの場合失敗する (赤色になる) はずです。その状態からファイルを変更して、このテストがパスする (緑色になる) ようになればよいわけです。ファイルを変更するとは、つまりそのテストに対応するコードを既存のアプリケーションからコピーしてくるということです。ただし、Rails 3.2とRails 4.0の違い、または関連するgem (特にRSpecとCapybara) の違いによってはこのシナリオ通りにならず、コードをコピーしてきてもテストが失敗することがあります。

著者の経験では、最初にコピーするのはモデルのspecと、それに対応するモデルのコードにしておくのがよいようです。モデルのテストは、コントローラやビューの結合テストと比べるとモデル内で完結している傾向が高いためです。そこで、モデルをテストできるようにするために最初にマイグレーションからコピーしましょう。

$ cp -r ../sample_app_2nd_ed/db/migrate db/
$ rake db:migrate
$ rake test:prepare

../sample_app_2nd_ed/db/migrateの末尾にスラッシュを追加しないよう注意してください。システムによっては、マイグレーションファイルがコピーされてもディレクトリがコピーされないことがあります。なお、Rails 4.0ではrake db:test:prepareコマンドを使用するのが一般的ですが、従来のrake db:test:prepareコマンドも問題なく動作します。

以後、このようにテストとアプリケーションを既存のアプリケーションから順次新しいアプリケーションにコピーすることを繰り返し、移行を完了させます。コピーに使用するツールは、コマンドラインでもGUIファイルブラウザでも好みのもので構いません。

$ cp -r ../sample_app_2nd_ed/spec/models spec/
$ cp -r ../sample_app_2nd_ed/app/models app/

ファイルのコピー時にはエラーが発生することも多く、システム環境に依存するコピー項目もあるので、本書ではコピーするファイルをすべての場合について列挙することはしません。ファイルをどのような順序でコピーするかについては、各自にお任せすることにします。

特に注意して頂きたいのが、generateで生成されたファイル (アプリケーションレイアウトapplication.html.erbなど) には重要なコードが含まれていることがあり、単純な上書きコピーを行えない場合があるということです。このような場合には、ご面倒でもコピー元とコピー先のファイルを開き、内容を確認しながら必要なコードだけを手動でコピーしてください (スクリーンキャストはこういう作業の説明が楽ですね)。

12.1.3特定の問題

アップグレードにともなって発生する多数の問題を事前にすべて予測することは、一般的には極めて困難です。本章では基本的に、一般的な戦略を述べるだけにしておきます。著者が遭遇した特定の問題についてはある程度記載いたしましたが、すべてのトラブルを網羅することは事実上不可能ですので、どうかご容赦願います。アップグレード作業にはどうしても相当なフラストレーションがつきまといます。この困難を突破するには、とにかく忍耐、とにかくネット検索、それしかありません (ご心配なく、ちゃんとカンニングペーパーも用意してあります)。

モデル

protected_attributes gem (リスト12.1) の動作がRails 3.2の「アクセス可能な属性」と異なっているため、マスアサインメントのセキュリティエラーをテストするコードは4.0では動作しなくなりました。このテストコードは削除する必要があります。たとえば、以下のコードはUserモデルのspec (spec/models/user_spec.rb)ですが、これは削除が必要です。

describe "accessible attributes" do
  it "should not allow access to admin" do
    expect do
      User.new(admin: true)
    end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
  end
end

これに対応するテストがMicropostモデルとRelationshipモデルにもあるので、それらも削除してください。

モデルでは他にも、デフォルトのスコープ (10.1.4.1) に関して以下の非推奨警告が表示されます。

DEPRECATION WARNING: Calling #scope or #default_scope
with a hash is deprecated.

この問題は、次のように修正できます。

default_scope order: 'microposts.created_at DESC'

上のコードを以下に置き換えます。

default_scope -> { order('created_at DESC') }

置き換え対象のコードはapp/models/micropost.rbにあります。Rails 4.0のdefault_scopeは、従来のハッシュに代えて、Ruby 1.9で導入されたlambdaを引数に取るように変更されました。->は、Ruby 1.9で導入されたlambdaの略記法です。詳細については4.0チュートリアルの第10章を参照してください。

Micropost spec (spec/models/micropost_spec.rb) もわずかに変更が必要です。dupメソッドを使用して@user.micropostsを複製していますが、これをto_aメソッド (配列への変換) に置き換えます。以下はdupを使用した既存のコードです。

it "should destroy associated microposts" do
  microposts = @user.microposts.dup
  @user.destroy
  microposts.should_not be_empty
  microposts.each do |micropost|
    Micropost.find_by_id(micropost.id).should be_nil
  end
end

理由は不明ですが、dup呼び出しは Rails 4.0では動作しなくなりました。to_aに置き換えることで正常に動作します。

it "should destroy associated microposts" do
  microposts = @user.microposts.to_a
  @user.destroy
  expect(microposts).not_to be_empty
  microposts.each do |micropost|
    expect(Micropost.where(id: micropost.id)).to be_empty
  end
end

コントローラとビュー

Relationshipsはテストの量がかなり少ないので、次はRelationshipsのテストを行うことをお勧めします。テストを行うと、ルーティングが失敗します。これは、Rails 4.0ではconfig/routes.rbmatchでHTTPメソッドをviaで明示的に指定することが要求されるようになったためです。

  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :sessions,      only: [:new, :create, :destroy]
  resources :microposts,    only: [:create, :destroy]
  resources :relationships, only: [:create, :destroy]
  root to: 'static_pages#home'
  match '/signup',  to: 'users#new',            via: 'get'
  match '/signin',  to: 'sessions#new',         via: 'get'
  match '/signout', to: 'sessions#destroy',     via: 'delete'
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'

次はrequest specです。

it { should have_selector('title', text: 'Sign up') }

上のようなコードは、Capybaraの変更が原因で失敗します。Capybaraのhave_selectorは画面に実際に表示される要素にしか適用できなくなりました (ページタイトルは一部のブラウザではトップバーに表示されますが、「実際に表示される要素」としては扱われません)。これを解決するには、新しいhave_titleメソッドを使用します。

it { should have_title(text: 'Sign up') }

正規表現による置き換えをサポートするエディタ (VimやSublime Textなど) を使用できる場合は、以下の正規表現を使用して一括置き換えを行えます。

have_selector\('title', text: (.*?)\)

置き換える文字列は以下を使用します。

have_title($1)

ただし、静的なページ用のspec (spec/requests/static_pages_spec.rb) では括弧を使用していないので、以下の正規表現も使用する必要があります。

have_selector 'title', text: (.*?)

置き換える文字列は以下を使用します。

have_title $1

(テキストエディタを使用して置き換えを行うという部分の意味がよくわからない場合は、スクリーンキャストを購入いただければその中で実際の操作をお見せいたします)

Capybaraの変更によって動かなくなった部分は他にもあります。inputフィールドのtitleタグは画面に表示されないので、have_selectorが効かなくなりました。これを解決するには、強力なXPathメソッドを使用します。このメソッドはHTML5を含むXMLドキュメントの要素を自在にナビゲートできます。変更は以下のとおりです。

describe "toggling the button" do
  before { click_button "Follow" }
  it { should have_selector('input', value: 'Unfollow') }
end
.
.
.
describe "toggling the button" do
  before { click_button "Unfollow" }
  it { should have_selector('input', value: 'Follow') }
end

上を以下のように置き換えます。

describe "toggling the button" do
  before { click_button "Follow" }
  it { should have_xpath("//input[@value='Unfollow']") }
  end
.
.
.
describe "toggling the button" do
  before { click_button "Unfollow" }
  it { should have_xpath("//input[@value='Follow']") }
end

置き換えの対象はspec/requests/user_pages_spec.rbにあります。

著者が遭遇した最後のエラーは、以下のコードのあいまいな箇所についてCapybaraが警告してきたことです。

it "should be able to delete another user" do
  expect { click_link('delete') }.to change(User, :count).by(-1)
end

上のコードはspecs/requests/user_pages_spec.rbにあります。以前のCapybaraは、古いdeleteリンクを問題なくクリックすることができましたが、新しいバージョンでは動作が厳密になり、リンクをCSS idなどで正確に指定することが要求されるようになりました。これはもちろん歓迎すべき変更です。このおかげで、テスト中に誤ったリンクがクリックされずに済むからです。しかしこのチュートリアルのサンプルでは、どのリンクがクリックされるかは重要ではありません。これを解決するには、match: :firstオプションハッシュを渡して、最初に見つけたdeleteリンクをクリックするようCapybaraに指示します。

it "should be able to delete another user" do
  expect do
    click_link('delete', match: :first)
  end.to change(User, :count).by(-1)
end

これでテストスイートはパスするはずです。

12.1.4仕上げ

残る作業は、スタイルシートのコピーと、Bootstrap JavaScriptをapplication.jsに追加することです。

$ cp -r ../sample_app_2nd_ed/app/assets/* app/assets/

上のコピー中にapplication.jsを上書きしないように注意してください (上書きの警告が表示されたら [いいえ] をクリックします)。次に、application.jsの内容を12.4のとおりに更新し、Bootstrap Javascriptを追加します。

リスト12.4 Bootstrap JavaScriptを追加する。
app/assets/javascripts/application.js
.
.
.
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap
//= require_tree .

ここまで問題なく進めることができれば、アプリケーションは4.0で動作するようになるはずです。以下のようにデータベースにサンプルデータを導入してローカルサーバーで実行することで、実際の動作を確認できます。

$ cp ../sample_app_2nd_ed/lib/tasks/* lib/tasks
$ rake db:populate
$ rails server

(上の手順ではスタイルシートがコピーされないことに気付いた方がいるかもしれません。著者もスクリーンキャストを制作中に気付きました。でもこのぐらいは簡単に修正できるはずです)

12.1.5追加機能

Rails 4.0には、本章の範疇を超える追加機能がいくつかあります。中でも代表的なのはTurbolinksでしょう。これはJavaScriptを使用してアプリケーションのユーザーインターフェイスを高速化する技法です。他にRussian doll caching (一度描画したHTMLをインテリジェントに保存して不要な再描画を抑制する新機能) もあります。このようなやや高度なトピックを理解するには、RailsCastsが便利です。

12.2Strong parameters

いよいよ、(Railsチュートリアルにとっては) Rails 3.2とRails 4.0の最大の違いであるStrong Parametersについて説明します。これはattr_accessibleを置き換えるものであり、マスアサインメントの脆弱性 (9.4.1.1) を防止するためのものです。attr_accessibleはモデルに対して適用しますが、Strong Parametersはコントローラ層に対して適用するという点が大きく異なります。認証や認可はコントローラで行われますが、それと同じ場所に適用するという点で、Strong Parametersは従来よりも適切な手法です。

以下の作業は、12.1でアップグレード完了したアプリケーションに対して行うことをお勧めしますが、GitHub上の完成品を使用してもかまいません。後者の場合は、ここより先の変更を適用していないブランチがありますので、それをチェックアウトすることをお勧めします。

$ git clone https://github.com/mhartl/sample_app_4_0_upgrade
$ cd sample_app_4_0_upgrade/
$ git checkout -t origin/only-upgraded

Strong Parametersを実装するために、最初にprotected_attributes gemをGemfileから削除してください (リスト12.1)。次に、UserモデルとMicropostモデルからattr_accessibleを削除します。

Strong parametersを使用して、どのパラメータを必須とし、どのパラメータを許可するかを指定できます。さらに、paramsハッシュをまるごと渡すとエラーが発生するようになっているので、Railsはデフォルトでマスアサインメントの脆弱性から保護されるようになりました。この変更により、テストは失敗するようになったはずです。

この場合、paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしたいと考えています。これは、以下のように記述することで行うことができます。

params.require(:user).permit(:name, :email, :password, :password_confirmation)

このコードの戻り値は、paramsハッシュのバージョンと、許可された属性です (:user属性がない場合はエラーになります)。

これらのパラメータを使いやすくするために、user_paramsという外部メソッドを使用するのが慣習になっています。このメソッドは適切な初期化ハッシュを返します。変更の結果をリスト12.5に示します。

リスト12.5 createアクションでStrong Parametersを使用する。
app/controller/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      sign_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
    end

  def edit
  end

  def update
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      sign_in @user
      redirect_to @user
    else
      render 'edit'
    end
  end
  .
  .
  .
  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

同じようなコードを使用して、マイクロポストも同様に保護します (リスト12.6)。

リスト12.6 Micropostsコントローラのcreateアクション。
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  .
  .
  .
  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end
  .
  .
  .
  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end

以上で完了です。ついにサンプルアプリケーションはマスアサインメントの脆弱性に対する免疫を獲得しました。必要な更新も問題なく行えています。テストスイートを実行することでこのことを確認できます。

12.3セキュリティのアップデート

本章の最後に、小規模なセキュリティアップデートを2つ紹介します。通常、マイナーな変更の紹介は次の版に回すのですが、本章の冒頭に記したとおり、セキュリティ問題は常に最優先課題であるため、ここに記載いたします。

12.3.1秘密鍵

1番目のセキュリティ変更は、secret_token.rbを更新して秘密鍵を動的に生成するようにします。この秘密鍵は、セッション変数を暗号化するためにRailsで使用されます。この秘密鍵は、デフォルトではリスト12.7に示したようにランダムな文字列がハードコードされます。ソースコードが非公開のWebアプリケーションであればこれでもよいのですが、Githubなどでソースを公開する場合にはこのままでは危険です。このトークンを一般にさらしてしまうと、悪意のある攻撃者がセッションcookiesを改ざんする可能性が生じます。

リスト12.7 デフォルトの秘密トークンファイル。
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.

# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
SampleApp::Application.config.secret_key_base = 'ced345e01611593
c1b783bae98e4e56dbaee787747e92a141565f7c61d0ab2c6f98f7396fb4b178
258301e2713816e158461af58c14b695901692f91e72b6200'

これを解決する方法は、秘密鍵をローカルで動的に生成してファイルに保存することです。実はこの問題はPhenoelit作 “Rails脆弱性自動レポート用ボット” が著者に親切にも警告してくれたおかげで気付いたもので、同ページに解決法も示されています。リスト12.8に示したように、この秘密鍵は必要に応じて読み出して利用されます。ここで特に注意して頂きたいのは、リスト12.2にも示したように、.secretファイルを.gitignoreに追記し、このファイルが間違っても一般からアクセス可能なリポジトリに置かれることのないようにする必要があるという点です。

リスト12.8 動的に秘密鍵を生成する秘密トークンファイル。
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly, such as by adding
# .secret to your .gitignore file.

require 'securerandom'

def secure_token
  token_file = Rails.root.join('.secret')
  if File.exist?(token_file)
    # Use the existing token.
    File.read(token_file).chomp
  else
    # Generate a new token and store it in token_file.
    token = SecureRandom.hex(64)
    File.write(token_file, token)
    token
  end
end

SampleApp40::Application.config.secret_key_base = secure_token

12.3.2記憶トークンを暗号化する

最後のセキュリティアップデートは、記憶トークン (8.2.1) を暗号化することです。この暗号化のタイミングは、記憶トークンをユーザーのブラウザに保存した、かつ記憶トークンをデータベースに保存するでなくてはなりません。こうすることにより、万一データベースに対して不正アクセスが行われても、攻撃者は記憶トークンを使用して特定のユーザーとして不正にサインインすることはできなくなります。本章では必要なコードを示すだけにとどめます。詳細については、Rails 4.0用のチュートリアルの [このアカウント設定を保存する] を参照してください。

最初に、新しいメソッドを2つ追加します (Userインスタンスは不要なのでUserクラスに追加します)。これにより、新しいユーザートークンを生成してSHA1暗号化アルゴリズムで暗号化します (リスト12.9)。

リスト12.9 before_createコールバックを使用してremember_token属性を作成する。
app/models/user.rb
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  before_create :create_remember_token
  .
  .
  .
  def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end
  .
  .
  .
  private

    def create_remember_token
      self.remember_token = User.encrypt(User.new_remember_token)
    end
end

次に、sign_inメソッドとcurrent_userメソッドを更新して、cookies内の新しいトークンを使用するようにし、続いてトークンを暗号化してデータベースに保存します (リスト12.10)。(追記: ソルト (暗号を強化するために元の平文に追加する短い文字列のこと) を追加していないSHA1ハッシュは一般にはセキュアではないと言われることがありますが、これは元の平文がパスワードのような短くかつランダムではない文字列の場合です。ここで暗号化する平文は十分長くかつランダムなので、生成されたハッシュは本質的にクラック不可能です。)

リスト12.10 セッションヘルパーを更新して記憶トークンを暗号化するようにする。
app/helpers/sessions_helper.rb
module SessionsHelper

  def sign_in(user)
    remember_token = User.new_remember_token
    cookies.permanent[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
    self.current_user = user
  end
  .
  .
  .
  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end
  .
  .
  .
end

少々残念なことに、記憶トークンをセキュアにするための変更を行ったことにより、従来のテストで使用していたサインインの取り扱い方法が使えなくなってしまいました。これを解決するには、テストユーティリティのsign_inヘルパーを変更し、Capybaraを使用せずにオプションを渡すようにします (リスト12.11)。詳細については4.0用チュートリアルの [このアカウント設定を保存する] を参照してください。

リスト12.11 暗号化されたトークンを扱えるように更新されたsign_inヘルパー。
spec/support/utilities.rb
.
.
.
def sign_in(user, options={})
  if options[:no_capybara]
    # Capybaraを使用していない場合にもサインインする。
    remember_token = User.new_remember_token
    cookies[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
  else
    visit signin_path
    fill_in "Email",    with: user.email
    fill_in "Password", with: user.password
    click_button "Sign in"
  end
end

no_capybaraオプションが必要な部分は3箇所のみです (リスト12.12の1箇所とリスト12.13の2箇所)。ここで、リスト12.13ではpatchメソッドが導入されたことに注目してください。これはHTTPのPATCH動詞に対応します。現時点ではPUTからPATCHへの変更は必須ではありませんが、Rails 4.0ではモデルオブジェクトの更新にはPATCHを使用することが推奨されており、将来のバージョンでは必須になる予定です。詳細については、4.0用のチュートリアル、特に「コラム 3.3 GETやその他のHTTPメソッドについて」を参照してください。

リスト12.12 Relationshipコントローラspecにno_capybaraオプションを追加する。
spec/controllers/relationships_controller_spec.rb
require 'spec_helper'

describe RelationshipsController do

  let(:user) { FactoryGirl.create(:user) }
  let(:other_user) { FactoryGirl.create(:user) }

  before { sign_in user, no_capybara: true }

  .
  .
  .
end
リスト12.13 認証ページspecにno_capybaraを追加する
spec/requests/authentication_pages_spec.rb
require 'spec_helper'

describe "Authentication" do
  .
  .
  .
  describe "authorization" do
    .
    .
    .
    describe "as wrong user" do
      let(:user) { FactoryGirl.create(:user) }
      let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
      before { sign_in user, no_capybara: true }

      describe "visiting Users#edit page" do
        before { visit edit_user_path(wrong_user) }
        it { should have_title(full_title('')) }
      end

      describe "submitting a PATCH request to the Users#update action" do
        before { patch user_path(wrong_user) }
        specify { response.should redirect_to(root_url) }
      end
    end

    describe "as non-admin user" do
      let(:user) { FactoryGirl.create(:user) }
      let(:non_admin) { FactoryGirl.create(:user) }

      before { sign_in non_admin, no_capybara: true }

      describe "submitting a DELETE request to the Users#destroy action" do
        before { delete user_path(user) }
        specify { response.should redirect_to(root_url) }
      end
    end
  end
end

以上で、必要な変更は完了です。テストスイートは緑色 (成功) になるはずです。3.2から4.0へのアップグレードについては、追加のスクリーンキャストもご覧ください。Strong Parametersやセキュリティアップデートの実装を手順を追って動画で解説しています。アップグレード中に行き詰まってしまった場合にもお勧めです。

アプリケーションのアップグレードとセキュリティアップデートの適用が完了したら、次はRails 4.0用Railsチュートリアルに進むことになるでしょう (そうするかどうかはもちろん皆様の自由です)。3.2用チュートリアルを完了させた今なら、4.0用チュートリアルは短時間で完了させることができるでしょう。4.0用チュートリアルもやっておけば、新機能について学ぶことができ、3.2用チュートリアルから十分に見直された記述に触れることもできます。4.0のすべては無理としても、3.2から4.0の変更点について言及しているコラム1.1は読んでおくことをお勧めします。

前の章
第12章 Rails 4.0 へのアップグレード
次の章