Passenger (mod_rails) を使ってみた

Posted by okkez Tue, 15 Apr 2008 13:58:00 GMT

Overview — Phusion Passenger (a.k.a. mod_rails)

思ったより簡単だったのでちょっとだけ手順を書いておく。

  • Apache は 2.2 系
  • Rails2.0.2
  • Redmine0.7.0RC1

上記はインストールできている and 動作確認できている前提で。 まずは、Passenger をインストール。

sudo gem install passenger

で以下のコマンドを実行。

passenger-install-apache2-module

そうすると対話型っぽい感じになって色々と親切に教えてくれる。足りないライブラリとそれらの入手方法とか。 個人的にはもう少し不親切でもいいような気がした。

このコマンドではApacheのモジュールをビルドするので Windows ではかなり苦労するであろう事が予想される。(調べてないけど)

最後に、httpd.conf に書く設定内容が表示されるのでそれをコピペして Apache を再起動すればOK。 各アプリの設定は後で書く。

もう一つのインストール方法でやる場合は、ソースを展開したディレクトリの下にビルドしてインストールされてしまうので、ちょっと気持ち悪い。手でコピーすればいいけどコマンドが動的に生成されるっぽいのでその辺の調整が必要。

### Redmine を使う

  • /var/webapps/redmine に置いている
  • /var/www/html が Apache の DocumentRoot

サブドメインが使える場合は以下でOK。

ServerName redmine.example.com DocumentRoot /var/webapps/redmine/public

サブドメインが使えない場合(素朴なイントラネットなど)。

ln -s /var/webapps/redmine/public /var/www/html/redmine

のようにシンボリックリンクを DocumentRoot 以下に用意する。 で、以下の内容を httpd.conf を追加する。

DocumentRoot /var/www/html RailsBaseURI /redmine

サブドメインが使える場合は NameBase の VirtualHost でいくらでもアプリケーションを置くことができるが、そうでない場合は以下のようにすればいけるかもしれない。(まだ試してない)

RailsBaseURI /redmine1 RailsBaseURI /redmine2

考えてみたけど VirtualHost を使用せずに一つのアプリに別々のRailsEnv を設置する方法がわからない。 上のやり方だと、一つの環境しか設定できないような気がするし。

以上の設定内容は全部、 Passenger のサイトに書いてあった。最初からドキュメントが揃っているのは素晴らしい!

Posted in | コメントはありません |

Capistrano を使ってみた

Posted by okkez Sat, 29 Mar 2008 13:47:00 GMT

Ruby で作られたデプロイツール Capistrano を使ってみたので使い方をまとめておく。

インストール

gem でインストールする。

$ sudo gem install capistrano

依存ライブラリも入るはず。

最初の一歩

こでは2.2.0を使用する。

$ cap --version Capistrano v2.2.0 $ cd ~/railsapp/ $ capify .

Rails アプリを作成しているディレクトリに移動して capify コマンドを実行するだけで その Rails アプリは Capistrano 対応になる。

capify コマンドでは以下のファイルが生成される。

  • Capfile
    • レシピファイル
  • config/deploy.rb
    • 設定ファイル

あとは config/deploy.rb を編集するだけで良い。

config/deploy.rb の編集

set :application, "application name" set :repository, "/path/to/repository" set :deploy_to, "/path/to/deploy/path" role :app, "application server" role :web, "web server" role :db, "databese server", :primary => true

set :hoge のところは特に説明は不要だと思う。しかし、set :repository の設定でちょっとハマったので後述する。 role の方は、各サーバーの名前や IPアドレスを記述する。ドキュメントによると複数のサーバを指定することも可能なようだ。 ウチでは全部同じサーバなのでそういう風に設定した。

$ cap deploy:setup

これでデプロイ先にディレクトリが作成される。

$ cap -q deploy:check

これで足りないものが無いかチェックしてくれる。

$ cap deploy:cold

初回のデプロイは上記コマンドを実行する。二回目以降は以下を実行する。

$ cap deploy

簡単ですね!

ハマりポイント

ウチのサーバの構成。

[作業マシン] <- [The Internet] -> [サーバ](app, web, db, svn) # 間の通信は svn or http

