マスターの導きを受けつつ、TDD で Ruby を学びながら悟りを開く - Koans

Written by @dr_taka_n at 2011/08/14 23:55 []

Koans とは

クイズ形式で Ruby を学ぶ Koans。その手法がなかなか凝っている。

koans

The goal is to learn the Ruby language, syntax, structure, and some common functions and libraries. We also teach you culture. Testing is not just something we pay lip service to, but something we live. It is essential in your quest to learn and do great things in the language.

Koans は Ruby 言語の文法、構造、そして、幾つかの共通関数やライブラリを学ぶことをゴールとしたもので、TDD の手法を用いてそれを実現している。

path_to_enlightenment.rb というファイルを起動することで、あるテストケースがカルマにダメージを与えたとのメッセージが表示され、マスターから助言を授かることになる。
表示されるメッセージを頼りにテストケースをパスするように修正していくことで、次の新たな設問(新たなカルマへのダメージ)へと進み、30ちょっとのカテゴリの計272問(2010/12/23版)の設問をクリアすることで悟りをひらけることになる。

設問をクリアしていく過程は以下のようになる。
例えば2番目の設問は以下の通りとなっている。

koans2

マスターが「君はまだ悟りをひらいていない」と言っている。”The answers you seek…” ということで “This shoud be true – Please fix this” とある。
「please meditate on the following code:」ということで、about_asserts.rb:16:in test_assert_with_message’` が指定されているので、該当ファイルの該当行を確認すると、

  # Enlightenment may be more easily achieved with appropriate
  # messages.
  def test_assert_with_message
    assert false, "This should be true -- Please fix this"  # line: 16
  end

assert が false となっている。当然これではテストは通らないので、true に書き変えて保存する。

再度 path_to_enlightenment.rb を起動してあげると、

$ ruby path_to_enlightenment.rb 
AboutAsserts#test_assert_truth has expanded your awareness.
AboutAsserts#test_assert_with_message has expanded your awareness.
AboutAsserts#test_assert_equality has damaged your karma.

The Master says:
  You have not yet reached enlightenment.
  You are progressing. Excellent. 2 completed.

The answers you seek...
  Failed assertion, no message given.

Please meditate on the following code:
  /home/hoge/Dropbox/work/koans/about_asserts.rb:25:in `test_assert_equality'

learn the rules so you know how to break them properly
your path thus far [.X________________________________________________] 2/274

次の設問へと続く。

基本は、テストケースのアサーションの期待値を埋めていくことで設問に答えていくことになる。

メッセージ(“The ansers you seek…“)に既に答えが出てしまってはいるが、そこはじっくり読まずに、テストケースとテストメソッド名の方に集中して何を学ばせようとしているのか頭の片隅に置きながら期待値を考えていると、よく考えられた設問になっているなぁと感心してしまう。

最初の方こそ上記のようなホントに初歩的な設問だが、徐々に内容が濃くなってくるので、なかなか良い復習、勉強になる。

また、一気に 274 問実施するのは流石にキツイので、できる時間に少しづつ実施することが可能な作りになっているのもうれしい。何問目まで回答できているかは .path_progress というファイルが覚えておいてくれているので、特に意識することなく、path_to_enlightenment.rb を起動すれば前回の設問から続きを行うことができる。

watchr を使って回答に集中

設問に答えては path_to_enlightenment.rb を起動するというのはメンドイ。
自動化しておく。

watchr gem を使うとファイルを監視下におき、編集された際に実行させたいことを定義することができる。 回答を記述するファイルを編集するたびに自動的に path_to_enlightenment.rb が実行されるようにしておく。

watchr をインストールしていない場合はインストールしておく。

$ sudo gem install watchr

path_to_enlightenment.rb と同じディレクトリに以下のファイルを用意しておく。

koans.watchr:

def run
  system("clear; ruby path_to_enlightenment.rb")
end

watch('.*\.rb$') { run }
run

watchr を起動しておく。

$ watchr koans.watchr

これで回答を行うファイルを編集する度にコンソールがリフレッシュされ path_to_enlightenment.rb の結果を確認することができる。

triangle.rb

大半は assertion の期待値を埋めていくのだが、テストケースを通す為の関数を書く設問もある。

以下の2問。

  • 設問その1:about_triangle_project.rb
class AboutTriangleProject < EdgeCase::Koan
  def test_equilateral_triangles_have_equal_sides
    assert_equal :equilateral, triangle(2, 2, 2)
    assert_equal :equilateral, triangle(10, 10, 10)
  end

  def test_isosceles_triangles_have_exactly_two_sides_equal
    assert_equal :isosceles, triangle(3, 4, 4)
    assert_equal :isosceles, triangle(4, 3, 4)
    assert_equal :isosceles, triangle(4, 4, 3)
    assert_equal :isosceles, triangle(10, 10, 2)
  end

  def test_scalene_triangles_have_no_equal_sides
    assert_equal :scalene, triangle(3, 4, 5)
    assert_equal :scalene, triangle(10, 11, 12)
    assert_equal :scalene, triangle(5, 4, 2)
  end
