(2015年までの)odaillyjp blog

イベント参加記録とプログラミング系の雑記

Railsのセッション管理方法について

セッション管理はWebアプリケーションを開発・運用するときに必ず関わってきますので、ある程度知っておかなければいけないことかと思うのですが、Railsのセッション管理について解説している資料が少ないように思えました。私もRailsのセッション管理についてあまり知識がありませんでしたので、簡単にですがまとめてみました。

なお、今回のエントリはセッション管理の大雑把な仕組みについて知っていることを前提にして進めていきます。セッション管理の仕組みについてご存知ない方は、『体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践』という書籍での説明がわかりやすかったので、そちらをご一読されると良いかと思います。

Railsのセッション管理方法

Railsでは、config/initializer/session_store.rbファイルにセッションの管理方法を指定します。デフォルトの設定ではCookieStoreというセッション管理を利用します。今回は「CookieStore」「ActiveRecordSessionStore」「DalliStore」の3つのセッション管理についてまとめていきます。

CookieStore

デフォルトの設定では、セッション管理に「CookieStore」という仕組みを使います。(正確には「EncryptedCookieStore」と呼びますが、ここでは「CookieStore」と呼ぶことにします。)CookieStoreは「セッションの中身を全てCookieに保存する」という仕組みを持つセッション管理です。試しに、セッションに値を格納する処理を行うアクションを用意して、このアクションにリクエストを送ってみます。

./app/controller/comments_controller.rb

class CommentsController < ApplicationController
  def index
    session['foo'] = 'foo'
  end
end

console

$ telnet localhost 3000
GET /comments HTTP/1.1
Host: localhost

/* レスポンスの一部 */
HTTP/1.1 200 OK
Set-Cookie: _application_session=aC9zaGlvSVppaXArSDV3SXI4N2tTbFFYZ3VHMVJMZU1PY2loTlBHd3ZiUnNYZnFlRDZsVE9UVWVvcEFBK2xXWTFseW91NTRzUlNWbjgzVnh1QkpucUR3N1IvaXNhZEM4VnpxbHFsQVlRQ2VMMVRyNy9OUHFIK3hIRDlrVlcrbHpENEdPVXREYWJKeE9lUmtLdHpQSEZrVnJJaU5PWENhRHF2R3JzcTFQQ0hGU2pVT1VmZUhydWpHb08xcXljQ2J0LS11SUpKaDJwd2ZFRjA2cnR0YkdHQkJ3PT0%3D--9016a8927b5818f7b06b9144bb4c5e80c91d7320; path=/; HttpOnly

レスポンスとして_application_sessionというキーを持つCookieが返ってきました。この_application_sessionというCookieの値の中に、セッションの内容が含まれているようです。

Cookieの値からセッションの内容を確認してみたいところですが、Rails4.0以降では内容が暗号化されていますので、Cookieの値からセッションの内容を簡単に確認することはできないようです。ただし、暗号化に使われた秘密鍵の値を知っているならば、複合化して、セッションの内容を確認することができるそうです。また、Rails3.2までは平文のままCookieに保存されていましたので、比較的簡単にセッションの内容を確認できていたそうです。

CookieStoreを利用するメリットとしてはRailsに付属しているライブラリだけで動かすことができること」「データベースなどへのアクセスが不要なので、処理が比較的早いこと」が上げられます。ただし、クライアントがセッションの内容を持つことになりますので、サーバーが好きなタイミングでセッションの内容を確実に消すことができないという問題があります。「悪意あるユーザーにCookieを盗まれてしまったときに、好きなタイミングでセッションの内容を消すことができないと、アカウントを長期間乗っ取られてしまう可能性があるのではないか」という話題があるようです。また、ユーザーはセッションの内容をメモしておけば、セッションの内容を好きなタイミングでメモしたときの状態まで戻すことができます。この仕組みを利用したサーバーの攻撃方法として「セッション再生攻撃」というものがあります。詳しいことは『Ruby on Rails Security Guide』で解説されていますので、是非ご一読ください。

CookieStoreはRailsデフォルトのセッション管理ですので、初心者はついつい利用してしまいがちです。しかし、クライアント側にセッション内容を保存するという仕様上、様々な問題を抱えていますので、利用しないほうがいいかと思います。

CookieStoreのまとめ

  • Railsデフォルトのセッション管理
  • Cookieにセッションの内容を保存する
  • セッションの内容は暗号化されているので、一般ユーザーが内容を簡単に確認することはできない(Rails4.0以降)
  • Rails標準の構成ですぐに使えること」と「処理が比較的早いこと」がメリット
  • クライアント側にセッション内容を保存するという仕様上、様々な問題を抱えている
  • Railsデフォルトだけど非推奨

ActiveRecordSessionStore

「ActiveRecordSessionStore」ActiveRecordを利用してDBにセッションの内容を保存するセッション管理です。利用するには、activerecord-session_store」というGemが必要です。

https://github.com/rails/activerecord-session_store

Readmeの内容に沿って導入すると、下記の構成のテーブルが作られます。

# == Schema Information
#
# Table name: sessions
#
#  id             :integer          not null, primary key
#  session_id     :string(255)    not null, 
#  data           :text
#  created_at     :datetime
#  updated_at     :datetime
#


Railsサーバを起動して、リクエストを送ってみます。

$ telnet localhost 3000
GET /comments HTTP/1.1
Host: localhost

/* レスポンスの一部 */
HTTP/1.1 200 OK
Set-Cookie: Set-Cookie: _session_id=b36a281518592c8811509f34f515a7f0; path=/; HttpOnly

レスポンスとして_session_idというキーを持つCookieが返ってきました。続けて、データベースのsessionsテーブルの中身を見てみます。

$ sqlite3 db/development.sqlite3
sqlite> .mode line
sqlite> SELECT session_id, data FROM sessions;
session_id = b36a281518592c8811509f34f515a7f0
      data = BAh7B0kiCGZvbwY6BkVGSSIIZm9vBjsAVEkiEF9jc3JmX3Rva2VuBjsARkki
MVhKblEvbkxrdXlKeHBwWTZSYU96UXVjQjY1UVBMMDJSMkJOeVR1cmdmSUU9
BjsARg==