ご存知のとおり、Subversion は svn, svn+ssh, file, http, https などのプロトコルに対応している。 自分の場合は、 svn+ssh でアクセスするようにしていたので最初はそのように設定した。

その場合、以下のように実行されるようでうまく行かなかった。

  1. ローカルでリポジトリの情報を取得
  2. app サーバに ssh で乗り込む[a]
  3. appサーバから svn+ssh でリポジトリからデータを取得[b]

3番の部分で ssh のコネクションが突然閉じてしまう。ログを確認してもよく分からなかったのであまり調査せずに サーバに乗り込んでから cap コマンドを実行するようにした。

リポジトリの設定だけ file:// を使用するように変更した。

今のところうまく動いているので、もう少し時間のある時にしっかり調べてみようと思う。

Posted in | コメントはありません |

第 24 回 Ruby 勉強会 @ 関西 初級者レッスン演習問題添削

Posted by okkez Sun, 23 Mar 2008 01:26:00 GMT

Posted in | コメントはありません |

第 24 回 Ruby 勉強会 @ 関西 初級者レッスン演習問題添削

Posted by okkez Sun, 23 Mar 2008 01:26:00 GMT

コードを書いて晒してくれていたのは上記の人たち。
# 公式サイトにトラックバックしてくれてた中から抜粋しました

今回はその中から特に添削希望してたっぽい上二つを添削します。
# 例によって趣味が出てます。

ここでは添削後のコードを示しますので、添削前のコードはリンク先を参照してください。

ちなみに今回は時間の都合でログ解析の添削はやりません。あしからずご了承ください。

id:e-kuroda

char_count = 0; word_count = 0; line_count = 0; chars = Hash.new(0) words = Hash.new(0) File.open("text.txt") do |f| f.each do |l| char_count += l.size l.scan(/./m) {|c| chars[c] += 1 } l.strip.scan(/\w+|[^\s\w]+/) do |w| words[w] += 1 word_count += 1 end end line_count = f.lineno end word_max_size = words.map{|w, n| w.size }.max puts "文字数 #{char_count}" puts "単語数 #{word_count}" puts "行数 #{line_count}" puts "文字出現頻度(文字 頻度(%) 回数)" puts chars.sort_by {|c, n| -n }.map{|c, n| sprintf("%-10s %4.2f%% %5d", c.dump, 100 * n/char_count.to_f, n) }.join("\n") puts "単語出現頻度(単語 頻度(%) 回数)" puts words.sort_by {|w, n| -n }.map{|w, n| sprintf("%-*s %4.2f%% %5d", word_max_size, w, 100 * n/word_count.to_f, n) }.join("\n")

添削ポイント

  • printf を使う意味が少ないところでは使わないようにした
    • puts の方が短いため
  • 出現頻度のところで一つずつ printf してたのをやめた
    • リストを作ったならできるだけリストのまま処理した方がわかりやすいため
  • 単語出現頻度の方でレイアウトが崩れていたのを修正した
  • 正規表現も直そうかと思ったけどやめておいた(\Wを使えば済んだ?)

id:mas-higa

def words(str) str.join.sub(/^\W+/, "").split(/\W+/) end str = ARGF.readlines puts "#{str.size} lines." puts "#{words(str).size} words." puts "#{str.join.size} characters." ### words = Hash.new(0) str.join.sub(/^\W+/, "").split(/\W+/).each do |i| words[i] += 1 end puts words.sort_by{|k, v| v}.map{|k, v| sprintf("%8d: %s", v, k) }.join("\n") ### chars = Hash.new(0) (0x00..0x7e).each{|c| chars[c.chr] = 0 } str.join.split(//).each do |c| chars[c] += 1 end chars.sort_by{|c| c[0][0] }.each_with_index{|c, idx| printf("%6d %6s", c[1], c[0].dump) puts if (idx + 1) % 8 == 0 }

添削ポイント

  • 分けて実行するのが面倒だったので一つのファイルにまとめました
  • 直す所が少なくて困りました
  • 比較的スコープが長い一文字変数はやめた
    • ゴルフしてた?
  • 文字別の所で登場回数ゼロの見えない文字も結果に表示する様にした
    • printf を使わずにやりたかったけど面倒だったのでやめた