end
  • 設問その2:about_triangle_project_2.rb
class AboutTriangleProject2 < EdgeCase::Koan
  # The first assignment did not talk about how to handle errors.
  # Let's handle that part now.
  def test_illegal_triangles_throw_exceptions
    assert_raise(TriangleError) do triangle(0, 0, 0) end
    assert_raise(TriangleError) do triangle(3, 4, -5) end
    assert_raise(TriangleError) do triangle(1, 1, 3) end
    assert_raise(TriangleError) do triangle(2, 4, 2) end
 end
end

設問その1のテストの要件を満たす関数を用意し、かつ、設問その2の制約もパスする必要がある。

答えは提供されていない(見つけられなかった。。)ので、ベストアンサーかどうかはわからないが、以下の関数を書くことでテストをパスしている。
ツッコミありましたらご指摘願いたい。

triangle.rb:

def triangle(a, b, c)
  raise TriangleError.new if [a, b, c].any? { |e| e <= 0 }
  raise TriangleError.new if ((a + b) <= c) || ((a + c) <= b) || ((b + c ) <= a)

  case [a, b, c].uniq.size
    when 1 then :equilateral
    when 2 then :isosceles
    else :scalene
  end
end

Rack アプリケーション向けの HTTP サーバ Unicorn の基本操作

Written by @dr_taka_n at 2011/07/30 23:55 [, ]

Unicorn とは?

Rails、Rack アプリケーションを動作させるコンテナとしては、PassengerThin、Mongrel などの選択肢がある。 それ以外にも Unicorn という Rack アプリケーション向けの HTTP サーバがあり、今回試しに使ってみたのでそのメモ。

設計方針に特徴的な部分があり、以下の記事に詳しい。

github の記事の結論で書かれているが、

Passenger is awesome. Mongrel is awesome. Thin is awesome.

Use what works best for you. Decide what you need and evaluate the available options based on those needs. Don’t pick a tool because GitHub uses it, pick a tool because it solves the problems you have.

それぞれの HTTP サーバが良い点を持っており、自身のサービスの用途、目的に応じて適切なものを使用するのがよいでしょうと。

サーバの良し悪しを測る項目の1項目として ab を使ったベンチなどがよくとられている。 確かにスループットにある程度のレベルのものは要求したいが、それよりも運用に入った際の安定性、信頼性、何か問題が起きた際のリカバリ含めた可用性の部分を気にしていた。

今回 Unicorn を試してみようと思ったのは、そこにあり、先の記事にも、以下の記載があり、Unicorn を試してみることにした。

Honestly, I don’t care. I want a production environment that can gracefully handle chaos more than I want something that’s screaming fast. I want stability and reliability over raw speed.

ここで記載するのは Unicorn を動作させるまでの基本的な設定と Unicorn を操作する基本的なオペレーションのメモになる。

  • Unicorn のインストールと事前準備
  • Unicorn の設定ファイルを用意する
  • Unicorn サーバを起動する
  • nginx と Unicorn を連携する
  • Unicorn を停止する
  • Unicorn の設定の再読込
  • サービスの提供を止めずにプログラムの再読込

Unicorn のインストールと事前準備

今回のお試し環境は以下の通りとなる。

  • アプリには簡単な Sinatra Hello World アプリを使用
  • Unicorn サーバのフロントは nginx をリバースプロキシとしてたてる

Unicorn 以外に nginx と Sinatra gem がインストールされている必要がある。

Unicorn は RubyGems で提供されているので gem コマンドを使用してインストールする。

# gem install unicorn