レスポンスに含まれていた_session_idと同じ値を持つレコードがsessionsテーブルに追加されています。このレコードのdataカラムの中に、セッションの内容が保存されているようです。とてもシンプルなセッション管理ですね。

ActiveRecordSessionStoreはGemを1つ追加するだけですぐに利用できますので、比較的簡単に導入できます。また、サーバーがセッションの内容は持つことになるので、CookieStoreが抱えていた様々な問題を解決できます。処理速度が気になるところですが、簡単に扱えるセッション管理をお探しでしたら、ActiveRecordSessionStoreという選択もあるのではないでしょうか。

ActiveRecordSessionStoreのまとめ

  • ActiveRecordを利用してDBにセッション内容を保存するセッション管理
  • activerecord-session_store」というGemが必要
  • 導入が比較的簡単
  • サーバー側にセッション内容を保存するので、CookieStoreが抱えていた様々な問題を解決できる

DalliStore

「DalliStore」memcachedにセッションの内容を保存するセッション管理です。「Dalli」というRubyのmemchacedクライアントGemに付属しています。RubyのmemchacedクライアントはDalli以外にもいくつか存在するのですが、HerokuのチュートリアルにはDalliを使った導入例が書いてありましたので、このエントリでもDalliを使うことにします。

mperham/dalli · GitHub

導入方法についてはReadmeをご一読ください。導入できましたら、memcachedを起動して、Railsサーバーにアクセスしてみます。

# memcachedをデーモンモードで起動
$ memcached -d
$ telnet localhost 3000
GET /comments HTTP/1.1
Host: localhost

/* レスポンスの一部 */
HTTP/1.1 200 OK
Set-Cookie: _session_id=6599177fbe4c96725e90a3bf0ce6a4c7; path=/; expires=Sun, 02 Nov 2014 06:00:44 -0000; HttpOnly

ActiveRecordSessionStoreと同じように、_session_idというキーを持つCookieが返ってきました。(Set-Cookieにexpiresオプションが付いていますが、これはDalliのReadmeに記載されている導入方法に合わせて、期限付きのセッションIDを返すように設定を書いたために付きました。)続けて、memcachedの中身を見てみます。memcachedはデフォルトでは11211ポートで起動しますので、11211ポートに接続すれば、memcachedと対話することができます。

$ telnet localhost 11211

memcachedに接続できましたら、下記のコマンドでセッションIDに対応するセッション内容を確認できます。

get _session_id:6599177fbe4c96725e90a3bf0ce6a4c7

「get _session_id:」の後の文字列にはセッションIDを入力します。このコマンドを実行すると、下記のような結果が返ってきます。