今後のこと

  • 添削希望者は自分のブログに「添削希望」と書いて公式wikiにトラックバック
  • 希望者が多い場合は「添削しやすい」コードを添削します
  • 約一週間かかります
  • 忙しいときはできないかもしれません

Posted in | コメントはありません |

第23回Ruby勉強会@関西の初級者レッスン演習問題の解答例たち

Posted by okkez Fri, 22 Feb 2008 15:04:00 GMT

ざっとこんなもんかな。時間ある時に勝手に添削する。

# 添削希望のものだけね。

Kanasan の 100 マス計算

趣味も大分入るけど以下のような感じで添削しました。

  • 題意を勘違いしてる?
    • 演算子もランダムにして欲しかったけどこれはこれでアリなので活かすことにする
  • 使える演算子は決まっているので、定数化する
  • 入力値のチェックに正規表現を使っていたがこの程度なら文字列比較で十分
  • 一度初期化したら変わらない変数は定数にする
  • メソッド定義のカッコは省略しない
    • るりまのフォーマットに従う方向で。
  • exit 1 にしとく
    • コマンドの失敗時の戻り値は 1 であることが多いため
  • 一文字変数はスコープが短い所で使用する
  • 配列の変数名は配列であることが予想できるような名前にする
kanasan's 100マス計算添削後
#! /usr/bin/ruby OPERATORS = %w[+ - * / %] DIGIT = 2 SEPARATOR = " " VERTICAL_LINE = "|" HORIZONTAL_LINE = "-" CROSS_LINE = "+" operator = ARGV[0].to_s unless OPERATORS.include?(operator) puts "wrong parameter: operator is only +-*/%" exit 1 end def display100Math(xs, ys, operator, display_answer) print SEPARATOR * (DIGIT - operator.size) print operator print SEPARATOR print VERTICAL_LINE xs.to_a.each do |x| print SEPARATOR print SEPARATOR * (DIGIT - x.to_s.size) print x end puts "" print HORIZONTAL_LINE * DIGIT print HORIZONTAL_LINE print CROSS_LINE xs.size.times do print HORIZONTAL_LINE print HORIZONTAL_LINE * DIGIT end puts "" ys.each do |y| print SEPARATOR * (DIGIT - y.to_s.size) print y print SEPARATOR print VERTICAL_LINE if display_answer xs.each do |x| answer = y.send(operator, x) print SEPARATOR print SEPARATOR * (DIGIT - answer.to_s.size) print answer end end puts "" end end xs = (1..9).sort_by{rand} ys = (1..9).sort_by{rand} display100Math(xs, ys, operator, false) puts "" display100Math(xs, ys, operator, true)

やらなかったこと

  • 100マス化
    • 今のは 81 マスしかない
  • 短く簡潔にすること
    • やっちゃうと大改造になるため(元が残らない)

sixeight さんの 100 マス計算

同じく趣味が入るけどこんな感じ。

  • いくつかの変数名/メソッド名を変更した
    • 多分、わかりやすくなった?
  • initialize に仕事積めすぎなので @header を make_question に移動
    • そうするとインスタンス変数である必要がなくなるのでローカル変数にした
  • make_matrix は表作成専用にした

適切に仕事を分割することでよりよくなったはず。

sixeight's 100 マス計算添削後
class Hyakumasu def initialize @op = [:+, :-, :*, :/][rand(4)] @row = (0..9).sort_by{ rand } @col = (0..9).sort_by{ rand } end def make_matrix header = "#{@op} | " + @col.join(' ') + "\n--|" + "---" * @col.length @row.inject(header){|r, v| r + "\n#{v} |" + (block_given? ? yield(v) : '') } end def display puts make_matrix puts puts make_matrix{|v| @row.map {|i| "%3d" %((@op == :/ && i.zero?) ? 0 : v.send(@op, i)) }.join } end end Hyakumasu.new.display

Posted in , | コメントはありません |

第23回 Ruby勉強会@関西に行ってきた

Posted by okkez Sun, 17 Feb 2008 13:56:00 GMT