動作環境のサンプルとして Sinatra アプリケーションを用意する。
(Sinatra がインストールされていない場合には、# gem install sinatra が必要。)

適当なディレクトリ($APP_DIR)に Rack アプリケーションのエントリポイントとなるファイル config.ru を用意し、簡単な HelloWorld アプリケーションを記述しておく。

$APP_DIR/config.ru:

require 'rubygems'
require 'sinatra/base'

class HelloApp < Sinatra::Base
  get '/hello' do
    'Hello World'
  end
end

run HelloApp

Rack の rackup コマンドを使用して、上記のアプリがきちんと動作することを確認しておく。

$ cd $APP_DIR
$ rackup
[2011-07-30 21:15:38] INFO  WEBrick 1.3.1
[2011-07-30 21:15:38] INFO  ruby 1.9.2 (2011-02-18) [i686-linux]
[2011-07-30 21:15:38] INFO  WEBrick::HTTPServer#start: pid=6260 port=9292

ブラウザから、http://localhost:9292/hello の URI で “Hello World” が表示されることを確認しておく。

Unicorn の設定ファイルを用意する

Unicorn は unicorn コマンドを使って起動するが、起動時に設定ファイルを指定して起動することができる。
設定例は、Class: Unicorn::Configurator からリンクのある以下のページで確認できる。

上記の最低限の設定を設定を流用し、以下の設定ファイルを用意する。
Unicorn は TCP/IP ソケットでも待機できるが、以下の設定では Unix ドメインソケットを使った設定のみとしている。

$APP_DIR/unicorn.conf:

worker_processes 2

listen '/tmp/unicorn.sock'

stderr_path File.expand_path('unicorn.log', File.dirname(__FILE__))
stdout_path File.expand_path('unicorn.log', File.dirname(__FILE__))

preload_app true

ログファイルの記載部分を見てもらえるとわかるが、Unicorn の設定ファイルには Ruby の構文が利用可能となっている。

Unicorn サーバを起動する

Unicorn サーバを起動する。
unicorn コマンドを利用して起動することになる。$APP_DIR に移動して、以下のコマンドで起動する。

$ unicorn -c unicorn.conf -D

先ほど作成した Unicorn の設定ファイルを -c オプションで指定し、-D オプションでデーモン起動を行っている。

unicorn はデフォルトで config.ru をアプリケーションのエントリポイントとして認識するので、起動時のディレクトリに存在する Sinatra で書かれた Rack アプリケーションである $APP_DIR/config.ru が上記のコマンドで読み込まれている。

ログファイルを確認してみる。

$ cat unicorn.log
I, [2011-07-30T21:50:47.966116 #6632]  INFO -- : unlinking existing socket=/tmp/unicorn.sock
I, [2011-07-30T21:50:47.966669 #6632]  INFO -- : listening on addr=/tmp/unicorn.sock fd=3
I, [2011-07-30T21:50:47.967666 #6632]  INFO -- : Refreshing Gem list
I, [2011-07-30T21:50:48.343228 #6632]  INFO -- : worker=0 spawning...
I, [2011-07-30T21:50:48.343795 #6632]  INFO -- : worker=1 spawning...
I, [2011-07-30T21:50:48.344260 #6632]  INFO -- : master process ready
I, [2011-07-30T21:50:48.344266 #6635]  INFO -- : worker=0 spawned pid=6635
I, [2011-07-30T21:50:48.344450 #6635]  INFO -- : worker=0 ready
I, [2011-07-30T21:50:48.344672 #6638]  INFO -- : worker=1 spawned pid=6638
I, [2011-07-30T21:50:48.344863 #6638]  INFO -- : worker=1 ready

エラーが表示されておらず、上記のようなログが出力されていれば問題なく起動が行えている。 master プロセスが1つ上がり、worker プロセス2つを起動している。

nginx と Unicorn を連携する

フロントでリクエストを処理する nginx の設定。

最低限の設定というところで、以下の設定を記述する。

  • upstream ディレクティブ(ロードバランシングの指定)で unicornapp というバックエンドサーバを定義し、Unicorn サーバの Unix ドメインソケットを指定しておく。
  • location ディレクティブで upstream に指定した unicornapp バックエンドサーバを指定する
upstream unicornapp {
  server unix:/tmp/unicorn.sock;
}

server {
        listen   80;
        server_name localhost;

        location / {
               proxy_pass http://unicornapp;
        }
}

nginx 設定ファイルの再読込を行い、ブラウザで http://localhost/hello の URI を参照すると先ほどの rackup コマンドを使用して確認した “Hello World” と同じメッセージの画面が表示されていることが確認できる。
(ここで確認している画面は Unicorn で提供されているサービス)

unicorn のログにもリクエストを処理したメッセージが確認できる。

$ tail -f unicorn.log
...
I, [2011-07-30T22:13:27.755913 #7306]  INFO -- : worker=1 ready
I, [2011-07-30T22:13:27.756925 #7303]  INFO -- : worker=0 spawned pid=7303
I, [2011-07-30T22:13:27.757150 #7303]  INFO -- : worker=0 ready
127.0.0.1 - - [30/Jul/2011 22:13:39] "GET /hello HTTP/1.0" 200 11 0.0155

以上で基本的な設定はおしまい。

Unicorn を停止する

Unicorn に対する操作は基本 Unicorn の master プロセスに対してのシグナルでの操作となる。

まずは停止方法。2つの停止方法がある。

  • プロセスを即座に終了する: INT/TERM シグナルを使用
  • 処理を行っているリクエストが完了するのを待ってプロセスを終了する: QUIT シグナルを使用
$ ps -ef | grep unicorn | grep -v grep
hoge      7299     1  0 22:13 ?        00:00:04 unicorn master -c unicorn.conf -D
hoge      7303  7299  0 22:13 ?        00:00:03 unicorn worker[0] -c unicorn.conf -D
hoge      7306  7299  0 22:13 ?        00:00:03 unicorn worker[1] -c unicorn.conf -D

上記でわかるように、master プロセスは 7299 のプロセスIDで動作している。master プロセスに対してシグナルを送る。

Unicorn を gracefully に終了させる場合、

$ kill -QUIT 7299

unicorn.log を確認。

I, [2011-07-30T23:32:13.866125 #7299]  INFO -- : reaped #<Process::Status: pid 7303 exit 0> worker=0
I, [2011-07-30T23:32:13.866403 #7299]  INFO -- : reaped #<Process::Status: pid 7306 exit 0> worker=1
I, [2011-07-30T23:32:13.866683 #7299]  INFO -- : master complete

2つの worker プロセスと master プロセスが終了したことがわかる。

Unicorn の設定の再読込

Unicorn の設定を再読込させる場合は、master プロセスに HUP シグナルを送る。

$ kill -HUP <master のプロセスID>

ログを確認してみると、再読みされていることが確認できる。

I, [2011-07-30T23:35:42.261177 #7768]  INFO -- : reloading config_file=unicorn.conf
I, [2011-07-30T23:35:42.272338 #7768]  INFO -- : Refreshing Gem list
I, [2011-07-30T23:35:42.557187 #7768]  INFO -- : done reloading config_file=unicorn.conf
I, [2011-07-30T23:35:42.557362 #7768]  INFO -- : reaped #<Process::Status: pid 7773 exit 0> worker=0
I, [2011-07-30T23:35:42.557432 #7768]  INFO -- : reaped #<Process::Status: pid 7776 exit 0> worker=1
I, [2011-07-30T23:35:42.557545 #7768]  INFO -- : worker=0 spawning...
I, [2011-07-30T23:35:42.558136 #7768]  INFO -- : worker=1 spawning...
I, [2011-07-30T23:35:42.558726 #7782]  INFO -- : worker=0 spawned pid=7782
I, [2011-07-30T23:35:42.558966 #7782]  INFO -- : worker=0 ready
I, [2011-07-30T23:35:42.559173 #7785]  INFO -- : worker=1 spawned pid=7785
I, [2011-07-30T23:35:42.559430 #7785]  INFO -- : worker=1 ready

サービスの提供を止めずにプログラムの再読込

プログラム(ここで言う Sinatra Hello World アプリ)を再配置するときなど、再度プログラムをロードし直す必要がある。
Unicorn サーバを停止して、再起動でもよいが、サービスの提供を止めずに実施する場合の実施方法は以下の方法となる。

現在のプロセスの状況は以下の通り。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:00 unicorn master -c unicorn.conf -D
hoge      7782  7768  0 23:35 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7785  7768  0 23:35 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D

ここで何らかのアプリの改修があり、プログラムを再配置したとする。

Unicorn では、既存のプロセスをそのまま残し、新たな Unicorn サーバのセットをもう一セット作成し、リクエストの処理もそちらに引き継いであげることができる。

まず 現在の master プロセスに USR2 シグナルを送る。

$ kill -USR2 7768

Unicorn のプロセスを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:01 unicorn master (old) -c unicorn.conf -D
hoge      7782  7768  0 23:35 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7785  7768  0 23:35 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
hoge      7822  7768  1 23:43 ?        00:00:00 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D

元々あったプロセス(master pid: 7768)に old という表示が入っており、Unicorn サーバのセットがもうワンセット追加されていることがわかる。

新たに生成されたプロセスには、プログラムの更新が反映されている。

この時の Unicorn の動作をログで確認してみると以下の動作になっている。

I, [2011-07-30T23:43:20.117449 #7822]  INFO -- : executing ["/home/hoge/.rvm/gems/ruby-1.9.2-p180/bin/unicorn", "-c", "unicorn.conf", "-D"] (in /home/hoge/Dropbox/work/unicorn-app-sample)
I, [2011-07-30T23:43:20.117641 #7822]  INFO -- : forked child re-executing...
I, [2011-07-30T23:43:20.798555 #7822]  INFO -- : inherited addr=/tmp/unicorn.sock fd=3
I, [2011-07-30T23:43:20.798829 #7822]  INFO -- : Refreshing Gem list
I, [2011-07-30T23:43:21.165052 #7822]  INFO -- : worker=0 spawning...
I, [2011-07-30T23:43:21.165798 #7822]  INFO -- : worker=1 spawning...
I, [2011-07-30T23:43:21.166209 #7826]  INFO -- : worker=0 spawned pid=7826
I, [2011-07-30T23:43:21.166311 #7822]  INFO -- : master process ready
I, [2011-07-30T23:43:21.166401 #7826]  INFO -- : worker=0 ready
I, [2011-07-30T23:43:21.166635 #7829]  INFO -- : worker=1 spawned pid=7829
I, [2011-07-30T23:43:21.166768 #7829]  INFO -- : worker=1 ready

新しいプロセスにリクエストの受付処理は引き継がれているので、古い方のプロセスを落としていく。

まずは、古い方の master プロセスに WINCH シグナルを送り、その worker プロセスを落としてあげる。 WINCH シグナルはその master プロセスの worker プロセスを gracefully に止めてあげる。

$ kill -WINCH 7768

プロセスとログを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7768     1  0 23:35 ?        00:00:01 unicorn master (old) -c unicorn.conf -D
hoge      7822  7768  0 23:43 ?        00:00:01 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
I, [2011-07-30T23:48:45.648299 #7768]  INFO -- : gracefully stopping all workers
I, [2011-07-30T23:48:45.678491 #7768]  INFO -- : reaped #<Process::Status: pid 7782 exit 0> worker=0
I, [2011-07-30T23:48:45.678664 #7768]  INFO -- : reaped #<Process::Status: pid 7785 exit 0> worker=1

確かに、古い方の Unicorn のプロセスは、master プロセスだけが残り、worker は全て終了している。

一旦ここで念のため、アプリがキチンと動作しているか確認しておく。
問題無いようであれば、古い master プロセスも止めてあげる。

$ kill -QUIT 7768

プロセスとログを確認してみる。

$ ps -ef | grep unicorn | grep -v grep
hoge      7822     1  0 23:43 ?        00:00:01 unicorn master -c unicorn.conf -D
hoge      7826  7822  0 23:43 ?        00:00:00 unicorn worker[0] -c unicorn.conf -D
hoge      7829  7822  0 23:43 ?        00:00:00 unicorn worker[1] -c unicorn.conf -D
I, [2011-07-30T23:51:35.694051 #7768]  INFO -- : master complete

これで、新しいプログラムで Unicorn サーバが動作している状態となる。

情報の集約とコミュニケーションの円滑化 - チームの情報のハブになる便利なツール Flowdock

Written by @dr_taka_n at 2011/07/09 00:35 [, ]

チーム内で扱われる情報を一箇所に集約し、リアルタイムにそれらの情報についてコミニュケーションをとれるようなツールはないだろうか? たまたまフォローしている方のつぶやきで知った Flowdock。1ヶ月程使ってみたが、まさに求めていた(以上の)ツール。

lowdock

実際に使用している画面は以下のような感じ。
使い方は至ってシンプル。まさにヒットしているコミニュケーションツール(twitter だったり、2ch だったり)の特徴だ。 このシンプルさのため、自分たちの目的に合わせて自由に使える柔軟性も合わせ持つ。

lowdock-1

左側が influx(直訳すると「流入」) と呼ばれているもので、チーム内に必要な情報を流しこむところ。
上記画面では、twitter のリアルタイム検索の結果、チェックしておきたい twitter アカウントの情報、Wiki ツールである Confluence の更新履歴などがリアルタイムに流れ込んできている。

この流れこんできている1つ1つの情報にはコメントを入れることもできる。
例えば wiki で書かれた更新履歴についてコメントして議論したり、後でチェックしておきたい情報にはコメントを入れておくようなこともできるので、情報が埋もれてしまうことがない。

右側がグループチャットのペイン。
独り言を呟くのもよいが、twitter のように “@” を使って宛先を指定できたり、”#” を使ってタグを付けることもできる。
自分宛のつぶやきには上記のように背景色が黄色になるのでわかり易い。また、設定で変えられるが自分宛のつぶやきが届いた場合には「ポローン」と音で通知してれる。

使い方がシンプルな割に多くの特徴をもつアプリケーションで、なかなかその良さの全ては書ききれない。
先に記述した内容と多少重複すること部分もあるが、公式サイトで書かれている3つの特徴について書いてみる。

  • Discuss (議論する)
  • Aggregate (集約する)
  • Organize (整理する)

具体的にどういうことか?

Discuss (議論する)

リアルタイムグループチャット && ファイルの共有と保管場所として

チーム内のコミニュケーションにメールを使うのは非効率でストレスが溜まる。
開発者間であれば、IRC などを使うのも手なのだが、デザイナーさんであったり、IT を専門としない方などとのやり取りには使い難い。
また、画像などが扱えると会話がスムーズになったりもする。

誰にでも簡単に使えて、画像の扱いも容易なリアルタイムグループチャット、まず1つ目の Flowdock の特徴。

更に、Twitter のように、”@” で宛先を指定でき、指定された方では音声での通知(設定は変えられる)、背景色の変化などで容易に認識できるようになっている。 また、”#” でタグ付けが行える。

タグでの絞り込み検索、”@” の宛先による絞り込み検索なども行えるので、チャットアプリケーションにありがちな 過去情報が埋もれてしまう心配もない

タグ、”@” による宛先指定は取り外し可能なので、自分などはちょっとしたタスク管理にも Flowdock を使っており、タグを活用している。 すぐできないタスクには “#todo” タグを振っておき、後で”#todo”タグでタスクを検索、完了したらタグを剥がす、というような使い方。
また、”@” を併用することで、誰のタスクなのか?まで管理できたりもする。

Aggregate (集約する)

他システムとの容易な連携

Redmine や JIRA といった課題管理システム、Confluence などの Wiki システム、github など、プロジェクトには欠かせない他システムとの連携が容易に、かつ、リアルタイムに行える。

リアルなアップデート情報は Flowdock だけを気にしていれば事足りるようになる。

このチームに必要な情報を全て1箇所に集約できるというのが、2つ目の Flowdock の特徴。

Atlassian 製品の代理店でもあるらしく、Atlassian 製品との連携は特によくでている。
それ以外のものでも、基本 RSS フィードをはいているものであれば、Flow に流し込むことができる。

また、Twitter との連携も可能で、リアルタイム検索の結果の流しこみ、Twitter アカウントの follow も可能。

開発チームであれば、リポジトリへのコミットログや、deploy 情報も流し込めるので Flowdock の influx を追っているだけで、ティーム内の活動状況を把握することができる。

Organize (整理する)

タグ付けで会話をナレッジに

グループチャットでは一過性の情報をやり取りすることが大半だが、Flowdock ではそのやり取りがそのままナレッジとしても蓄積される仕組みを用意してくれている。
これが3つ目の Flowdock の特徴。

まず、やり取りされる情報は全て永久に Flowdock に保存される。
Twitter のようなタグ付け、メンションの機能、そして、全文検索も可能なので、過去やり取りした情報を容易に後からピックアップすることができる
(タグは日本語でも全然問題ないが、全文検索に関しては現状日本語での検索は行えない。)

「ドッグフードを食べる」

ダラダラと書いてしまったが、使ってみてもらえるとすぐにその良さを体感してもらえるのではないかと思う。

ソフトウェア開発の概念で、「ドッグフードを食べる」という言葉がある。

自社製品を自分たちでも実際に業務で使って評価して開発していることを言うのだが、使ってみて、まさにそうして開発されてきたサービスではないのかなと感じた。

自分たちが必要、便利だと思うものをサービスにして、それをその利用者にも喜んでもらえる、という状況は理想的な状況だ。

フィンランドの会社のようで、メンバの方とちょっとやり取りさせてもらったのだが、とても丁寧で親切。
そして、自分たちのサービスを好きで、誇りを持っており、更によいサービスにしていきたいと思っている意志と情熱がひしひしと伝わってきた。

今後の更なる進化も期待できるサービス。

作業データを複数の VM で共有する - VirtualBox「共有フォルダ」の使い方

Written by @dr_taka_n at 2011/07/03 17:01 [, ]

複数の VM 環境を扱っているとき、VM 上でカスタマイズする設定ファイル、スクリプトなどの大半は各 VM で共有できることが多い。
VM 環境毎に新たにファイルを作成したり、ファイルを scp 等でコピーしたり、など、まともにやっていると管理含めてメンドイことになる。
ローカルで開発を行っており、複数の VM を使って作業を行う場合、VM 環境からローカル(ホストOS)のファイルを扱えるようにしておいて、各 VM で共有できると何かと便利だ。

VM 環境からローカル(ホストOS)のファイルを「共有フォルダ」として扱えるようにする。

また、このホスト OS の対象ファイルをオンラインストレージサービス Dropbox などで管理していものを利用すると、どのマシンでも同じ作業データを使用して VM 環境を利用できるようになる。

環境と手順の概要

環境は以下の通りとなる。

  • Oracle VM VirtualBox v4.0.8
  • ローカル(ホスト)OSは、Ubuntu 10.04 Desktop or Windows XP
    VirtualBox が動作すれば特に選ばないはず。
  • 仮想 OS は Turnkey Linux (Ubuntu 10.04) を使用

手順は以下の流れとなる。

  1. VM Manager での作業
    1. VM Manager でネットワーク設定に “Host only adapter” を追加しておく
    2. 共有フォルダを作成しておく
  2. VM ゲスト OS での作業
    1. 必要なパッケージのインストール
    2. Guest Additions のインストール
      a. イメージのマウント
      b. Guest Addictions のインストール実行
    3. 共有フォルダを利用する

1. VM Manager での作業

まずは、VM Manager での作業。まだ VM のマシンは起動していない。

1.1. VM Manager でネットワーク設定に “Host only adapter” を追加しておく

VM Manager を使用して仮想マシンのネットワーク設定に “Host only adapter” を設定していない場合には設定しておく。

virtualbox-network-setting

1.2. 共有フォルダを作成しておく

VM から使用するローカル(ゲストOS)のディレクトリを指定し、共有フォルダとして VM Manager に登録しておく。

virtualbox-share-folder-setting

上記の例では、実体としてのフォルダを”ファルダのパス” (/home/xxx/Dropbox) に指定し、”フォルダ名” に “Dropbox” と指定している。 後者の “フォルダ名” というのが、VM 環境のゲスト OS から参照する際のフォルダの名称となる。

ここで、VM のマシンを起動しておく。

2. VM ゲスト OS での作業

VM のゲスト OS での作業になる。

2.1. 必要なパッケージのインストール

bzip2dkmsbuild-essentiallinux-headers-<kernel-version> が必要となる。

# apt-get install bzip2
# apt-get install dkms
# apt-get install build-essential
# apt-get install linux-headers-`uname -r`

2.2. Guest Additions のインストール

VirtualBox が提供している Guest Addictions のインストールを行う。 まずは、このインストールに使用するファイルをゲストOSから見えるようにする。

2.2.a) イメージのマウント

VirtualBox の “デバイス” メニューにある “Guest Addictions のインストール” をクリックする。

virtualbox-guest-addictions-install-1

クリックした後、画面上何の変化もなく、何が起きたの?的な状況になるが、これで、ゲスト OS から “Guest Addictions” のインストール用ファイルがあるイメージを参照できるようになっている。

ゲスト OS からマウントする。

# mkdir /media/cdrom
# mount /dev/cdrom /media/cdrom
# cd /media/cdrom/
# ls -l
total 37393
dr-xr-xr-x 3 root root     2048 2011-05-17 01:59 32Bit
dr-xr-xr-x 2 root root     2048 2011-05-17 01:59 64Bit
-r-xr-xr-x 1 root root      647 2011-01-19 21:42 AUTORUN.INF
-r-xr-xr-x 1 root root  7853516 2011-05-17 01:53 VBoxLinuxAdditions.run
-r-xr-xr-x 1 root root 14664192 2011-05-17 01:55 VBoxSolarisAdditions.pkg
-r-xr-xr-x 1 root root  9284432 2011-05-17 01:45 VBoxWindowsAdditions-amd64.exe
-r-xr-xr-x 1 root root  6190464 2011-05-17 01:39 VBoxWindowsAdditions-x86.exe
-r-xr-xr-x 1 root root   278832 2011-05-17 01:39 VBoxWindowsAdditions.exe
-r-xr-xr-x 1 root root     6966 2011-05-17 01:51 autorun.sh
-r-xr-xr-x 1 root root     5523 2011-05-17 01:51 runasroot.sh

インストールプログラムである VBoxLinuxAdditions.run が見えている。

2.2.b) Guest Addictions のインストール実行

インストールを実行する。

# ./VBoxLinuxAdditions.run 
Verifying archive integrity... All good.
Uncompressing VirtualBox 4.0.8 Guest Additions for Linux..........
VirtualBox Guest Additions installer
tar: Record size = 8 blocks
Removing existing VirtualBox DKMS kernel modules ...done.
Removing existing VirtualBox non-DKMS kernel modules ...done.
Building the VirtualBox Guest Additions kernel modules ...done.
Doing non-kernel setup of the Guest Additions ...done.
Starting the VirtualBox Guest Additions ...done.
Installing the Window System drivers ...fail!
(Could not find the X.Org or XFree86 Window System.)

“Window System drivers” が fail しているが、サーバ用途で使っていてファイルの共有だけの用途であれば無視して構わない。

2.3. 共有フォルダを利用する

先に VM Manager 側の作業で共有フォルダ “Dropbox” を作成していた。 この共有フォルダをゲスト OS でマウントする。

# mkdir Dropbox
# mount -t vboxsf Dropbox ~/Dropbox
# cd Dropbox/
# ls -l

以上でゲスト OS のファイルを VM ゲスト OS から利用可能となる。

参考サイト

Refinery CMS - カスタム Engine でのマイグレーションの追加

Written by @dr_taka_n at 2011/06/26 23:05 [, , ]

作成したカスタム Engine でマイグレーションを追加する場合、Refinery CMS のお作法としてどうするのがよいか? 如何様にもできそうだが、カスタム Engine のメンテナンス性が保てるやり方がよい。そのメモ。

まず最初に product という簡単なサンプルカスタム Engine を作成する。 その後にマイグレーションを追加する方法を記載していく。作業の流れは以下の通り。

  1. Rails 標準の rails generate migration でマイグレーションファイルを作成する
  2. 作成されたマイグレーションファイルをカスタム Engine 配下(vendor/engines/my_engine/db/migrate)に配置する
  3. generator を使用して Rails プロジェクト本体に配置し直して、rake db:migrate

0. 新規のカスタム Engine を作成しておく

これから作業するための新規のカスタム Engine を作成しておく。product という簡易な engine になる。

$ rails g refinery_engine product title:string description:text image:image
      create  vendor/engines/products/app/controllers/admin/products_controller.rb
      create  vendor/engines/products/app/controllers/products_controller.rb
      create  vendor/engines/products/app/models/product.rb
...(snip)...
      create  vendor/engines/products/refinerycms-products.gemspec
      create  vendor/engines/products/spec/models/product_spec.rb
------------------------
Now run:
bundle install
rails generate refinerycms_products
rake db:migrate
------------------------

最後のメッセージの指示通り引き続きコマンドを叩き、カスタム Engine を動作可能な状態としておく。

$ bundle install
...(snip)...
$ rails g refinerycms_products
      create  db/migrate/20110615142942_create_products.rb
      create  db/seeds/products.rb
------------------------
Now run:
rake db:migrate
------------------------
$ rake db:migrate
==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0014s
-- add_index(:products, :id)
   -> 0.0005s
==  CreateProducts: migrated (0.4472s) ========================================

以上で OK。サーバを起動して管理画面を確認してみると確かに product の管理画面が追加されている。

add-migrateion-to-engine

1. Rails 標準の rails generate migration でマイグレーションファイルを作成する

Product のモデルはすでに作成済みであるが、販売促進用のブローシャアをそれぞれの製品につけたくなったとする。製品に添付ファイルをつけられるようにする。

まずは、Rails 標準の rails generate migration を使用して普通に(Rails way で)マイグレーションファイルを作成する。

$ rails g migration AddBrochureToProducts
      invoke  active_record
      create    db/migrate/20110616161904_add_brochure_to_products.rb

マイグレーションファイルに必要な記載を行っておく。

db/migrate/20110615145336_add_brochure_to_product.rb:

class AddBrochureToProducts < ActiveRecord::Migration
  def self.up
    add_column :products, :brochure_id, :integer
  end

  def self.down
    remove_column :products, :brochure
  end
end

マイグレーションファイルへの追加は以上で終了となる。

上記のマイグレーションの記載に関して、products テーブルに brochure_id という外部キーのフィールドを追加し、関連付けまでさせてたのはわかるが、brochure 本体はどこ?となるだろう。

ここでは、Refinery CMS が標準で用意している Resources Engine を利用している。

resource(ファイル)を扱う為の仕組みは既に Refinery CMS には用意されており、Resource モデルも既に存在する。上記はその外部キーを products テーブルに作成しようとしている。

当然上記のスキーマの更新だけでは動作しないので、既に作成済みの Product モデルにも Resouce への関連を追加しておく。

vendor/engines/products/app/models/product.rb:

class Product < ActiveRecord::Base

  acts_as_indexed :fields => [:title, :description]

  validates :title, :presence => true, :uniqueness => true

  belongs_to :image

  belongs_to :brochure, :class_name => 'Resource' # add
end

2. 作成されたマイグレーションファイルをカスタム Engine 配下(vendor/engines/my_engine/db/migrate)に配置する

先ほどの作成されたマイグレーションファイルは Rails プロジェクト本体の db/migrate 配下に作成されている。 これを Engine の db/migrate 配下に移動する。

$ mv db/migrate/20110616161904_add_brochure_to_products.rb vendor/engines/products/db/migrate/

3. generator を使用して Rails プロジェクト本体に配置し直し rake db:migrate

Refinery CMS が用意している generator を使用して、Rails プロジェクト本体に配置し直す。

$ rails g refinerycms_products
      create  db/migrate/20110616163831_add_brochure_to_products.rb
You already have a migration called create_products
      create  db/seeds/products.rb
------------------------
Now run:
rake db:migrate
------------------------

products Engine 配下の db/migrate に移動したマイグレーションファイルが、Rails プロジェクト本体の db/migrate 配下にコピーされる。 これで migrate する。

$ rake db:migrate
==  AddBrochureToProducts: migrating ==========================================
-- add_column(:products, :brochure, :integer)
   -> 0.0014s
==  AddBrochureToProducts: migrated (0.0015s) =================================

仮に同じ名前のマイグレーションファイルがあった場合には、rails g refinerycms_products を実行しても Rails プロジェクト本体にはファイルはコピーされない。

$ rails g refinerycms_products
You already have a migration called add_brochure_to_products
You already have a migration called create_products
   identical  db/seeds/products.rb
------------------------
Now run:
rake db:migrate
------------------------

以上となる。

スキーマに変更を加える場合には 1 〜 3 の同様の作業を繰り返す。

まどろっこしいことをやっているようだが、この方法により、カスタム Engine ディレクトリ内に全て完結した構成がとれる。
独立性が保たれるので、他プロジェクトへの移植性も高くなる。

(蛇足) 管理画面にブローシャアの登録機能を追加

「カスタム Engine でのマイグレーションの追加」の作業は以上で終了だが、モデルには手を加えたものの、管理画面にブローシャアを登録する為の機能が存在しないので追加する。

既に管理画面用の共通部品として refinerycms-core で resource 用のPicker(/shared/admin/_resource_picker.html.erb)が用意されているので、これを利用する。

管理画面のフォームの view に以下の追加を行っておく。

vendor/engines/products/app/views/admin/products/_form.html.erb:

イメージのフィールドの下あたりで brochure 用のフィールドを追加しておく。

...(snip)
  <div class='field'>
    <%= f.label :image -%>
    <%= render :partial => "/shared/admin/image_picker", :locals => {
          :f => f,
          :field => :image_id,
          :image => @product.image,
          :toggle_image_display => false
        } %>
  </div>
  # add
  <div class='field'>
    <%= f.label :brochure -%>
    <%= render :partial => "/shared/admin/resource_picker", :locals => {
          :f => f,
          :field => :brochure_id,
          :resource => @product.brochure,
        } %>
  </div>

管理画面で確認すると確かにブローシャア用のフィールドが追加されておりちゃんと機能する。

add-migrateion-to-engine2

add-migrateion-to-engine3

同様にユーザに表示する画面にもブローシャアをダウンロードするための機能を追加しておく必要があるが、ここでは割愛する。