VALUE _session_id:6599177fbe4c96725e90a3bf0ce6a4c7 1 94
{Ifoo:EFIfoo;TI"_csrf_token;FI"1v7q3nI5BJznOdoll00iroIlAkPhvnhS7mwIotU2bOlQ=;F
END

memcachedのドキュメントによると、2行目の部分が値を表しているそうです。今回はセッションのfooというキーに"foo"という文字列を保存してみましたが、それらしき値が見えていますね。確認できましたら、quitコマンドで接続を切りましょう。

DalliStoreはActiveRecordStoreSessionと比べて導入が少し複雑です。また、memcachedの扱いに慣れていない方は、問題が起きたときの対応に苦労すると思います。しかし、処理速度が早いですし、memcached自体が様々な機能を持っていますので、使いこなすことができれば便利なのかもしれません。

DalliStoreのまとめ

  • memcachedにセッション内容を保存するセッション管理
  • memcachedとDalliが必要
  • サーバー側にセッション内容を保存するので、CookieStoreが抱えていた様々な問題を解決できる
  • ActiveRecordSessionStoreと比べて、導入が大変だが、処理速度が早い
  • 初心者が扱うのは難しいが、使いこなせれば便利なのかも?

「Forwardableモジュール」と「委譲」について学びました

ある程度の規模のWebサービスを作ろうとすると、Rubyの組み込みライブラリやRailsMVC構造だけで設計することに限界を感じてきてしまいます。そんなときに、様々なデザインパターンや便利なモジュールを知っていれば、最適な設計を選択することができるかもしれません。例えば、過去の記事で紹介したObservableモジュールを使えば、見通しが良いコードが書ける場面があるかもしれません。その他にも、便利なモジュールはたくさんあると思いますので、少しずつ調べてブログに残していきたいと考えています。

今回は、わりと忘れがちなRuby標準添付モジュールである「Forwardable」と、それに関わる「委譲」という仕組みについて調べてみました。

Forwardableモジュール

Forwardableモジュールは、クラスに対してメソッドの委譲機能を追加するモジュールです。「委譲」とは何でしょうか。

Forwardableモジュール
http://docs.ruby-lang.org/ja/2.1.0/class/Forwardable.html

委譲(Delegate

オブジェクト指向プログラミングにおいて、再利用したい機能を自身のオブジェクトに取り込むのではなく、その機能を持つオブジェクトに処理を依頼することを「委譲」と呼びます。処理を依頼するオブジェクトを「委譲元オブジェクト」、依頼を受けて処理を行うオブジェクトを「委譲先オブジェクト」と呼びます。あるオブジェクトに定義されているメソッドやプロパティを他のオブジェクトでも利用したい場合、「委譲」以外に「継承」や「ミックスイン」を使うという方法もあります。「継承」や「ミックスイン」を使うと、継承先(もしくはミックスイン先)のオブジェクトに定義されている全ての機能が利用できようになりますが、「委譲」は一部の機能だけが利用できるようになります。「余計な機能は取り込みたくない」というときに委譲を使うと良いのではないでしょうか。また、委譲元オブジェクトと委譲先オブジェクトに親子関係がうまれないところも委譲のポイントだと思います。

デモンストレーション

デモンストレーションとして、スタック機能を持つクラスを実装してみましょう。まずはForwardableモジュールを使わずに実装してみます。

class Stack < Array; end

はい、出来上がりました。スタックに必要な機能は既にArrayクラスが持っていますので、Arrayクラスを継承したクラスを作るだけで実装は終わりです。スタックとして使えるかどうか試してみましょう。

stack = Stack.new
stack.push 1, 2, 3
puts stack.pop
# => 3

意図した通りに動いていますね。

しかし、この実装には大きな問題があります。それは「#shiftなどの余計なメソッドが使えてしまうこと」です。Arrayクラスを継承してしまったのですから当然ですね。何とかしないといけません。Rubyには定義済みのメソッドをクラスから取り除くことができるが.undefというメソッドがありますので、これを使って#shiftメソッドを取り除いてみましょう。

class Stack < Array
  undef :shift
end

stack = Stack.new
stack.push 1, 2, 3
stack.shift
# => NoMethodError

#shiftメソッドを取り除くことに成功しました。他にも#unshift、#[]などもスタックには不要ですので、これらも取り除きたいですね。
しかし、いちいち.undefを使ってメソッドを取り除かないといけないのは面倒です。もう少し楽にスタックを実装できないのでしょうか。

そこで登場するのがForwardableモジュールです。Forwardableモジュールを使うと、必要なメソッドだけを持つスタックを簡単に実装できます。

require 'forwardable'

class Stack
  extend Forwardable

  def initialize
    @ary = []
  end

  def_delegators :@ary, :push, :pop, :clear, :size
end

Forwardableモジュールの使い方は『Ruby Reference Manual』見てください。

Forwardableモジュール
http://docs.ruby-lang.org/ja/2.1.0/class/Forwardable.html

では、先ほど作ったStackクラスを使ってみます。

stack = Stack.new
stack.push 1, 2, 3
stack.shift
# => NoMethodError

結果を見ると、必要なメソッドだけを持つスタックを実装できていることがわかりますね。

委譲の利用しどころを考えた

デモンストレーションでは「使えるメソッドに制限をかける」という目的で委譲を利用したように見えますが、委譲を利用する本来の目的は「他のオブジェクトに処理を任せること」だそうです。「このクラスではこんな処理を担当するべき」「あのクラスではこんな処理を担当するべき」といった感じに、オブジェクトの責務を考えて設計したいときに委譲を使うと効果的だと思いました。
オブジェクトの責務を考える為に、MVCフレームにおいてControllerクラスからViewクラスにレンダリングの指示を送る処理を委譲を使って書いてみました。

require 'forwardable'

class View
  def render(context)
    # レンダリング処理
  end
end

class BaseController
  extend Forwardable

  def initialize
    @view = View.new
  end

  def_delegator :@view, :render
end

class AppController < BaseController
  def index
    render :index
  end
end

Railsのrenderメソッドを意識して書いてみましたが、いかがでしょうか。

Railsで委譲

Railsで委譲を利用する場合は、ActiveSupportの.delegateメソッドを使う方法もあるようです。
.delegateメソッドはForwardableモジュールの.def_delegatorメソッドと似たような処理を行います。

class Foo < ActiveRecord::Base
  def method1
    'method1'
  end
end

class Bar < ActiveRecord::Base
  belongs_to :foo
  delegate :method1, to: :foo
end

bar = Bar.new
bar.foo = Foo.new
puts bar.method1
# => 'method1'

詳しくはRailsのドキュメントを見てください。

RailsAPIsドキュメントの#delegateメソッド
http://api.rubyonrails.org/classes/Module.html#method-i-delegate

以上、簡単にですが調べたことのまとめでした。

RailsのCSRF対策の仕組みについて

先日、Rails で開発しているときに意図しない InvalidAuthenticityToken エラーが発生して、すごくハマってしまいました。そのときに RailsCSRF対策の仕組みについて調べてみましたので、ブログに残しておきます。

RailsCSRF対策

Rails が生成した ApplicationController には以下の記述があります。

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

protect_from_forgery メソッドがコントローラーに記述されていると、CSRF対策が有効になるようです。protect_from_forgery メソッドについて調べていきます。

protect_from_forgery メソッド

まずは protect_from_forgery メソッドソースコードを見てみます。

rails/request_forgery_protection.rb at 79d50ce3104d2ff4a3964b12139120b85dce35e7 · rails/rails · GitHub

def protect_from_forgery(options = {})
  self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
  self.request_forgery_protection_token ||= :authenticity_token
  prepend_before_action :verify_authenticity_token, options
  append_after_action :verify_same_origin_request
end

protect_from_forgery メソッドを呼び出すと、以下の2つのメソッドがフィルターとしてコントローラーに定義されます。

ちなみに、prepend_before_action メソッドや prepend_after_action メソッドで定義したフィルターは他のフィルターよりも先に実行されます。 CSRF対策は verify_authenticity_token メソッドで行っていますので、今回は verify_authenticity_token メソッドの処理だけを見ていきます。

verify_authenticity_token メソッド

大雑把に説明すると、verify_authenticity_token メソッドはHTTPリクエストのヘッダーやボディに入っているCSRF対策用トークンの値を検証するメソッドです。検証した結果、不正だと判断した場合に InvalidAuthenticityToken エラーを発生します。(このエラーは protect_from_forgery メソッドのwithオプションに :exception を渡したときに発生します。他の値を渡したときの処理については調べていません。)

もう少し詳しく知りたいので、verify_authenticity_token メソッドソースコードを見てみます。

def verify_authenticity_token
  mark_for_same_origin_verification!

  if !verified_request?
    if logger && log_warning_on_csrf_failure
      logger.warn "Can't verify CSRF token authenticity"
    end
    handle_unverified_request
  end
end

どうやら verified_request? というメソッドがあり、このメソッドの返り値が false だったときに不正だと判断するようです。verified_request? メソッドソースコードも見てみます。

def verified_request?
  !protect_against_forgery? || request.get? || request.head? ||
    valid_authenticity_token?(session, form_authenticity_param) ||
    valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
end

このメソッドでは以下の5つの項目を検証していきます。

  • 設定ファイルで config.application_controller.allow_forgery_protection を false に設定しているか
  • リクエストのHTTPメソッドがGETであるか
  • リクエストのHTTPメソッドがHEADであるか
  • セッション変数 _csrf_token の値とリクエストボディの authenticity_token の値を比較した結果、正しいと判断されるか
  • セッション変数 _csrf_token の値とリクエストヘッダーの X-CSRF-Token の値を比較した結果、正しいと判断されるか

検証した結果、1つでも当てはまる項目があったときは true を返し、当てはまる項目がなかったときは false を返すようです。本番環境では config.application_controller.allow_forgery_protection を true に設定して、データを更新するときはPOSTやPUTメソッドでリクエストを送るのが一般的ですので、最初の3つの項目に当てはまることはなさそうです。そうなると、検証を通すには「authenticity_token をフォームに埋め込んでおく」「X-CSRF-Token をリクエストヘッダーに設定しておく」のどちらかの対応を行わなければいけない……ということになります。

authenticity_token をフォームに埋め込む

まずは「authenticity_token をフォームに埋め込んでおく」方法から見ていきます。実は form_for や form_tag のヘルパーで生成されたフォームには、最初から authenticity_token が埋め込まれています。

erb

<%= form_for @person do |f| %>
  <%= f.submit %>
<% end %>

生成されるhtml

<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post">
  <div style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
  </div>

  <input name="commit" type="submit" value="Create Person" />
</form>

ヘルパーが自動的に authenticity_token を埋め込んでくれるので、開発者がCSRF対策のために手間をかけてコードを書かなくても大丈夫なのですね。

Ajaxでフォームの内容を送信するとき

先ほど「ヘルパーが自動的に authenticity_token を埋め込んでくれる」と言いましたが、やり方によっては authenticity_token が埋め込まれないことがあります。それは form_for などのヘルパーに remote: true オプションを付けたときです。

erb

<%= form_for @person, remote: true do |f| %>
  <%= f.submit %>
<% end %>

生成されるhtml

<form accept-charset="UTF-8" action="/people" class="new_person" data-remote="true" id="new_person" method="post">
  <div style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
  </div>

  <input name="commit" type="submit" value="Create Person" />
</form>

form_for ヘルパーに remote: ture オプションを付けると、フォームタグに data-remote 属性が設定されます。Rails 付属の jQueryjquery-rails)では、data-remote 属性が設定されているフォームの submit イベントを実行するとAjax送信を行うようになっています。(ソースコードからそのまま引用すると記事が長くなってしまうので要約しますが、下記のような感じで書かれています。興味がある方は jquery-rails のソースコードを見てください。)

$document.delegate('form', 'submit.rails', function(e) {
  var remote = $(this).data('remote') !== undefined;

  if (remote) {
    /* Ajax送信を行う */
  }
});

Ajax送信でデータの更新を行いたいときは form_for ヘルパーに remote: true オプションを付けるのですが、これだとフォームに authenticity_token が埋め込まれません。authenticity_token がないということはCSRF対策の検証に失敗してしまう……ということでしょうか。しかし、実際にこのリクエストを送っても検証を通ってくれます。何故でしょうか。リクエストヘッダーを見てみるとわかるのですが、いつの間にかに X-CSRF-Token がリクエストヘッダーに設定されています。

リクエストヘッダー

X-CSRF-Token: NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=

authenticity_token がフォームに埋め込まれていなくても X-CSRF-Token がリクエストヘッダーに設定されていれば検証には通りますので、先ほどのリクエストは問題なく検証を通ったのですね。

Ajax送信で X-CSRF-Token を設定する

Ajax送信を使ったとき、いつの間にか X-CSRF-Token がリクエストヘッダーに設定されていました。どこで設定しているのか気になりますね。jquery-rails のソースコードを見てみます。

// Make sure that every Ajax request sends the CSRF token
CSRFProtection: function(xhr) {
  var token = $('meta[name="csrf-token"]').attr('content');
  if (token) xhr.setRequestHeader('X-CSRF-Token', token);
},

// 省略

$.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

どうやら、jQuery の ajaxPrefilter メソッドAjax送信する直前に実行されるメソッド)で X-CSRF-Token を設定しているようです。