いつも通りみんなの集合時間より早く行って簡単な打ち合わせ。

日本Rubyの会 公式Wiki - 第23回 Ruby/Rails勉強会@関西

Ruby1.9 の仕様 − Array 関連の新しい機能 by こなみ さん

よくも悪くも講義?って感じだった。 最後の方の内容が面白かっただけに尻切れとんぼっぽくなったのが残念。

純粋関数型言語Ruby(3) 30分でわかるMonad by 氏久(ujihisa)さん

やっぱり 30min ではわからなかった Monad

参加者大半おいてけぼり??

Rails以外のWebアプリケーションフレームワーク」 by yhara さん

merb, capmping, ramaze の紹介。前二つはストヤン経由で聞いてたけど結局試してなくてごめんなさい。

今度、試す。

Rubyのリフレクション by 大林さん

おもしろかった。久しぶりに燃えるセッションだった。 # って自分こんなにリフレクション好きだったっけ?

すごく勉強になった。要復習。

これくらいの濃さのセッションが毎回あるといいのだけど。

Ruby初級者向けレッスン第17回 by okkezさん

自分のセッション。

初級者レッスンと言いつつ、るりまの宣伝乙。 演習問題もリファレンスを見てやってもらう前提だったのでこんなものかと。

演習問題の最後に、使うかもしれないメソッド一覧を載せたのは良かったらしい。Keep

そう言えば、自分が発表する場合の時間の読み方がかなり正確になってきた気がする。

Posted in | コメントはありません |

Date#cweek

Posted by okkez Wed, 23 Jan 2008 13:18:00 GMT

Ruby には暦週を求める Date#cweek というメソッドがあるが、こいつは月曜日スタートで日曜日で終わる。

でも、たまに日曜日から土曜日での暦週が欲しくなることがある。というか今日必要になった。

なので作ってみた。

class Date def myweek if wday == 0 (self + 3).cweek # cweek + 1 でいいかも。。。 else cweek end end end

とりあえず試した範囲では上手く動いてた。

Posted in | コメントはありません |

第22回 Ruby/Rails勉強会

Posted by okkez Mon, 14 Jan 2008 14:37:00 GMT

行ってきました。

第22回 Ruby/Rails勉強会@関西

Ruby勉強会のときは大抵、天気がよろしくないってのは定説なのだろうか。今回もくもりときどき雨。

今回は、スタッフのうちで学生なメンバーが動けなかったので、急遽司会担当。事前打ち合わせほとんどなしで臨んだ。

人工知能で学ぶ Ruby (入門) by 石川 さん

Excel で一次元な RPG っていうのが面白かった。Ruby で変愚蛮怒みたいなのを作ると面白いのかもとか思ったけど、その場で名前が思い出せなかったので言わなかった。

大学での人工知能の講義の初歩の初歩ってこんな感じなのかってのがわかって面白かった。

Miyako1.4でつくるアニメーション by サイロス誠 さん

サイロスさんがずっと開発してらっしゃるゲーム作成のためのライブラリの新バージョンの紹介。

動画の上で画像を動かすことが出来たのは面白いと思った。

「Ruby on Windows」出版記念講演 by cuzic さん

基調講演っぽくて良かった。 cuzic さんの熱い思いが伝わりました。

その後の即売会も、盛り上がってたみたいで良かったです。

ショートな課題でジャムプログラミング by こなみ さん

いくつか、問題があってそれを解くというモノ。 前日に解いていたので、自分のを Hiki にこっそりアップしておいた。

ナビゲータがもう少ししっかりコントロールして欲しかった。

懇親会

例によって懇親会は mixi で書くかもしれない。

Posted in | コメントはありません |

第 20 回 Ruby 勉強会@関西 初級者レッスン演習課題解答例(RSpec版)

Posted by okkez Sun, 04 Nov 2007 03:22:00 GMT

Test::Unit版 と同じことを RSpec でやってみた。

第 20 回 Ruby 勉強会@関西 に解答例(answer.pdf)がアップされていますがそれの捕捉説明的なものです。前回と同様、配布資料(print.pdf)が手元にある前提で進めます。

Test::Unit 版とは書いた時期が違うので微妙に実装が違っていたりします。

