(2015年までの)odaillyjp blog

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

Ruby2.2で新たに加わったitselfメソッドについて

2014年12月25日にRuby2.2.0がリリースされました。Rubyがアップデートされるたびに、新機能の紹介エントリを書いていたのですが、今回はitselfメソッドの追加が気になりましたので、このメソッドだけに絞ってエントリを書きます。

Kernel#itself

itselfメソッドはレシーバ自身を返すだけのメソッドです。Ruby2.2.0未満のバージョンでも、以下のように実装することで、itselfメソッドを使うことができます。

class Object
  def itself
    self
  end
end

実は、ActiveSupport4.2.0にもこっそりと追加されていますので、Rails4.2.0以降を使っていれば、Ruby2.2.0未満でもitselfメソッドを使うことができます。

ところで、このメソッドはどのような場面で使うのでしょうか。RailsAPIドキュメントを参考にして、ユースケースを考えてみました。

ユースケース

例えば、ActiveRecordを使って、あるモデルの全てオブジェクトを取得する処理があるとします。
基本的には全てのオブジェクトを取得してほしいのですが、ユーザーがオプションとして取得条件を付けたときだけは、その条件で絞り込みを行って欲しいとします。この場合、今までは以下のように実装していたかと思います。

# Model
class Entry
  scope :published, lambda do
    # 公開済み記事のみを取得する条件が書かれたスコープ
  end

  scope :drafted, lambda do
    # 下書き記事のみを取得する条件が書かれたスコープ
  end
end

# Controller
class EntriesController
  def index
    @entries = Entry
    # 「公開済み記事のみ」か「下書き記事のみ」で絞り込むように指定されているならば、絞り込みを行う
    if state.presence_in(%w(published drafted))
      @entries.public_send(state)
    end
    @entries.order(:created_at)
  end
end

このままでも複雑ではありませんが、itselfメソッドを使うことで、このようにコーディングできるようになりました。

# Model
# 先ほどと同じなので省略

# Controller
class EntriesController
  @entries = Entry.public_send(state.presence_in(%w(published drafted)) || :itself).order(:created_at)
end

いかがでしょうか。public_sendメソッドやsort_byメソッドなど、メソッド名を引数とするメソッドのデフォルト値としてitselfメソッドを使うと良いのではないでしょうか。

以上、itselfメソッドの紹介でした。

2015/01/22 記事修正

エントリ公開当初のコード。

# Controller
class EntriesController
  @entries = Entry.public_send(params[:state].presence_in(%w(published drafted)) || :itself).order(:created_at)
end

「presence_inメソッドで値をチェックしているとはいえ、paramsの値をそのままpublic_sendメソッドの引数に使うのは怖いのでは?」という意見をいただきまして、コードを修正しました。

Yochiyochi Committers Who's Who in 2014

この記事は「よちよちAdventCalender」の2日目の記事です。昨日の記事は『Precompile - じゃがいもエンジニアのブログ』でした。

毎年のRubyKaigiで、近永さんという方が、1年間の内でCRubyに対してのコミット数が多かった方々を紹介するCRuby Committers Who's Whoという講演をされています。とてもクールな講演で、「どこかで似たようなことをやってみたい」と思っていました。現在、よちよち.rbのmeetupsリポジトリにコントリビュータとして登録されているメンバーは55人います。多いですね。今回は「よちよちAdventCalender」というイベントに乗じて、『CRuby Committers Who's Who』の真似ごととして、よちよち.rbのリポジトリに対してコミット数が多かった方々の名前と今年の活躍ぶりを勝手ながら紹介していきます。

ルール

  • 2014年11月30日の段階で記録を取っています
  • 自分自身を紹介しても仕方がないので、自分はランキングから省いています

紹介

1位から順番に発表していきます。

1位 yucao24hoursさん

1位はご存知、よちよち.rbの主催者であるyucao24hoursさんです。「全員が初心者である」という今までにあまり無かった特徴を持つコミュニティを一から立ち上げ、毎週休まずに開催されています。その活躍ぶりから、よちよち.rbのRubyKajaに選ばれました。yucao24hoursさんの数多い活躍ぶりから1つだけをピックアップするならば、2013年の年末に行われた「よちよちプルリクエスト大会」の開催だと思います。「よちよちプルリクエスト大会」はよちよち.rbが始まって、まだ間も無い頃に開催されました。

「 Pull Request を送ってみたいんです!」
「普段は怖くてできなかったので、みんなで PR を試せたらいいな!」

yucao24hoursさんはそんな参加者の思いを感じ取り、一人で企画して準備し、開催まで話を持っていきました。私も参加者の内の一人だったのですが、そのカリスマ性に驚かされました。

2位 katorieさん

2位のkatorieさんを紹介します。katorieさんは「よちよち.rb」フィヨルドインターン「RubyKaigi2014」などを通じてコミュニティにコミットされています。katorieさんは第0回よちよち.rbからの参加者で、今もほぼ毎回参加しています。(もしかして、皆勤賞???)katorieさんの何よりも凄い所は、よちよち.rbの参加レポートを毎回欠かさずにブログに書いているところです。一度でもブログを書いたことがある人でしたら、ネット上に公開する文章を書くことがどんなに大変なことであるのか分かるかと思います。毎回欠かさずにブログを書き続けることができる情熱の元を教えていただきたいです。

katorieさんのブログはこちらです。
プログラミングお勉強記録

3位 ta1kt0meさん