Ajax送信するときは、jquery-rails が X-CSRF-Token を自動的に設定してくれるので、開発者がCSRF対策のために手間をかけてコードを書かなくても大丈夫なのですね。

まとめ

  • Rails では以下の5つの項目の内、1つでも当てはまる項目があればCSRF対策の検証に通る
    • 設定ファイルで config.application_controller.allow_forgery_protection を false に設定している
    • リクエストのHTTPメソッドがGETである
    • リクエストのHTTPメソッドがHEADである
    • セッション変数 _csrf_token の値とリクエストボディの authenticity_token の値を比較した結果、正しいと判断される
    • セッション変数 _csrf_token の値とリクエストヘッダーの X-CSRF-Token の値を比較した結果、正しいと判断される
  • form_for などのヘルパーを使うと、authenticity_token がフォームに埋め込まれる
  • ただし、remote: true オプションを付けた場合は、authenticity_token は埋め込まれない
  • Ajax送信するときは、jquery-rails がリクエストヘッダーに X-CSRF-Token を設定してくれる
  • 開発者はCSRF対策のために特別なコードを書かなくても大丈夫なようになっている


【おまけ】Ajax送信で X-CSRF-Token が設定されない不具合

jquery-railsAjax送信しているのに、何故か X-CSRF-Token が設定されない!」という不具合が発生した場合、remote: true オプションを付けながら authenticity_token もフォームに埋め込んで対処する方法があります。以下のようにヘルパーに authenticity_token: true オプションを付けておくと、remote: true を付けていても authenticity_token がフォームに埋め込まれます。

<%= form_for @person, remote: true, authenticity_token: true do |f| %>
  <%= f.label :name %>:
  <%= f.text_field :name %>

  <%= f.submit %>
<% end %>

そもそも、何故こんな不具合が発生したのか原因はわかっていないのですが……

Chef の思想や基本を優しく学びました

知っていることはブログに書いておかないと不安になる病気にかかってしまいましたので、Chef についてのメモを残しておきます。

はじめに

アプリケーションサーバMySQL とか Nginx とか様々なパッケージをインストールしているかと思います。皆さんはこれらのパッケージをどうやってインストールしていますか。

「パーケッジ管理ツールを使って手動でインストールしているよ!」

お疲れ様です><

「必要なパッケージをまとめてインストールするシェルスクリプトを作ったよ!」

シェルスクリプトを書くのは楽しいですね!

「構成管理ツールを使っているよ!」

わぉ!ツールの思想や使い方を私に教えてください!

アプリケーションサーバを構築したことがないよ!」

一緒に勉強しましょう。

……まあ、そんな感じで様々な方がいらっしゃると思うのですが、一番最初にあげた「手動でインストールしていく方法」には以下のような問題がありますのであまり行いたくないです。

  • サーバが複数台あるときに、作業に時間がかかる
  • 人手による作業なので、ケアレスミスが起こりやすい
  • 手順書などの資料を管理していかなければいけない