ちなみに RSpec を使って書くのは初めてです。RSpec についてはるびまの記事を参照してください。

なお、stack_spec.rb は、そのとき注目している機能に関係のある部分のみ掲載します。

実装するメソッド

  • empty?
    • スタックが空なら true、そうでなければ false を返す。
  • size
    • スタックのサイズを返す。
  • push(val)
    • 引数の値をスタックの一番上に積む。
  • pop
    • スタックの一番上の値を取り除いて返す。スタックが空の場合は Stack::EmptyStackErrorが発生する。

Step1

  • Stack#empty? のテスト
  • 「新しいスタックの empty? は真」
stack_spec.rb
require 'stack' describe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end after do @empty_stack = nil end end

Stack が空のときの動作を記述。

  • spec -c で実行すると色つきで結果が表示される
stack.rb
class Stack def empty? true end end
  • とりあえず Test::Unit 版と同じように Fake it してみた

Step2

  • Stack#push と Stack#pop のテスト
  • 「新しいスタックに 3 を push して pop すると 3 が返る」
stack_spec.rb
describe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end after do @stack = nil end end
  • describe の引数に書いた内容を before のブロックに記述する
  • it 〜 end に実際にチェックしたい内容を記述する
  • after のブロックに後始末の処理を記述する
  • before と after は省略可能
stack.rb
class Stack def empty? true end def push(val) end def pop 3 end end
  • Test::Unit 版と同じように Fake it

Step3

  • Stack#size のテスト
  • 「新しいスタックに 3 を push すると size は 1」
stack_spec.rb
describe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
  • Step2 と事前準備の内容は同じなので同じ describe ブロック内に it 〜 end を追加する
stack.rb
class Stack def empty? true end def push(val) end def pop 3 end def size 1 end end
  • 実装はひたすら Fake it

Step4

  • Stack#size のテスト (2)
  • 「新しいスタックに 3 を push すると size は 1」
  • 「さらに 5 を push すると size は 2」
stack_spec.rb
describe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end end
  • 前のステップとは状況が違うので新しい describe ブロックを用意した
stack.rb
class Stack def initialize @size = 0 end def empty? true end def push(val) @size += 1 end def pop 3 end def size @size end end
  • Fake it のままでは通らないので、ちょっとまじめに実装。

Step5

  • Stack#empty? のテスト (2)
  • 「新しいスタックに 3 を push すると empty? は偽」
stack_spec.rb
describe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end it "should not be empty" do @stack.should_not be_empty end after do @stack = nil end end
  • 事前準備の状況が Step2, Step3 と同じなので同じ describe に振舞を追加
stack.rb
class Stack def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop 3 end def size @size end end

Step6

  • Stack#pop のテスト (2)
  • 「新しいスタックを pop すると Stack::EmptyStackError が発生する」
stack_spec.rb
describe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end it "should raise Stack::EmptyStackError" do lambda{ @empty_stack.pop }.should raise_error Stack::EmptyStackError end after do @empty_stack = nil end end
stack.rb
class Stack class EmptyStackError < StandardError end def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? 3 end def size @size end end

Step7

  • Stack#pop のテスト (3)
  • 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」
stack_spec.rb
describe Stack, "when push 3 and 5, then pop" do before do @stack = Stack.new @stack.push(3) @stack.push(5) @stack.pop end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
stack.rb
class Stack class EmptyStackError < StandardError end def initialize @size = 0 end def empty? size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? @size -= 1 3 end def size @size end end

Step8

  • Stack#pop のテスト (4)
  • 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」
stack_spec.rb
describe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end it "should pop 5" do @stack.pop.should == 5 end after do @stack = nil end end
stack.rb
class Stack class EmptyStackError < StandardError end def initialize @size = 0 @stack = [] end def empty? size == 0 end def push(val) @size += 1 @stack.push(val) end def pop raise EmptyStackError if empty? @size -= 1 @stack.pop end def size @size end end

Step9

最後にリファクタリングします。