3位のta1kt0meさんの紹介します。ta1kt0meさんはRubyKaigi2014のgihyo.jpレポート班を担当されていました。よちよち.rbの中のRubyKajaを決めるために、皆でRailsを使って「kajaeru」という投票サービスを作ったのですが、「kajaeru」という面白い名前はta1kt0meさんが付けられました。今は『エイゴ・ギジュツ・ドクショカイ MetaNight』というイベントを開催されています。ta1kt0meさんの活躍の中で紹介したいことと言えば、ブログだと思います。ta1kt0meさんは技術のキャッチアップがとても早く、新たに学んだことを優しい文章でブログに書きますので、読んでいてすごく面白いです。

そんなta1kt0meさんのブログはこちらです。
JanGaJan.com

4位 upinetreeさん

4位は第0回よちよち.rbからの参加者であるupintreeさんです。upinetreeさんの活躍・・・というよりは個人的に助けられたことなのですが、あえてマイナーなところから紹介したいと思います。

よちよち.rbでは開始して間も無い頃から、自己紹介ファイルをGithubで管理し、参加するたびに自分の自己紹介ファイルを更新してプルリクを送り、それを皆でレビューするという方法を行っています。Gitの使い方、特にコミットの粒度に関してはかなり細かくレビューが入りまして、だいたい「一つの作業に対して、一つのコミットを作る」という考えでレビューがされていると思っています。よちよちでのコミットの粒度の考えが面白かったので、僕は他のプロジェクトに参加したときにも同じ粒度でコミットしてみたのですが、メンバーから「こまめにgit commitすると、リポジトリが太ってロググラフが見え難くなるから止めてね」と言われてしまいました。そのことをよちよちで話したみたところ、よちよちメンバーの皆さんが色々な意見を出してくださったのですが、その中でも特にupinetreeさんは親身になって相談にのっていただきました。

(チャットの一部だけを引用)

個人的には一つのコミットに一つのことをするようなるべく心がけてます

(コミットが細か過ぎるとコミットメッセージを読むのに)多少は苦労しますが、それだけ内容があるPRということですからねぇ…むしろコミットメッセージにない修正が一緒に入っていた時のほうが混乱するかなと思います。その指摘の意図がどのレベルでまとめるかわからないのですが…

ある種チームの文化によるものだとも思うので、話しあって考え方を共有しておくと良いかもです

すごく嬉しかったです。個人的なことですが、紹介しておきたいことでしたので、あえて書かせていただきました。

5位 bonbon0605さん

5位のbonbon0605さんを紹介します。bonbon0605さんの活躍の中で紹介したいことは、一度よちよち.rbで発表してくださったLTについてです。bonbon0605さんがTokyuRuby会議07の抽選LT用として「よちよち.rbで知ったコミュニティの素晴らしさ」という内容のスライドを作られていました。このスライドの中で「抽選LTは怖いけど、ゆかおさん、おだいさん、松さんがいるし、行こうかなと思った」と僕の名前を書いてくださったことがすごく嬉しかったです。

bonbon0605さんのスライドはこちらです。
よちよち.rbで知ったコミュニティの素晴らしさ

6位 pupupopoさん

6位はpupupopoさんです。今回はコミット数で順位付けをしてしまいましたので6位という扱いになりましたが、参加回数は2位のkatorieさんとほぼ同じくらいだと思います。(2〜10位までのコミット数の差はほとんどありませんでしたので、順位はあまり気にしないでください。)pupupopoさんの活躍と言えば、よちよち.rbが始まってからちょうど1周年目にあたる第45回ミートアップのときに、会場係を担当されたことだと思います。素敵な会場を用意してくださり、ありがとうございました。

7位 to0526さん

7位は「ミスタードアラ」ことto0526さんです。to0526さんはよちよち.rb初参加者にとても優しい方です。to0526さんの活躍と言えば、11月に開催された開発合宿でカードゲーム『ニムト』を持ってきて、よちよちメンバーに流行させたことだと思います。僕も合宿帰りの電車の中でRuby版ニムトを作ることになるなんて、想像していませんでした。

8位 ryotashiraishiさん

8位は「うめぼし」ことryotashiraishiさんです。ryotashiraishiさんがよちよち参加され始めたのは、7月くらいからだったかと思うのですが、コミット数では上位にランクインされました。ryotashiraishiさんの活躍で紹介したいことは、11月のよちよちbeerで幹事を担当されたことです。よちよち.rbでは、ビールを飲みながら技術ネタとか普段興味があることとかを話し合って交流を深める「よちよちbeer」というイベントを月1回くらいのペースで開催しています。ryotashiraishiさんは、ある開発コンテストに応募する予定のアプリを開発している最中で、11月はとても忙しかったようですが、そんな忙しい中でもよちよちbeerの幹事を担当してくれました。ありがとうございました。

9位 highwideさん

9位はhighwideさんです。今回はmeetupリポジトリだけを見て順位を付けてみましたが、kajaeruリポジトリのコミット数で見ると2位に入っています。11月に開催された開発合宿にも参加されていて、そこで面白いサービスを企画し、開発に力を入れられていました。highwideさんの活躍と言えば、やはり先述のkajaeruだと思います。yucao24hoursさんやto0526さんが厳しいレビューを行う中で、何度もプルリクエストを送り、リリースまで頑張ってくれました。

10位 yuki3738さん

10位はyuki3738さんです。yuki3738さんはスマートニュースさんでよちよち.rbを開催するときの会場係を担当されています。とても優しい方で、僕が転職活動でうまくいかないことがあって憂鬱になっていたときに飲みに誘ってくれました。ありがとうございました。

他にも

今回はよちよち.rbのリポジトリに対するコミット数だけを見て上位10名の紹介をしてみましたが、よちよち.rbには他にも素敵な方々がいます。時間の都合により紹介できなくて、ごめんなさい。どんな素敵な方がいるのか気になりましたら、ぜひよちよち.rbに遊びに来てみてください。

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