そこで、今回は構築管理ツール「Chef」を使って、アプリケーションサーバに様々なパッケージをインストールして、サーバの設定を行ってみます。

構成管理ツールとは

パッケージのインストールやサービスの起動設定をコードで記述しておいて、後で簡単に実行できる環境を提供するツール「構成管理ツール(Configuration management tool)」と呼びます。構成管理ツールを使うことには以下のようなメリットがあります。

  • サーバが複数代あっても、記述したコードを全てのサーバに適用するだけなので、作業に時間があまりかからない
  • プログラムによる作業なので、ケアレスミスが起こらない
  • 設定を記述したコードが手順書となるので、余計な手順書を作らなくて済む
  • サーバ環境を構築することを目的として作られているツールなので、シェルスクリプトなどを使ったときよりもコードをメンテナンスしやすい

構成管理ツールには「Chef」や「Ansible」などがあります。

Chef とは

「Chef」は RubyErlang で作られた構成管理ツールです。

Chef | IT automation for speed and awesomeness | Chef

自由に使えるオープンソース版と、様々なサポートが付いたエンタープライズ版の2種類があります。

Chef の用語

レシピ

「〜がインストールされている」「〜と書かれた設定ファイルが存在する」という最小単位の状態をコードで記述したファイルのことを「レシピ(Recipe)」と呼びます。

クックブック

レシピを組み合わせて、「〜がインストールされて、それを使うための〜という設定が整っている」という状態を定義しておくファイルを「クックブック(Cookbook)」と呼びます。1つのクックブックにつき、一つのパッケージについて書いておくのが一般的なやり方だそうです。クックブックを用意したいときは、自分で書いていくほかに「Opscode Community」からダウンロードする方法もあります。

Opscode Community

リポジトリ(キッチン)

クックブックやそのほかで Chef の実行に必要なファイルを集めたものを「リポジトリ(Repository)」、もしくは 「キッチン(Kitchen)」と呼びます。git を使うときはリポジトリ単位で管理していくのが一般的なやり方だそうですね。

Chef のサーバ構成

Chef には、「Chef Server/Client」というツールを使って「クライアント・サーバ形式」という方法でサーバの状態を管理する方法と、「Chef Solo」というツールを使ってスタンドアロン形式」という方法でサーバの状態を管理する方法の2通りがあります。

クライアント・サーバ形式(Chef server/client)

f:id:Shindo_Masaya:20140705141902j:plain:w600

レシピやクックブックを編集する場所であるワークステーション(Workstation)」ワークステーションで作ったレシピやクックブックを管理する場所である「Chef サーバ(Chef server)」、Chef サーバからレシピやクックブックを取得してサーバの状態を構築する場所である「ノード(Node)」の3つの構成でサーバを管理する方法を「クライアント・サーバ形式」と呼びます。ノードには Chef client ツールだけをインストールすればいいので、ノードの数が多くても楽に対応できますが、開発端末や管理対象サーバ以外に Chef サーバとして動かす端末が1台必要になってしまうという難点があるようです。

スタンドアロン形式(Chef solo)

f:id:Shindo_Masaya:20140713200523j:plain:w400

ワークステーションから各ノードにクックブックを直接送り、各ノードで Chef solo ツールを使ってクックブックを適用するという方法でサーバを管理する方法をスタンドアロン形式」と呼びます。クライアント・サーバ形式とは違い Chef サーバを用意する必要はありませんので、小規模な環境で有効なようです。

Chef の思想について

サーバの状態を管理する

Chef では以下のようなコードを記述してサーバを構築していきます。

package 'httpd' do
  action :install
end

Chef のことがわからなくても、「httpd というパッケージをインストールする」となんとなく読めますが、Chef の思想では「httpd というパッケージがインストールされている状態である」と読むのが正しいそうです。もともと Chef はサーバ構築作業を行うためのツールではなく、サーバの状態を管理するためのツールとして開発されたそうです。この違いを意識して Chef を使えると、「サーバの状態を知りたくなったときは Chef に記述したコードを見ればいい」と判断できるようになります。

冪等であるべき

Wikipediaから「冪等」の概念を引用します。 冪等 - Wikipedia

ある操作を1回行っても複数回行っても結果が同じであることをいう概念である。

ここに書かれているように、Chef で環境構築の処理を複数回行ってもサーバの状態は常に同じにならなければいけません。複数回実行すると結果が変わってしまうようなコードを書いていると、誤って複数回実行したときにトラブルの元になりますし、コードを見てもサーバの状態を知ることができなくなります。

サーバの状態を「収束」させる

コードには「サーバのあるべき状態」が記述されていますので、各サーバに微妙な違いがあったとしても、Chef を実行したときにサーバはコードに書いてある通りの状態に収まらないといけません。Chef では「実行するたびに、サーバが本来あるべき状態に収まる」ことを「収束」と表現しています。

サーバの状態は全て Chef に管理させる

Chef はサーバの状態を管理するためのツールなので、Chef を使うと決めたらパッケージの管理や設定は全て Chef にまかせてしまった方がいいです。手動でインストールしたパッケージがあったりしたら、コードだけを見てもサーバの状態を知ることができなくなります。

Chef のインストール

普段から Ruby を使っている人であれば、Rubygems からダウンロードしてインストールするのが楽だと思います。

$ gem install chef

公式ドキュメントや『Chef実践入門』や多くのブログ記事ではこの方法が紹介されています。

それ以外だと、公式サイトに「Chef Development Kit」という便利なツールキットが用意されていますので、こちらをインストールする方法もあります。

Chef Development Kit

公式チュートリアルは「Chef Development Kit」を使うことを想定していますので、公式チュートリアルに沿って勉強したい方は「Chef Development Kit」を使った方がいいのかもしれません。

chef-apply コマンドを使って Hello, world する

カレントディレクトリに「Hello, world!」と書かれたファイルを出力するレシピを作り、chef-apply というレシピを単体実行するコマンドを使って実行する演習を行って、レシピの書き方と Chef の動作のイメージを掴みましょう。

まずは以下のようなコードを記述します。

# ./hello_recipe.rb
file 'hello_world.txt' do
  content 'Hello, world!'