stack_spec.rb
require 'stack' describe Stack, "when empty" do before do @empty_stack = Stack.new end it "should be empty" do @empty_stack.should be_empty end it "should raise Stack::EmptyStackError" do lambda{ @empty_stack.pop }.should raise_error Stack::EmptyStackError end after do @empty_stack = nil end end describe Stack, "when push 3" do before do @stack = Stack.new @stack.push(3) end it "should pop 3" do @stack.pop.should == 3 end it "should size 1" do @stack.size.should == 1 end it "should not be enpty" do @stack.should_not be_empty end after do @stack = nil end end describe Stack, "when push 3 and 5" do before do @stack = Stack.new @stack.push(3) @stack.push(5) end it "should size 2" do @stack.size.should == 2 end it "should pop 5" do @stack.pop.should == 5 end after do @stack = nil end end describe Stack, "when push 3 and 5, then pop" do before do @stack = Stack.new @stack.push(3) @stack.push(5) @stack.pop end it "should size 1" do @stack.size.should == 1 end after do @stack = nil end end
  • 上記の仕様を満たすように実装コードを書き換えます。
  • 本来はちょっとずつ書き換えるべきですが、最終結果のみ書いておきます
stack.rb
require 'forwardable' class Stack extend Forwardable def_delegators(:@stack, :empty?, :push, :size) def initialize @stack = [] end def pop raise EmptyStackError if empty? @stack.pop end class EmptyStackError < StandardError end end
  • forwardable ライブラリを使用するとメソッドを指定して特定のオブジェクトに委譲できる
  • 使用方法はリファレンスマニュアルを確認してください

Posted in | コメントはありません |

第20回 Ruby 勉強会 @ 関西

Posted by okkez Fri, 02 Nov 2007 11:52:00 GMT

行ってきた。

第 20 回 Ruby 勉強会

内容は置いといて、初級者レッスンの解答例(answer.pdf)に補足説明を入れておく。 資料は上記サイトでダウンロードしてくださいな。

以下では、配布資料(print.pdf)が手元にあることを前提に進めていきます。

実装するメソッド

  • empty?
    • スタックが空なら true、そうでなければ false を返す。
  • size
    • スタックのサイズを返す。
  • push(val)
    • 引数の値をスタックの一番上に積む。
  • pop
    • スタックの一番上の値を取り除いて返す。 スタックが空の場合は Stack::EmptyStackErrorが発生する。

テスト駆動開発版

TDD でやってくのです。

Step1

  • Stack#empty? のテスト
  • 「新しいスタックの empty? は真」

ということなのでまずはテストを書きます。