end

このコードが今回のレシピになります。大雑把に説明すると、「カレントディレクトリにある hello_world.txt ファイルには『Hello, World!』という内容が書かれていること」という定義を書きました。 早速 chef-apply コマンドを使ってレシピを実行してみます。

$ chef-apply hello_recipe.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[hello_world.txt] action create
    - create new file hello_world.txt
    - update content in file hello_world.txt from none to dffd60
    --- hello_world.txt 2014-07-05 15:01:15.000000000 +0900
    +++ /var/folders/40/rs6fnn7n7k190r2p1bsfz81w0000gn/T/.hello_world.txt20140705-2513-1ralz4i 2014-07-05 15:01:15.000000000 +0900
    @@ -1 +1,2 @@
    +Hello, World!

うまくできたのでしょうか?生成されたファイルの内容を見てみます。

$ ls
hello_recipe.rb
hello_world.txt

$ cat hello_world.txt
Hello, world!

大成功でした!ついでに、冪等であることを確認するために、もう一度 chef-apply コマンドを実行してみます。

$ chef-apply hello_recipe.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[hello_world.txt] action create (up to date)

もう一度ファイルの内容を見てみます。

$ cat hello_world.txt
Hello, world!

内容が変わっていませんね。複数回実行しても内容が変わりませんでしたので、「冪等である」と言えますね。今度は少し意地悪をして、hello_world.txt の内容を手動で書き換えてみます。

$ vi hello_world.txt
- Hello, word!
+ Have fun.

この状態で chef-apply コマンドを実行するとどうなるのでしょうか?試してみます。

$ chef-apply hello_recipe.rb

================================================================================
    Error executing action `create` on resource 'file[hello_world.txt]'
    ================================================================================

    Errno::EACCES
    -------------
    Permission denied @ dir_s_mkdir - /var/chef

# 以下略

エラーが出てしまいました。/var/chef ディレクトリ作成時の権限エラーなようですので、sudo を付けて実行します。

$ sudo chef-apply hello_recipe.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[hello_world.txt] action create
    - update content in file hello_world.txt from 20a627 to dffd60
    --- hello_world.txt 2014-07-05 15:14:13.000000000 +0900
    +++ /tmp/.hello_world.txt20140705-2998-109c1on      2014-07-05 15:17:27.000000000 +0900
    @@ -1,2 +1,2 @@
    -Have fun.
    +Hello, World!

今度はうまく実行できました。ファイルの内容を見てみます。

$ cat hello_world.txt
Hello, world!

先ほど編集した「Have fun.」という文字が消えて、「Hello, world!」が書かれている状態になっています。これが「Chef を実行すると、サーバごとに個別に行った編集は消えて、あるべき状態に『収束』する」ということですね。 ちなみに、元々のファイル内容は /var/chef/backup ディレクトリにバックアップされるようです。(先ほど出たエラーは、バックアップの書き込みに失敗したというエラーだったようです。)

$ cat /var/chef/backup/hello_world.txt.chef-20140705151727.409540
Have fun.

少し使ってみての感想

「クライアント・サーバ形式」と「スタンドアロン形式」の違いを理解するのはそこまで難しいことではない

「『Chef Server/Client』と『Chef Solo』の違いがわかりにくい!」 という話しをよく聞きますが、公式サイトで優しく説明されていましたので、違いがわかりにくい感じはしませんでした。Syslog などクライアント・サーバ形式のツールを使ったことがある方ならば、公式サイトトップの「How chef works」項目にある図を見て勉強すれば、そんなに時間はかからずに違いがわかるようになるのではないでしょうか。

「Chef Server/Client」は中規模以上のサーバ構成向けのツール

アプリケーションサーバが3台あって、DBサーバが1台あって、ステージング用のサーバが1台あって……といった中規模以上のサーバ構成にならない限りは Chef Server は使う必要はなさそうに思えました。残念ですが、当分の間は私には触る機会がないかもしれないです。

「Chef Developer Kit」を使うと導入が楽になる

「Chef Developer Kit」には Ruby や Chef や Berkshelf など、Chef を使う上で必要なものが一通り入っています。Ruby のインストールができていない場合は「Chef Developer Kit」を使ったほうが導入が楽になると思います。私の環境では Rubygems でインストールした Ohai を実行したときに端末がフリーズする不具合が発生してしまいましたので、私は「Chef Developer Kit」を使うことにしました。

用語が覚えにくい

シェフ、キッチン、クックブック、レシピ、ナイフ……一聞では何のことかわからない可愛い名前が付けられていて、用語が覚えにくいです。「chef-solo」とか「chef-client」とか「knife」とか、増えるコマンドが多いのも少し辛いです。

Ruby が好きな人には良さそう

「I love Ruby!」

知っておかないといけないことはこれだけではない

今回は基本的なことだけしか書いていませんので、Chef を使う上で知っておかないといけないことはまだまだあります。公式ドキュメントとか各書籍などを読んで勉強していきましょう。

Chef活用ガイド コードではじめる構成管理

Chef活用ガイド コードではじめる構成管理

Chef実践入門 ~コードによるインフラ構成の自動化 (WEB+DB PRESS plus)

Chef実践入門 ~コードによるインフラ構成の自動化 (WEB+DB PRESS plus)

guard-rspec に送った Pull request がマージされました

rspec-rails は2014年6月にリリースされた v3.0.0 から rails_helper.rb という新しいヘルパーファイルを生成するようになりました。 純粋な RSpec の設定は spec_helper.rb に記述し、RailsRSpec を使うときに必要な設定は別のヘルパーファイル(rails_helper.rb)に記述した方が良いという考えにより変更されたようです。

(参考: Move spec_helper to rails_helper. · d3065c0 · rspec/rspec-rails · GitHub

「guard init rspec」コマンドで生成した Guardfile は rails_helper.rb を監視対象にしていませんでしたので、このファイルを監視するように Pull request を送りました。

f:id:Shindo_Masaya:20140716205325p:plain:w600

Updated Guardfile template so that Guard can watch the new rails helper file. by odaillyjp · Pull Request #275 · guard/guard-rspec · GitHub

相手に意図が伝わる英語を使えているか不安だったのですが、単純な内容の Pull request でしたので、送ってから10分足らずでマージされていました。

よちよち向け Guard 説明

ここからは PR とは関係がない話しになるのですが、よちよち.rb で「Guardって何?」という話しが出ていましたので、軽くメモを残しておきます。

Guard とは

一言で説明すると、Guard は「ファイルが更新されたときに『何か』をしてくれるツールです。 「監視対象のファイル名」と「そのファイルが更新されたときに行う処理内容」を Guardfile というファイルに記述しておくと、監視対象のファイルのタイムスタンプが更新されたときに自動的に処理を行ってくれます。 例えば、以下の内容が記述された Guardfile を用意します。

# Guardfile

watch('hello.txt') do
  puts ''
  puts '-------------'
  puts 'Hello, world!'
  puts '-------------'
end

これには、カレントディレクトリにある hello.txt が更新されたときに「Hello, world!」と出力するようにと記述されています。(ただの Ruby のコードですね。) guard を起動し、別ウィンドウで hello.txt のタイムスタンプを更新してみます。

$ guard
22:30:00 - INFO - Guard is now watching at '/Users/odailly/learning/guard'
[1] guard(main)>

# 別ウィンドウで hello.txt のタイムスタンプを更新した

-------------
Hello, world!
-------------

「Hello, world!」の出力に成功しました。 Guard は何をしてくれるツールなのか、なんとなくイメージできましたでしょうか?

今度はもう少し実用的なことを試してみます。 先ほど起動した Guard は「exit」コマンドで一度止めておきましょう。 まずは Gemfile を作成し、以下の内容を記述をします。

source 'https://rubygems.org/'

gem 'guard'
gem 'guard-bundler'

Bundler を使って gem をインストールします。

$ bundle install

Guardfile に以下の内容を追記します。

guard :bundler do
  watch('Gemfile')
end

これには、Gemfile が更新されたときに自動で bundle install を実行するようにと記述されています。 Guard を起動し、別ウィンドウで Gemfile を更新してみます。 (今回は Bundler で Guard をインストールしましたので、Bundler 経由で Guard を起動します。)

$ bundle exec guard
22:43:50 - INFO - Bundle already up-to-date
22:43:50 - INFO - Guard is now watching at '/Users/odailly/learning/guard'
[1] guard(main)>

# Gemfile に「gem 'what_methods'」を追加した

Fetching gem metadata from https://rubygems.org/...........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
# 途中略
Installing what_methods 1.0.1
Your bundle is complete!
Gems in the group production were not installed.
It was installed into ./vendor/bundle
22:44:44 - INFO - Bundle installed

「what_methods」のインストールに成功しました。 今回は Guard から Bundler を使うためのプラグイン「guard-bundler」を利用しました。 Guard には他にも様々なプラグインがありますので、いくつか紹介します。

プラグイン 説明
guard-shell Guard から shell コマンドを実行する
guard-rspec Guard から rspec を実行する

プラグインGithub の Guard organization から見つけることができます。

Guard · GitHub

私のお気に入り設定

「terminal-notifier-guard」という gem を入れて、Guard の処理結果を Mac の通知センターに飛ばすようにしています。邪魔には感じないくらいに目立ってくれるので、お気に入りです。

f:id:Shindo_Masaya:20140716211707j:plain

Springest/terminal-notifier-guard · GitHub

第23回オフラインリアルタイムどう書くにへなちょこな感じで参加しました

『オフラインリアルタイムどう書く』というプログラミング勉強会があります。

オフラインリアルタイムどう書く | 横浜へなちょこプログラミング勉強会

たまに『オフラインリアルタイムどう書く』で過去に出題された問題を家でちまちまと解いていたのですが、第23回にして初めてオフラインリアルタイム(現地)で参加しました。

問題

第23回の問題はこちらです。

くねくね増加列〜 横へな 2014.7.5 問題

どう解いた?

再帰深さ優先探索を使って解こうとしたのですが、間違った再帰の使い方をしていて、時間内に解くことができませんでした。帰宅してから30分ほど時間を使ってコードを書き直して、なんとかサンプルデータをクリアすることができました。私の解答は以下になります。

どう書く# 23 http://nabetani.sakura.ne.jp/hena/ord23sn ...

コードを大雑把に説明します。このコードは問題を解くためのメソッド(solve)、インスタンス変数を初期化するメソッド(init_numbers_field と init_memo)、深さを探索するメソッド(count_depth)で構成されています。メインとなる処理は count_depth メソッドです。count_depth メソッドは「マス目(x, y)から探索を始めたとき、一番長く辿ることができる経路のマス目の数」を返します。例えば、以下の表の一番左上のマス目から探索を始めるとします。

f:id:Shindo_Masaya:20140709234145j:plain:w400

この場合、経路は以下の2つがあります。

  • 経路1(青):「0, 1, 2, 6, 7, 9」
  • 経路2(赤):「0, 1, 2, 5, 7」

f:id:Shindo_Masaya:20140709234235j:plain:w400

経路1が6マスで一番長いので、一番左上のマス目に対して count_depth メソッドを呼び出したときは「6」を返します。この count_depth メソッドをすべてのマス目に対して呼び出してみて、最終的に一番長く辿ることができた経路のマス目の数を返すのが solve メソッドです。

細かいこと

  • クラスを作っておいて、クラスの initialize メソッドに初期化の処理を持っていっても良かったのですが、時間の都合上やっていません。
  • 時間を短縮するためにテストケースを動的に定義しています。(この勉強会によく参加されている方はサンプルデータからテストケースを簡単に作成するスクリプトなどをあらかじめ用意しているそうです。次回は真似しようと思います。)
  • 二次元配列は[x][y]の形で値を取れるようにしたほうがわかりやすいかと思い、transpose メソッドを使いました。([x][y]と[y][x]のどちらがわかりやすい?)
  • 最初は番兵なしで書いていたのですが、if の条件文がややこしくなりましたので、最終的には番兵を使いました。
  • count_depth メソッドはメモ化を実装済みです。

どう失敗した?

最初は今まで辿った経路を配列に格納するような実装をしていました。

どう書く# 23 最初のリビジョン

しかし、再帰処理の中で元の関数に戻るときに配列の中身がリセットされませんので、辿った経路情報が配列に残ってしまい、期待した通りに動いてくれませんでした。失敗でした。再帰を使うときは副作用がないように実装するのが基本ですので、考え方を間違えていました。この間違えさえなければ時間内で解けていたと思います。

感想

初心者、初参加者に優しいイベントでした。久しぶりにピュアなRubyを書くことができて楽しかったです。また参加させていただきます。

備忘録

リアルタイムで解けなかったのは再帰に慣れていなかったからだと思いますので、自戒のために再帰を使うときの注意点を書き留めておきます。

再帰を使うときの注意点

  • 再帰の終了条件を忘れずに実装する
  • 再帰が呼び出されるたびに問題が徐々に単純化されるように実装する
  • 副作用がないように実装する

パーフェクト Rails を読みました(7章)

この記事は『パーフェクト Rails を読みました』シリーズの7回目の記事です。
前回の記事は『パーフェクト Rails を読みました(6章) - Programming log - Shindo200』です。

前回の記事を投稿してから間があいてしまったのですが、『パーフェクト Ruby on Rails』の7章を読み終えましたので、記録を残しておきます。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

7章の感想

7章ではテスティングフレームワークの使い方、MVC に対してのテストコードの書き方、TDDの進め方を学んでいきます。普段から Rspec + Capybara + FactoryGirl を使ってテストコードを書いていたり、『TDD Boot Camp』というイベントでTDDの思想について勉強していたりしましたので、この章はさくさくと読めました。

合わせて読みたい記事:TDDBC長岡に参加してきました - Programming log - Shindo200
※手前味噌ですが、未だに読み返すことが多いので紹介しました。

5章〜7章で便利なgemやCIサービスがたくさん紹介されていましたので、読んでいるだけで「自分でWebサービスを開発して、運用したい!」という気持ちが沸いてきて、体がうずいてしまいます。この書籍、恐ろしいです。

備忘録

Model, View, Controller に対してそれぞれテストすべきこと

Model に対してテストすべきこと

  • メソッドが期待通りの値を返すか
  • メソッドが期待通りの副作用をもたらしているか
  • バリデーションが正しく設定されているか

View に対してテストすべきこと

  • 期待通りのhtml片が出力されているか

Controller に対してテストすべきこと

  • アクションがビューに渡すインスタンス変数に期待通りのオブジェクトを代入しているか
  • アクションが期待通りのステータスコードを返しているか
  • アクションが期待通りのテンプレートを選択しているか

should-matchers

バリデーションやリレーションなどのテストで使える様々なマッチャーを提供する should-matchers という gem があります。

試しに、この gem を使ってバリデーション関連のテストコードを書いてみます。

class BlogEntry < ActiveRecord::Base
  validates :title, length: { maximum: 50 }, presence: true
end

# RSpec
describe BlogEntry do
  it { should validate_presence_of(:title) }
  it { should ensure_length_of(:title).is_at_most(50)
end

とても簡潔に書くことができますね。テストを実行すると以下のようなメッセージが出力されます。

$ .bin/bundle exec rspec spec/models/blog_entry_spec.rb

BlogEntry
  should require title to be set
  should ensure title has a length of at most 50

Finished in 0.03148 seconds (files took 1.42 seconds to load)
2 examples, 0 failures

fixture replacement

テストを実行する前にあらかじめテスト用のデータを作成しておきたい場合、Rails 標準付属の fixture という機能を使ってテスト用のデータを作成しておきます。fixture では Yaml 形式でテスト用のデータの内容を書いておきます。簡単に使うことができますので、シンプルなWebアプリを作るときはこれで十分だと思うのですが、開発の現場ではより便利な機能を持つ fixture replacement という種類のライブラリが利用されています。The Ruby Toolboxで人気の fixture replacement を調べてみました。(2014/06/22 現在)

f:id:Shindo_Masaya:20140622211334p:plain:w600

FactoryGirl という gem が圧倒的に人気ですね。

Rspec のexpect記法とshould記法について

Rspec3 が正式リリースされ、should記法を使うと警告が出るようになりました。Rspec3 ではexpect 記法を使ってください。ちなみに、BasicObject を継承したクラスに対して should を使うとテストが通らないことがあるので、expectという新しい記法を作ったようです。(BasicObject を継承するというのはすごく稀なケースだと思うのですが……そうでもないのでしょうか?)

参考: Myron Marston » RSpec's New Expectation Syntax

どうしても Rspec3 でshould記法が使いたい場合、以下のような設定を spec_helper.rb などに記述しておくことで Rspec3 でもshould記法が使えるようです。

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    # should記法を使う(expect記法を使うと警告を出す)
    expectations.syntax = :should

    # expect記法を使う(should記法を使うと警告を出す)
    expectations.syntax = :expect

    # どちらの記法も使う
    expectations.syntax = [:should, :expect]
  end
end

capybara-webkit などのドライバと一緒に database_cleaner をインストールすべき理由

データベースからデータの削除を行う database_cleaner という gem があります。capybara-webkit などのドライバを使う場合は、必ず一緒にインストールしておいてください」という話しを聞いたことがあったのですが、何故一緒にインストールしないといけないのでしょうか。

『パーフェクト Ruby on Rails』P.262 から引用します。

テスト中にデータベースを操作した場合、テストケース毎にデータベースの状態を元に戻さないといけません。Railsはデフォルトでは、データベーストランザクションの機能を利用して、テストケース毎にデータベースをロールバックすることで状態を元に戻しています。しかし、seleniumやpoltergeist、capybara-webkitなどのドライバは、テストケースとして実行されるプロセスとは別にサーバ用のプロセスを作ります。トランザクション中の変更は別のプロセスからは見れません。そのため、これらの別プロセスを起動するドライバを利用したテストを行う場合、Railsデフォルトの設定ではデータベースの変更内容を見ることができません。

capybara-webkit などのドライバからデータベースを操作した場合、ドライバがデータベースをどのように変更したのかテスティングフレームワークは知ることができないから、テスト前の状態に正しく戻すことができず、データベースにデータが残ってしまう。だから、ドライバを使うときは、データベースの削除を行う gem (detabase_cleanerなど)を一緒にインストールしないといけない……ということですね。

お疲れ様でした

8章では「DevOps」について触れるようです。「自分でWebサービスを開発して、運用したい!」という気持ちが沸いてきたタイミングで「DevOps」ネタがあるのはとても嬉しいです。