test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end end
空っぽのテストなので簡単ですね。 * Stack#empty? のテストをしたいのでテストメソッド名は test\_empty? * assert は 第一引数の式が真で無い場合にに失敗する で、実装。普通は失敗してから Fake it するのですが、print.pdfでは既に失敗してるのでここではいきなり成功しときます。
stack.rb
class Stack def empty? true end end
ここでは成功さえすれば良いのでいきなり true を返します。 #### Step2 * Stack#push と Stack#pop のテスト * 「新しいスタックに 3 を push して pop すると 3 が返る」 まずテスト。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end end
* Stack#push と Stack#pop のテストなのでテストメソッド名は test\_push\_and\_pop
stack.rb
class Stack def empty? true end def push(val) end def pop return 3 end end
* Stack#push はテストが通るので、まだ空っぽでいい。 * Stack#pop はテストを通すために 3 を返す。 明らかにテストが不足しているが、まだ書かない。一歩ずつ確実に進んで行く。 #### Step3 * Stack#size のテスト * 「新しいスタックに 3 を push すると size は 1」 なにがなんでもまずテスト。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') end end
* push してから size を確認するのでテストメソッド名は test\_push\_and\_size * まだ一個しか push しない * 二個 push したらどうなる?とかこの段階では考えない。
stack.rb
class Stack def empty? true end def push(val) end def pop return 3 end def size 1 end end
* 3 を push したら size が 1 になればいいのだから Stack#size は 1 を返せばいい * しつこく Fake it * テストを通すことだけ考える。 #### Step4 * Stack#size のテスト (2) * 「新しいスタックに 3 を push すると size は 1」 * 「さらに 5 を push すると size は 2」 とりあえずテストを書いてみる。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end end
素直に書けばこうなるはず。 実装コード
stack.rb
class Stack def initialize @size = 0 end def empty? true end def push(val) @size += 1 end def pop return 3 end def size return @size end end
* Fake it では通らなくなったので、マジメに実装。 * 放っておいても今のところ大丈夫な部分は放置。 #### Step5 * Stack#empty? のテスト (2) * 「新しいスタックに 3 を push すると empty? は偽」 いつまでも Stack#empty? が true しか返さないのはまずいので、false を返す場合のテストを書いてみる。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end end
で、テストが失敗するのでテストが通るように実装コードを変更します。
stack.rb
class Stack def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop return 3 end def size return @size end end
* @size == 0 が true/false のいずれかを返すので return します * 関係ない部分は弄りません #### Step6 * Stack#pop のテスト (2) * 「新しいスタックを pop すると Stack::EmptyStackError が発生する」 誰がなんと言おうがテストコードを先に書きます。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end end
* 空のスタックに対して pop するのでテストメソッドは test\_empty\_pop * assert_raise はブロックに例外が発生する可能性のある処理を記述する 実装コード
stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? return 3 end def size return @size end end
* Stack#pop を修正。 * Stack#empty? が true の場合に例外を発生させる * 関係ない部分は意地でも書き換えない #### Step7 * Stack#pop のテスト (3) * 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」 例によってテストを先に書きましょう。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end def test_push_push_pop_and_size @stack.push(3) @stack.push(5) @stack.pop assert_equal(1, @stack.size, 'pop decrements the size.') end end
* テストメソッド名は test\_push\_push\_pop\_and\_size * 特別なことはしていません 実装コード
stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 end def empty? return @size == 0 end def push(val) @size += 1 end def pop raise EmptyStackError if empty? @size -= 1 return 3 end def size return @size end end
* 関係ない部分は弄らない、と #### Step8 * Stack#pop のテスト (4) * 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」 そろそろ佳境ですが、テストを先に書きます。
test_stack.rb
require 'stack' require 'test/unit' class TestStack < Test::Unit::TestCase def setup @stack = Stack.new end def test_empty? assert(@stack.empty?, 'a new stack is empty.') end def test_push_and_pop @stack.push(3) assert_equal(3, @stack.pop, 'pop returns the last value.') end def test_push_and_size @stack.push(3) assert_equal(1, @stack.size, 'push increments the size.') @stack.push(5) assert_equal(2, @stack.size, 'push increments the size.') end def test_push_and_empty? @stack.push(3) assert_equal(false, @stack.empty?, 'a stack with data is not empty.') end def test_empty_pop assert_raise(Stack::EmptyStackError, 'to pop a empty stack raise an error.') {@stack.pop} end def test_push_push_pop_and_size @stack.push(3) @stack.push(5) @stack.pop assert_equal(1, @stack.size, 'pop decrements the size.') end def test_push_push_and_pop @stack.push(3) @stack.push(5) assert_equal(5, @stack.pop, 'pop returns the last value.') end end
実装コード
stack.rb
class Stack class EmptyStackError < StandardError; end def initialize @size = 0 @values = Array.new end def empty? return @size == 0 end def push(val) @size += 1 @values[@size - 1] = val end def pop raise EmptyStackError if empty? val = @values[@size - 1] @size -= 1 return val end def size return @size end end
外から見た動作ではスタックぽく動くようになりましたね。 さて、 irb で中身を覗いてみましょう。
irb(main):001:0> load 'stack.rb' => true irb(main):002:0> s = Stack.new => # irb(main):003:0> s.push 1 => 1 irb(main):004:0> s.push 2 => 2 irb(main):005:0> s.push 3 => 3 irb(main):006:0> s.inspect => "#" irb(main):007:0> s.pop => 3 irb(main):008:0> s.pop => 2 irb(main):009:0> s.pop => 1 irb(main):010:0> s.inspect => "#" irb(main):011:0>

@values の中身が Stack#pop しても消えませんね。これはスタックとしては拙いのでリファクタリングしましょう。

というわけで次回に続く。

Posted in | コメントはありません |

Older posts: 1 2 3 4 5